Does Java – JIT optimize new objects?

I created this class, which is immutable and has a smooth 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,userId); }
    public Message assignee(String a) { return new Message(email,a,userId); }
    public Message conversationId(String c) { return new Message(email,c,userId); }
    public Message subject(String s) { return new Message(email,s,userId); }
    public Message userId(String u) { return new Message(email,u); }

}

My question is whether the optimizer can avoid a large number of object creation when creating a new object like this:

Message m = new Message()
    .email("foo@bar.com")
    .assignee("bar@bax.com")
    .subject("subj");

Instead, what are the benefits of making a separate variable builder object?

Update 2: after reading apangin's answer, my benchmark is invalid I'll leave it here for reference. How not to benchmark:)

Update: I venture to use this code to measure myself:

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,userId);
    }

}

public static MessageBuilder createNew() {
    return new MessageBuilder();
}

public Message(String email,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,userId); }
public Message escalationEmail(String e) { return new Message(email,userId); }
public Message assignee(String a) { return new Message(email,userId); }
public Message conversationId(String c) { return new Message(email,userId); }
public Message subject(String s) { return new Message(email,userId); }
public Message userId(String u) { return new Message(email,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);
}


}

If the string parameter is not a new object, then I find that the difference is great (the builder is faster), but all are the same (see the comment code in getString)

I imagine a more realistic scenario. When all strings are new objects, the difference is negligible, and JVM startup will slow down the first string (I tried two ways)

Using "new string" code is many times slower in total (I have to reduce N), which may indicate that some optimization of "new message" is in progress, rather than "new string"

Solution

Yes, hotspot JIT can eliminate redundant allocation in the local environment

This optimization is provided by JDK 6u23 enabled escape analysis It is often confused with stack allocation, but it is actually more powerful because it not only allows objects to be allocated on the stack, but also completely eliminates allocation by replacing object fields with variables (scalar substitution) Optimization

Optimization is controlled by the - XX: eliminateallocations JVM option, which is on by default

Because of allocation elimination optimization, the examples you create message objects work effectively in the same way They do not assign intermediate objects; Just the last one

Your benchmark shows misleading results because it collects many micro benchmarks for common pitfalls:

>It contains several benchmarks in a method; > It measures the OSR stub instead of the final compiled version; > It does not do warm-up iterations; > It does not consume results, etc

Let's measure it correctly with jmh As a reward, jmh has an allocation profiler (- Prof GC), which shows how many bytes are actually allocated for each iteration I added a third test that disables elite allocations optimization to show differences

package bench;

import org.openjdk.jmh.annotations.*;

@State(Scope.Benchmark)
public class MessageBench {

    @Benchmark
    public Message builder() {
        return Message.createNew()
                .email(getString())
                .assignee(getString())
                .conversationId(getString())
                .escalationEmail(getString())
                .subject(getString())
                .userId(getString())
                .create();
    }

    @Benchmark
    public Message immutable() {
        return new Message()
                .email(getString())
                .assignee(getString())
                .conversationId(getString())
                .escalationEmail(getString())
                .subject(getString())
                .userId(getString());
    }

    @Benchmark
    @Fork(jvmArgs = "-XX:-EliminateAllocations")
    public Message immutableNoOpt() {
        return new Message()
                .email(getString())
                .assignee(getString())
                .conversationId(getString())
                .escalationEmail(getString())
                .subject(getString())
                .userId(getString());
    }

    private String getString() {
        return "hello";
    }
}

The results are as follows Both builder and immutable perform the same operation, allocating only 40 bytes per iteration (exactly the size of a message object)

Benchmark                                        Mode  Cnt     score     Error   Units
MessageBench.builder                             avgt   10     6,232 ±   0,111   ns/op
MessageBench.immutable                           avgt   10     6,213 ±   0,087   ns/op
MessageBench.immutableNoOpt                      avgt   10    41,660 ±   2,466   ns/op

MessageBench.builder:·gc.alloc.rate.norm         avgt   10    40,000 ±   0,001    B/op
MessageBench.immutable:·gc.alloc.rate.norm       avgt   10    40,001    B/op
MessageBench.immutableNoOpt:·gc.alloc.rate.norm  avgt   10   280,001    B/op
The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>