I created this class for being immutable and having a fluent API:
public final class Message {
public final String email;
public final String escalationEmail;
public final String assignee;
public final String conversationId;
public final String subject;
public final String userId;
public Message(String email, String escalationEmail, String assignee, String conversationId, String subject, String userId) {
this.email = email;
this.escalationEmail = escalationEmail;
this.assignee = assignee;
this.conversationId = conversationId;
this.subject = subject;
this.userId = userId;
}
public Message() {
email = "";
escalationEmail = "";
assignee = "";
conversationId = "";
subject = "";
userId = "";
}
public Message email(String e) { return new Message(e, escalationEmail, assignee, conversationId, subject, userId); }
public Message escalationEmail(String e) { return new Message(email, e, assignee, conversationId, subject, userId); }
public Message assignee(String a) { return new Message(email, escalationEmail, a, conversationId, subject, userId); }
public Message conversationId(String c) { return new Message(email, escalationEmail, assignee, c, subject, userId); }
public Message subject(String s) { return new Message(email, escalationEmail, assignee, conversationId, s, userId); }
public Message userId(String u) { return new Message(email, escalationEmail, assignee, conversationId, subject, u); }
}
My question is, will the optimizer be able to avoid lots of object creations when a new object is created like this:
Message m = new Message()
.email("foo@bar.com")
.assignee("bar@bax.com")
.subject("subj");
Is there anything to be gained from making a separate mutable builder object instead?
Update 2: After reading apangin's answer my benchmark is invalidated. I'll keep it here for reference of how not to benchmark :)
Update: I took the liberty of measuring this myself with this code:
public final class Message {
public final String email;
public final String escalationEmail;
public final String assignee;
public final String conversationId;
public final String subject;
public final String userId;
public static final class MessageBuilder {
private String email;
private String escalationEmail;
private String assignee;
private String conversationId;
private String subject;
private String userId;
MessageBuilder email(String e) { email = e; return this; }
MessageBuilder escalationEmail(String e) { escalationEmail = e; return this; }
MessageBuilder assignee(String e) { assignee = e; return this; }
MessageBuilder conversationId(String e) { conversationId = e; return this; }
MessageBuilder subject(String e) { subject = e; return this; }
MessageBuilder userId(String e) { userId = e; return this; }
public Message create() {
return new Message(email, escalationEmail, assignee, conversationId, subject, userId);
}
}
public static MessageBuilder createNew() {
return new MessageBuilder();
}
public Message(String email, String escalationEmail, String assignee, String conversationId, String subject, String userId) {
this.email = email;
this.escalationEmail = escalationEmail;
this.assignee = assignee;
this.conversationId = conversationId;
this.subject = subject;
this.userId = userId;
}
public Message() {
email = "";
escalationEmail = "";
assignee = "";
conversationId = "";
subject = "";
userId = "";
}
public Message email(String e) { return new Message(e, escalationEmail, assignee, conversationId, subject, userId); }
public Message escalationEmail(String e) { return new Message(email, e, assignee, conversationId, subject, userId); }
public Message assignee(String a) { return new Message(email, escalationEmail, a, conversationId, subject, userId); }
public Message conversationId(String c) { return new Message(email, escalationEmail, assignee, c, subject, userId); }
public Message subject(String s) { return new Message(email, escalationEmail, assignee, conversationId, s, userId); }
public Message userId(String u) { return new Message(email, escalationEmail, assignee, conversationId, subject, u); }
static String getString() {
return new String("hello");
// return "hello";
}
public static void main(String[] args) {
int n = 1000000000;
long before1 = System.nanoTime();
for (int i = 0; i < n; ++i) {
Message m = new Message()
.email(getString())
.assignee(getString())
.conversationId(getString())
.escalationEmail(getString())
.subject(getString())
.userId(getString());
}
long after1 = System.nanoTime();
long before2 = System.nanoTime();
for (int i = 0; i < n; ++i) {
Message m = Message.createNew()
.email(getString())
.assignee(getString())
.conversationId(getString())
.escalationEmail(getString())
.subject(getString())
.userId(getString())
.create();
}
long after2 = System.nanoTime();
System.out.println("no builder : " + (after1 - before1)/1000000000.0);
System.out.println("with builder: " + (after2 - before2)/1000000000.0);
}
}
I found the difference to be significant (builder is faster) if the string arguments are not new objects, but all the same (see commented code in getString)
In what I imagine is a more realistic scenario, when all the strings are new objects, the difference is negligible, and the JVM startup would cause the first one to be a tiny bit slower (I tried both ways).
With the "new String" the code was in total many times slower as well (I had to decrease the n
), perhaps indicating that there is some optimization of the "new Message" going on, but not of the "new String".
My understanding is that the JIT compiler works by re-arranging the existing code and performing basic statistical analysis. I don't think though that the JIT compiler can optimize object allocation.
Your Builder is incorrect and your fluent API will not work the way you expect (create just a single object per built).
You need to have something like:
Then you can have something like.
First, your code didn't have a builder approach and generate a lot of object, but there is already an example of a builder so I will not add one more.
Then, regarding the JIT, short answer NO (there is no optimization of new object creation, except for dead code) ... long answer no but ... there is other mechanism that will optimize stuff in the JVM/
There is a string pool that avoid creation of multiple strings when using string literals. There is also a pool of object for each primitive wrapper type (so if you create a Long object with Long.valueOf it's the same object that is returned each time you ask for the same long ...). Regarding strings, there is also a string deduplication mechanism integrated in the G1 garbadge collector in java 8 update 20. You can test it with the following JVM options if you're using a recent JVM : -XX:+UseG1GC -XX:+UseStringDeduplication
If you really want to optimize new objet creation, you need to implement some sort of Object pooling and have your object being immutable. But be careful that this is not a simple task and you will end up having a lot of code dealing with object creation and managing pool size to not overflow your memory. So I advise you to do it only if it's really necessary.
Lastly, object instantiation in the heap is a cheap operation unless you create millions of objects in a second and the JVM is doing a lot of optimization in a lot of fields so, unless some good performance benchmark (or memory profiling) prove that you have an issue with object instantiation don't think about it too much ;)
Regards,
Loïc
No, but instantiation is a very cheap operation on the JVM. Worrying about this performance loss would be a typical example of premature optimization.
Working with immutables is generally a good approach. On the other hand builders also won't hurt you, if you use the builder instances in a small context, so their mutable state is accessible only in a small, local envorironment. I don't see any severe disadvantages on any side, it is really up to your preference.
In builder pattern, you should do like this:
Which
Message.new()
will create an object of builder class, the functionemail(..)
andassignee(...)
will returnthis
. And the lastbuild()
function will create the Object based on your data.Yes, HotSpot JIT can eliminate redundant allocations in a local context.
This optimization is provided by the Escape Analysis enabled since JDK 6u23. It is often confused with on-stack allocation, but in fact it is much more powerful, since it allows not only to allocate objects on stack, but to eliminate allocation altogether by replacing object fields with variables (Scalar Replacement) that are subject to further optimizations.
The optimization is controlled by
-XX:+EliminateAllocations
JVM option which is ON by default.Thanks to allocation elimination optimization, both your examples of creating a
Message
object work effectively the same way. They do not allocate intermediate objects; just the final one.Your benchmark shows misleading results, because it collects many common pitfalls of microbenchmarking:
Let's measure it correctly with JMH. As a bonus, JMH has the allocation profiler (
-prof gc
) which shows how many bytes are really allocated per iteration. I've added the third test that runs withEliminateAllocations
optimization disabled to show the difference.Here are the results. Both
builder
andimmutable
perform equally and allocate just 40 bytes per iteration (exactly the size of oneMessage
object).