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

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

《深入了解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);
	}
}


  输出了’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文件后就再无瓜葛。

  编译器会为接口生成()方法,但接口中不能使用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				// 从当前方法返回空

  两者显然是截然不同的。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5677019.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存