掌握Java 11的Constantdynamic

为了使JVM对动态语言更具吸引力,该平台的第七版已将invokedynamic引入了其指令集。 Java开发人员通常不会注意到此功能,因为它隐藏在Java字节码中。 简而言之,通过使用invokedynamic ,可以将方法调用的绑定延迟到第一次调用之前。 例如,该技术由Java语言用于实现lambda表达式,这些表达式仅在首次使用时才需要出现。 这样做, invokedynamic已发展成为一种基本的语言功能,我在先前的博客文章中对此进行了详细介绍 。 使用constantdynamic ,Java 11中引入了类似的机制,只是它延迟了常量值的创建。 这篇文章描述了此功能的目的和内部工作原理,并展示了如何使用Byte Buddy库生成利用此新指令的代码。

Java中的常量值是什么?

在Java 5之前,Java程序中的常量值只能是字符串或原始类型。 这些常量作为文字内置在语言中,甚至由javac编译器假定以减小类文件的大小。 例如,在以下代码片段中,从不实际读取only字段的值,而是在编译期间将其复制到其使用站点:

class ConstantSample {
  final String field = “foo”;
  void hello() {
    System.out.print(field);
  }
}

代替读取hello方法中的字段,生成的字节码将包含对常量foo的直接引用。 实际上,上述类将永远不会尝试读取该字段的值,可以通过使用Java反射对其进行更改来验证该字段的值,然后调用hello仍会打印foo

为了表示这样的常量值,任何Java类文件都包含一个常量池,可以将其视为一个表,该表写出存在于类范围内的任何常量值。 这意味着在方法中使用或用作字段值的常量,但也包含描述类的其他不可变信息,例如类的名称或被调用方法的名称及其声明的类型名称。 一旦在类的常量池中记录了一个值,就可以通过指向常量池中特定条目的偏移量来引用值。 这样做,在整个类中重复的值仅需要存储一次,因为偏移量当然可以多次引用。

因此,当在上述源代码中读取该字段时, javac发出一个字节代码,该字节代码引用常量池中foo值的偏移量,而不是发出对该字段的读取指令。 当该字段被声明为final时,可以做到这一点,其中javac会忽略反射值更改的边缘情况。 通过发出读取常量的指令,与读取字段的指令相比, javac还节省了一些字节。 这就是使这种优化有利可图的原因,特别是因为字符串和数字值在任何Java类中都相当普遍。 较小的类文件可帮助Java运行时更快地加载类,而显式的常量性概念可帮助JVM的JIT和AOT编译器应用进一步的优化。

所描述的针对相同常数的偏移量的重用还隐含重用值的标识。 由于用单个实例表示相等的字符串值,因此以下语句在Java中将声明为true:

assert “foo” == “foo”;

在幕后,foo的两个值都指向定义类的常量池中的相同常量池偏移量。 此外,JVM甚至可以通过遍历在常量池中找到的字符串来跨类对常量字符串进行重复数据删除。

恒定池存储的局限性

类文件的常量池中值的这种表格表示形式非常适合简单值,例如字符串和数字基元。 但是同时,当javac没有发现恒定的值时,它可能会带来非直观的后果。 例如,在以下类中, hello方法中未将唯一字段的值视为常量:

class NoConstantSample {
  final String field = “foo”.toString();
  void hello() {
    System.out.print(field);
  }
}

尽管toString方法对于字符串而言是微不足道的,但是这种情况对于不评估Java方法的javac仍然未知。 因此,编译器不能再将恒定池值作为print语句的输入。 相反,它必须发出该字段的字段读取指令,如前所述,该指令需要其他字节。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值