常量传播是现代的编译器中使用最广泛的优化方法之一,它通常应用于高级中间表示(IR)。该方法解决了静态检测表达式在运行过程中是否总是求值为唯一常数的问题,如果在调用过程中知道哪些变量会有常数值,以及这些值会是什么,编译器就能在编译过程中简化常数。
本文介绍一下Javac常量传播对类初始化的影响
第一,JavaCompiler。
在虚拟机运行java类时,需要初始化类。一般而言,java代码需要在虚拟机中至少编译两次(至少说明程序和JIT都可以对编译进行动态优化)。javacompiler在编译时对代码进行优化。
事实上,javac的优化更为保守:基本上是有限的方法,比如简单的常量传播,无用的扫码等。
第二,类的加载和卸载。
类加载器:
加载->确认->准备->分析->初始化->使用。
激发条件:
l 不需要考虑JIT分支预测):新建对象(实际上就是invokespecial)、getstatic/putstatic(数量很大的读写成员+final修正引用类型)、getstatic(读写类引用类型)、invokestatic。
l 反射调用
类卸载过程:
卸载(GC回收:常数符号引用清除,内存回收)
触发条件(同时符合下列三个条件):
l 成员域和方法的类静态引用类型不被其他类引用。
l 无分类示例。
l 破坏类loader。
第三,类加载案例。
编码测试
实施结果如下。
(1) Child.A不仅不会触发子类的载入,也不会触发父类。由于A是成员,Javac根据最终修饰符指向常量和A变量,以常量符号引用替代代码段扫描,实现常量传播优化。
[]Child.A=+Child.A+::未触发基础载入,未触发子载入][]编译后[]Child.A=123::未触发基础载入,未触发子载入]
a指的是能让父亲装货的数量。例如修正A。
public static final String A = new String("AA");
(2)Child.B不会触发子类的初始化或触发子类的初始化,主要原因是B成员指向一个常量,但它可能会被其他线程修改,因为程序正在运行,所以不能进行常量传播。
通过append,StringBuilder将Child.B字符串与Child.B使用的getstatic读操作相连接,从而触发父亲的负载。
GETSTATIC com/apptest/Child.B : I
原因在于,父亲和孩子的静态成员中包含了方法,所以只能初始化父亲。方法直接指向父亲,如果孩子没有垄断。GETSTATIC仅装载Child,而未进行初始化。
(3)child[]ch=null无用代码,因此无需讨论就被javac删除了。
(4)newChild[]阵列为Child,Java阵列的factor类为Object,Child[]。类本身和Chil.class将进行讨论。
(5)Child.class这个项目比较特殊,Child.class是一个比较特殊的静态常量,并且是只读的,但是Child.class实际上是Child类的一个示例,而且Child=newChild()本身就是一个抽象概念,Child.class只是Child=newChild()类的一个常量,实际使用是Child child = new Child(); 而不是Child.class child = new Child.class[]; 此外,在java class header中,它的classpoint也不是指向Child的,因此和类只是描述关系。