《深入了解Java虚拟机》类的加载之被动引用

Posted 九死九歌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《深入了解Java虚拟机》类的加载之被动引用相关的知识,希望对你有一定的参考价值。

  什么是被动引用?这篇文章里面我们提到过: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);
	


/*
stdout:
SuperClass.static initializer
Hello world!
*/

  输出了’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;
	


/*
stdout:
*/

  无输出,因而将类对象赋值为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];
	


/*
stdout:
*/

  什么也没有输出,说明没有类被初始化,父类被动使用,子类未使用到。

  不同于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);
	


/*
stdout:
Hello world!
*/

  虽然STR是ConstClass的静态字段,但是这里我们并没有对该类进行初始化。因为经过编译阶段的常量传播优化,已经将STR的值"Hello world!"直接存储在了公用类的常量池中。也就是公用类的class文件中并不存在对ConstClass的符号引用。这两个类在编译为class文件后就再无瓜葛。

  编译器会为接口生成<clinit>()方法,但接口中不能使用static静态代码块。接口与类的区别就在于类初试化时要求父类必须全部初始化,而接口不需要。

  扯远了,我们看看上面代码的字节码文件:

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				// 从当前方法返回空

  两者显然是截然不同的。

以上是关于《深入了解Java虚拟机》类的加载之被动引用的主要内容,如果未能解决你的问题,请参考以下文章

深入了解java虚拟机---类加载机制主动引用和被动引用

深入理解Java虚拟机——类加载的时机

深入理解Java虚拟机——类加载的时机

深入理解JVM-类加载初始化阶段-类的主动与被动引用

jvm学习002 虚拟机类加载过程以及主动引用和被动引用

深入理解Java虚拟机类的初始化过程