您的浏览器过于古老 & 陈旧。为了更好的访问体验, 请 升级你的浏览器
Ready 发布于2019年12月07日 18:44 最近更新于 2019年12月07日 21:33

原创 Java 编译器的优化魔法之【常量引用替换】

4572 次浏览 读完需要≈ 8 分钟 Javajavac

内容目录

在介绍Java编译器(javac)的常量引用替换之前,我先说一个我所经历的与之相关的真实案例。

真实的惨案

还记得,是在三年前,有位技术同事负责维护一个老系统。

老系统中有一个常量类(Const.java)用来存储所有的常量配置数据,包括用于请求第三方平台的 APP_IDAPP_KEY(意即账号和密钥),另一个实现该功能的工具类(ThirdPartyClient.java))需要依赖这两个配置数据。

大致的代码如下:

public class Const {

	/** 请求第三方平台所需的 APP_ID */
	public static final String APP_ID = "theAppId";
	/** 请求第三方平台所需的 APP_KEY */
	public static final String APP_KEY = "thePassword";

}
public class ThirdPartyClient {

	public void postRequest(String args) {
		String text = Const.APP_ID + Const.APP_KEY + args;
		// handle the text
		// ……
	}

}

由于正式环境和测试环境需要使用不同的账号和密钥,因此每次在打包发布前,都需要将测试环境的配置修改为正式环境的配置,然后编译 => 打包 => 部署到正式环境。

有一次,那是一个月黑风高的夜晚……该同事忘记修改配置,就直接将程序部署到了正式环境。部署之后,当然很快就发现了故障……所幸发现得还算及时,而且由于是在晚上凌晨,因此也没造成什么重大损失。

该同事开始全力挽救,希望能尽快解决掉该问题。

修改好了配置后,由于将程序代码重新完整编译、打包、部署上传需要花费比较多的时间,救急如救火,同事就打算不重新打包上传,而是直接将Const.java重新编译后的字节码文件(Const.class)上传并覆盖原文件,然后重启。

问题来了,奇怪的是:重启之后,Web 应用仍然使用的是 旧的配置(也就是用于 测试环境 的配置)?!

经过反复验证,同事确认上传的字节码文件为修改后的字节码文件,甚至又重试了几次,但问题依旧。TA 通过 debug 查看输出,也证明这两个配置已经是正式环境的配置了。

于是,紧急情况下,同事开始寻求支援。

我听了同事的描述后,建议同事将修正后重新编译的 ThirdPartyClient.class 上传并替换掉原文件,然后重启试试。

不出意外,Web 应用程序恢复正常,使用的配置当然也是正式环境的了。

同事百思不得其解。

Java编译器(javac)在编译Java源代码时,会读取并分析源代码,并进行一些优化工作。实际上,出现这个问题的根本原因就是 Java 编译器的优化特性之一 —— 常量引用替换

“罪魁祸首”——常量引用替换

当Java编译器编译源代码时,如果发现某处代码引用了「常量」(同时使用staticfinal两个关键字来修饰),且该常量为字面值形式原始数据类型字符串,Java编译器会将此处的常量引用优化为常量值的「内联」(inline)。

就拿上面的例子来说,ThirdPartyClient.java中的代码将被优化为如下等价代码:

public class ThirdPartyClient {

	public void postRequest(String args) {
		// Const.APP_ID  的引用 被替换成了该常量在编译时的字符串字面值
		// Const.APP_KEY 的引用 被替换成了该常量在编译时的字符串字面值 
		String text = "theAppId" + "thePassword" + args;
		// handle the text
		// ……
	}

}

在Java中,常量在初始化赋值后是不能再被改变的,因此Java编译器就会针对常量进行优化,从而在运行时避免变量引用的调用开销。

当然,它也有可能带来「坏处」,本文中提到的「惨案」即为一例。

大家了解了Java编译器的这个优化特性之后,自然也就能够轻松避免这样的问题。

小结

  • Java编译器会在编译源代码时,也会进行优化,会将代码中的常量引用替换为对应的字面值。
  • 不是所有的常量引用都会被替换,该常量必须是原始数据类型字符串类型,常量的值必须是字面值(例如:1true"Hello")或在编译期间能够直接计算出结果的常量表达式(例如:2 * 25 + 1"Hello" + "World")。

注意

  • 在应用部署的过程中,尽量不要通过类似的方式来修改配置参数!这种重复的必要性操作是很容易疏忽的,一旦疏忽,就可能给生产环境带来重大损失。我们应该编写程序代码或使用第三方工具,确保依赖于运行环境的参数配置能够自动化智能化的切换,而无需在每次部署时都提心吊胆。
  • 上面示例代码中的"theAppId" + "thePassword"实际上也会被 Java 编译器优化合并为一个字符串字面值"theAppIdthePassword"
  • CodePlayer技术交流群1
  • CodePlayer技术交流群2

1 条评论

二周 · 5年前

right!(期待加上emoji)

0 0 0

撰写评论