【问题标题】:Does Java compiler handle inline Strings efficiently?Java 编译器是否有效地处理内联字符串?
【发布时间】:2009-07-23 13:12:01
【问题描述】:

1.

static final String memFriendly = "Efficiently stored String";
System.out.println(memFriendly);

2.

System.out.println("Efficiently stored String");

Java 编译器会以相同的方式处理这两个(1 和 2)吗?

仅供参考:高效我指的是运行时内存利用率以及代码执行时间。例如第一种情况可以在堆栈加载变量 memFriendly 上花费更多时间吗?

【问题讨论】:

    标签: java string optimization compiler-construction


    【解决方案1】:

    这在Java Language Spec:

    每个字符串字面量都是一个引用 (§4.3) 到一个实例 (§4.3.1, §12.5) 类字符串(§4.3.3)。细绳 对象具有恒定值。细绳 文字,或者更一般地说,字符串 这是常量的值 表达式(第 15.28 节)- 被“实习”所以 至于共享独特的实例,使用 String.intern 方法。

    您也可以使用javap 工具亲自查看。

    对于此代码:

    System.out.println("Efficiently stored String");
    final String memFriendly = "Efficiently stored String";
    System.out.println(memFriendly);
    

    javap 给出以下内容:

    0:   getstatic     #2; //Field java/lang/System.out:Ljava/io/PrintStream;
    3:   ldc       #3; //String Efficiently stored String
    5:   invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    8:   getstatic     #2; //Field java/lang/System.out:Ljava/io/PrintStream;
    11:  ldc       #3; //String Efficiently stored String
    13:  invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    

    【讨论】:

    • 感谢您挖掘这个。我的用例是我的代码中有字符串(将被多次调用),这些字符串只使用一次,想想 logger.info("Log this");现在将这个字符串设为静态最终会提高性能吗?
    • 使用对静态最终字段的引用不太可能与内联文字有任何性能差异。由于 Hotspot 在运行时执行了如此多的优化,因此判断是否存在可测量差异的唯一方法是对其进行测量。关于微基准的相关问题:stackoverflow.com/questions/504103/…
    • 谢谢。我还使用了 javap 并将静态 final 的输出与内联版本进行了比较。两者完全相同。您对 Hotspot 的运行时优化是正确的,这首先让我感到困惑,我如何保证一种方法更好,以便我可以将其用作最佳实践。
    • @Monis: 如果你在你的应用程序中经常调用logger.info("Log this");,那么在方法调用中进行方法调用所花费的时间,尤其是将输出记录到磁盘上的时间将会很长,很长,很长超过字符串存储方式之间的速度差异。
    • @Grant k,如果字符串很长怎么办?
    【解决方案2】:
    public static void main(String[] args) {
        System.out.println("Hello world!");
    
        String hola = "Hola, mundo!";
        System.out.println(hola);
    }
    

    下面是 javap 显示的反汇编代码:

    0:   getstatic       #16; //Field java/lang/System.out:Ljava/io/PrintStream;
    3:   ldc     #22; //String Hello world!
    5:   invokevirtual   #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    8:   ldc     #30; //String Hola, mundo!
    10:  astore_1
    11:  getstatic       #16; //Field java/lang/System.out:Ljava/io/PrintStream;
    14:  aload_1
    15:  invokevirtual   #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    18:  return
    

    看起来第二个字符串正在被存储,而第一个字符串只是直接传递给方法。

    这是用 Eclipse 的编译器构建的,这可以解释我的答案和 McDowell 的不同。

    更新:如果hola 被声明为final,以下是结果(结果没有aload_1,如果我正在阅读此内容,则表示此字符串已存储并如您所料,内联):

    0:   getstatic       #16; //Field java/lang/System.out:Ljava/io/PrintStream;
    3:   ldc     #22; //String Hello world!
    5:   invokevirtual   #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    8:   ldc     #30; //String Hola, mundo!
    10:  astore_1
    11:  getstatic       #16; //Field java/lang/System.out:Ljava/io/PrintStream;
    14:  ldc     #30; //String Hola, mundo!
    16:  invokevirtual   #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    19:  return
    

    【讨论】:

    • 嗯,这很正常,因为两个字符串有不同的文本,而在 McDowell 中它们是相同的。
    • 哇!感谢您挖掘这个。我的用例是我的代码中有字符串(将被多次调用),这些字符串只使用一次,想想 logger.info("Log this");现在将这个字符串设为静态最终会提高性能吗?
    【解决方案3】:

    在这种情况下,编译器将同时处理两者。

    只要在编译时定义了一个字符串,Java 就会优化字符串的存储。

    如果一个字符串是在运行时定义的,Java 没有办法做同样的优化。

    【讨论】:

    • 我也这么认为,但我找不到具体的参考,我的意思是理论上。谢谢 这是一篇很好的文章,有参考,但不是我要找的:muzso.hu/2009/03/05/…
    • 我找不到任何具体的文章或规格,抱歉。
    【解决方案4】:

    您拥有的代码是等价的,因为字符串文字是由编译器自动执行的。

    如果你真的很关心字符串的性能并且会一遍又一遍地重用相同的字符串,你应该看看字符串类的 intern 方法。

    http://java.sun.com/javase/6/docs/api/java/lang/String.html#intern()

    【讨论】:

    • 不,不,不!出于“性能原因”(这个词几乎让我呕吐)而随意实习字符串是错误的。除非您进行大量字符串处理,否则只会给您带来麻烦。
    • 除了厌恶之外,你还有什么可以支持你毫无根据的说法吗?我给出了使用实习生的充分理由,在这种情况下,需要一遍又一遍地重用同一组字符串。为什么每次都创建新的?实习生的唯一缺点是实习生的字符串永远不会被垃圾收集。我想这就是你所抱怨的。但是,如果您将在程序的整个生命周期中重复使用相同的字符串,那么实习它们而不是每次都创建新字符串是有意义的。
    • 看到这个它给出了实习生的利弊。 mindprod.com/jgloss/interned.html
    • “为什么不调用intern()”这个问题的答案在您提供的“Java 词汇表”文章中给出了确切的答案——编译时出现的所有String 文字都会自动被实习. 考虑清楚。您有一个在编译时不存在的字符串,但会根据一些运行时信息(用户输入、控制台参数等)而有所不同。如果您只创建一次字符串并传递它,intern() 不会为您带来任何好处。如果您创建 same 运行时字符串 repeatedly,那么您可能违反了 DRY,最好的优化可能是消除重复。
    猜你喜欢
    • 1970-01-01
    • 2020-09-28
    • 2011-01-11
    • 1970-01-01
    • 2012-01-24
    • 1970-01-01
    • 1970-01-01
    • 2010-11-27
    • 1970-01-01
    相关资源
    最近更新 更多