jit会优化新对象吗?

我创建了这个类是不可变的,并且具有流畅的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); } } 

我的问题是,当像这样创建一个新对象时,优化器是否能够避免大量的对象创建:

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

相反,制作单独的可变构建器对象有什么好处吗?

更新2:在阅读了apangin的答案后,我的基准测试无效。 我会把它留在这里作为参考如何不进行基准测试:)

更新:我冒昧地使用此代码自行测量:

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

如果字符串参数不是新对象,那么我发现差异很大(构建器更快),但都是相同的(请参阅getString中的注释代码)

在我想象的是一个更现实的场景中,当所有字符串都是新对象时,差异可以忽略不计,JVM启动会导致第一个字符串变慢(我尝试了两种方式)。

使用“new String”代码总共慢了很多倍(我不得不减少n ),或许表明正在进行“新消息”的某些优化,而不是“新字符串”。

是的,HotSpot JIT可以消除本地环境中的冗余分配。

此优化由JDK 6u23启用的Escape Analysis提供。 它经常与堆栈分配混淆,但实际上它更强大,因为它不仅允许在堆栈上分配对象,而且通过用变量(标量替换)替换对象字段来完全消除分配。优化。

优化由-XX:+EliminateAllocations JVM选项控制,默认情况下为ON。


由于分配消除优化,您创建Message对象的示例都以相同的方式有效地工作。 他们不分配中间对象; 只是最后一个。

您的基准测试显示误导性结果,因为它收集了许多微基准测试的常见缺陷 :

  • 它在一个方法中包含了几个基准;
  • 它测量OSR存根而不是最终编译版本;
  • 它没有做热身迭代;
  • 它不会消耗结果等

让我们用JMH正确测量它。 作为奖励,JMH具有分配探查器( -prof gc ),它显示每次迭代实际分配的字节数。 我添加了禁用EliminateAllocations优化的第三个测试以显示差异。

 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"; } } 

结果如下。 builderimmutable执行相同的操作,每次迭代只分配40个字节(完全是一个Message对象的大小)。

 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,000 ± 0,001 B/op MessageBench.immutableNoOpt:·gc.alloc.rate.norm avgt 10 280,000 ± 0,001 B/op 

我的理解是JIT编译器通过重新安排现有代码执行基本统计分析来工作 。 我不认为JIT编译器可以优化对象分配。

您的构建器不正确,您的流畅API将无法按预期工作(每个构建只创建一个对象)。

你需要有类似的东西:

  public class Message () { public final String email; public final String escalationEmail; private Message (String email,String escalationEmail) { this.email = email; this. escalationEmail = escalationEmail; } public static class Builder { public String email; public String escalationEmail; public static Builder createNew() { return new Builder(); } public Builder withEmail(String email) { this.email = email; return this; } public Builder withEscalation(String escalation) { this.escalation = escalation; return this; } public Builder validate() { if (this.email==null|| this.email.length<7) { throw new RuntimeException("invalid email"); } } public Message build() {¨ return new Message(this.email,this.escalation); } } } 

然后你可以有类似的东西。

 Message.Builder.createNew() .withEmail("exampple@email.com") .withEscalation("escalation") .validate() .build(); 

首先,您的代码没有构建器方法并生成大量对象,但是已经有一个构建器的示例,所以我不会再添加一个。

然后,关于JIT,简答NO(没有新对象创建的优化,除了死代码)…长回答否但是……还有其他机制可以优化JVM中的东西/

有一个字符串池,在使用字符串文字时避免创建多个字符串。 每个原始包装器类型还有一个对象池(因此,如果使用Long.valueOf创建一个Long对象,则每次请求相同的长度时,它都返回相同的对象…)。 关于字符串,在Java 8 update 20中的G1 garbadge收集器中还集成了一个字符串重复数据删除机制。如果您使用的是最新的JVM,可以使用以下JVM选项对其进行测试:-XX:+ UseG1GC -XX:+ UseStringDeduplication

如果您真的想要优化新的对象创建,则需要实现某种对象池并使对象不可变。 但请注意,这不是一项简单的任务,您最终会有很多代码处理对象创建和管理池大小而不会溢出内存。 所以我建议你只有在真的有必要时才这样做。

最后,堆中的对象实例化是一种廉价的操作,除非你在一秒钟内创建了数百万个对象,而JVM在很多领域都进行了大量的优化,所以除非一些好的性能基准测试(或内存分析)certificate你有对象实例化的问题不要考虑太多;)

问候,

卢瓦克

在构建器模式中,您应该这样做:

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

哪个Message.new()将创建构建器类的对象,函数email(..)assignee(...)将返回this 。 最后一个build()函数将根据您的数据创建Object。

优化器是否能够避免大量对象创建

不,但实例化是JVM上非常便宜的操作。 担心这种性能损失将是过早优化的典型例子。

相反,制作单独的可变构建器对象有什么好处吗?

使用不可变量通常是一种很好的方法。 另一方面,如果您在小环境中使用构建器实例,构建器也不会伤害您,因此只有在一个小的本地环境中才能访问它们的可变状态。 我认为任何一方都没有任何严重的缺点,这完全取决于你的偏好。