什么是被动引用?这篇文章里面我们提到过:JVM学习笔记内存与垃圾回收篇
我们来看几个例子。
例一package com.spd.jvm; class SuperClass { static { System.out.println("SuperClass.static initializer"); } public static String str = "Hello world!"; } class SubClass extends SuperClass { static { System.out.println("SubClass.static initializer"); } } public class NotInitialization { public static void main(String[] args) { System.out.println(SubClass.str); } }
输出了’SuperClass.static initializer’,没有输出’SubClass.static initializer’,说明初始化了父类,没有初始化子类。对于静态字段,只有直接定义这个字段的类才会被初始化。因此通过子类调用父类中的静态字段,只会触发父类的初始化。因而父类是主动使用,而子类是被动使用。
例二package com.spd.jvm; class SuperClass { static { System.out.println("SuperClass.static initializer"); } public static String str = "Hello world!"; } class SubClass extends SuperClass { static { System.out.println("SubClass.static initializer"); } } public class NotInitialization { public static void main(String[] args) { SuperClass sc = null; } }
无输出,因而将类对象赋值为null是不会进行初始化的,属于被动引用,然而调用构造器函数,令sc = new SuperClass();的话,是会进行初始化的,这时就是主动引用了。
例三package com.spd.jvm; class SuperClass { static { System.out.println("SuperClass.static initializer"); } public static String str = "Hello world!"; } class SubClass extends SuperClass { static { System.out.println("SubClass.static initializer"); } } public class NotInitialization { public static void main(String[] args) { SuperClass[] sc = new SuperClass[16]; } }
什么也没有输出,说明没有类被初始化,父类被动使用,子类未使用到。
不同于C++中类数组中的对象调用空构造器来赋值,java中的类数组是先赋值为空,因而不会进行SuperClass的初始化(见例二)。但若随便调用某个数组元素的构造器函数赋值,那就会初始化SuperClass了。
这里虽然没有触发类com.spd.jvm.SuperClass的初始化阶段,但却触发了一个名为’[Lcom.spd.jvm.SuperClass‘的类的初始化。
这个类的类名明显不合法,这是一个由虚拟机自动生成的,直接继承自java.lang.Object的类。创建过程由字节码指令anewarry触发。
这个类代表了一个元素类型为com.spd.jvm.SuperClass的一位数组,数组应有的属性和方法(用户可直接使用的只有length属性和clone()方法)都实现在这个类里。
java数组访问比起C/C++是安全的,因为java的数组包装在了一个用户不可见的类里,数组的访问也被封装了起来,因此可以检验数组越界。而C/C++的数组更底层一些,直接对数组指针进行移动。不进行越界检验,因而有可能会访问到非法内存。
查看NotInitialization类main函数字节码:
bipush 16 // 将单字节整数16压入栈顶 anewarray #2 // #2是类com.spd.jvm.SuperClass在常量池中的引用,声明这样一个数组,并将其引用值压入栈顶。 astore_1 // 将栈顶引用值数值存入变量1 return // 从当前方法返回空例四
package com.spd.jvm; class ConstClass { static { System.out.println("ConstClass.static initializer"); } public static final String STR = "Hello world!"; } public class NotInitialization { public static void main(String[] args) { System.out.println(ConstClass.STR); } }
虽然STR是ConstClass的静态字段,但是这里我们并没有对该类进行初始化。因为经过编译阶段的常量传播优化,已经将STR的值"Hello world!"直接存储在了公用类的常量池中。也就是公用类的class文件中并不存在对ConstClass的符号引用。这两个类在编译为class文件后就再无瓜葛。
编译器会为接口生成
扯远了,我们看看上面代码的字节码文件:
getstatic #2 // 获得指定类的静态域,并将其压入栈顶,此处#2为java.io.PrintStream,即Sytem.out对应的类。 ldc #4 // 将int、float或String型常量值从常量池中推送至栈顶。此处#4即为"Hello world!" invokevirtual #5 // 调用实例方法,此处#5即为PrintStream.println return // 从当前方法返回空
我们再看看例一的字节码文件:
getstatic #2 // 获得PrintStream的静态域并压入栈顶 getstatic #3 // 获得String的静态域并压入栈顶 invokevirtual #4 // 执行方法println return // 从当前方法返回空
两者显然是截然不同的。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)