目录
类从被加载到虚拟机所管辖的内存中,再到卸载出内存,包括:
加载开始于需要加载该类的地方。
而对类的初始化“有且仅有”5中情况:
1.遇到new,getstatic,putstatic和invokestatic这4条指令时,如果类没有初始化时,则需要先触发其初始化。生成这4条指令的最常见Java代码场景是:使用new关键字实例化对象、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外),以及调用一个类的静态方法。
2. 使用java.lang.reflect包的方法对类进行反射调用时候。
3. 当初始化一个类时,发现其父类没有进行初始化,则需先触发其父类的初始化。
4. 当虚拟机启动时,用户需要制定一个要执行的主类(包含main()方法的那个类),当前类需要先初始化
5. 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_putStatic、REF_getStatic、REF_invokeStatic的方法句柄,并且此方法句柄所对应的类没有进行初始化时,需要初始化该类。
至于初始化的行为见后面小节,主要就是执行按java文件的书写顺序合并了的静态字段的赋值操作和代码块操作。
以上5种称为对类的主动引用,除以上5种情况外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的例子网上查找。
加载
3件事情:
验证
文件格式验证
元数据验证
方法的字节码验证
……
符号引用验证
准备
为类变量在方法区中分配内存,并赋初始值。
Public static int value = 123;//准备阶段赋值为0,初始化阶段赋值为123
Public static final int value = 123;//编译期在文件中已经有包含123信息的constantvalue属性,所以在准备阶段直接赋值123,初始化阶段无行为。
解析
将常量池中的符号引用替换为直接引用。
符号引用类型有:CONSTANT_Class_info 、CONSTANT_Fieldref_info、CONSTANT_Method_info、CONSTANT_InterfaceMethodRef_info、CONSTANT_MethodType_info类型、CONSTANT_MethodHandle_info、CONSTANT_InvokeDynamic_info等分别是类或接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符(符号引用)等。
直接引用是:指向目标的指针、相对便宜量、可以间接定位到目标的句柄。
anewarray、 checkcast、 getfield、 getstatic、 instanceof、nvokedynamic、 invokeinterface、 invokespecial、 invokestatic、 invokevirtual、ldc、 ldc_w、 multianewarray、 new、 putfield 和 putstatic上述任何一条字节码指令都需要对它的符号引用的进行解析。
类或接口符号引用的解析 略
字段符号引用的解析 略
类方法符号引用的解析 略
接口方法符号引用的解析 略
其他类型符号引用的解析,例如:CONSTANT_String_info,维护在虚拟机的一张(intern)内部列表,该列表里存放字符串对象的引用,解析就是把CONSTANT_String_info替换为虚拟机中字符串对象的引用。String类的intern方法返回维护列表的引用值。
初始化
此段才真正开始执行字节码……虚拟机生成的<clinit>方法,而非类实例方法<init>,该方法合并了类变量的赋值操作和静态代码块操作,他就是初始化要执行的字节码。虚拟机保证父类的<clinit>方法先执行,因此第一个被执行<clinit>方法的类是java.lang.Object。
如果类中没有类变量赋值操作,也没有静态代码块,则不生成<clinit>方法;
接口中不能有静态代码块,可又有接口(类)变量,因此接口有<clinit>方法,只有当接口中的变量被使用时,接口才被初始化(<clinit>方法被调用执行),实现类若没有使用接口的接口变量,则接口不需用初始化。
<clinit>方法指挥初始化一次,所以会同步加锁。
package org.fenixsoft.clazz;
import java.util.Collections;
/**
* 常量池中CONSTANT_Fieldref_info、CONSTANT_Method_info、CONSTANT_InterfaceMethodRef_info等类型的常量
* 都会有一项Class_index属性,它指向一个CONSTANT_Class_info类型的常量,该常量描述了类全限定名
* 还有CONSTANT_MethodType_info类型、CONSTANT_MethodHandle_info、CONSTANT_InvokeDynamic_info
* 这3种和jdk1.7新增动态语言特性相关
*/
public class TestClass<Tc> {//Class级的属性列表里有签名属性
//准备阶段赋值为0,初始化阶段在<clinit>方法中赋值123
//CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
public static int value = 123;
//编译期赋值123在Class文件中
//无CONSTANT_Fieldref_info类型常量,值已经编译在value1字段表的constant属性里
public final static int value1 = 123;
//CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
private int m;
//在Class文件中,对应的字段表含有签名属性
//CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
private Tc t;
//以下测试字段的解析
//引用类型如果在本类中没有使用(赋值语句等),那么就不会在常量池里编译出
// CONSTANT_Fieldref_info和CONSTANT_Class_info信息
private TestInterfaceRef testInterfaceRef;
//有使用(赋值语句等),在常量池里就会有CONSTANT_Fieldref_info和CONSTANT_Class_info信息
//CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
private TestInterfaceRef testInterfaceRef1 = new TestInterfaceImplRef();
//CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
//"一二三"和"一二三四"字符串对象的引用被维护在虚拟机的一张(intern)内部列表里
private String testStringRef = "一二三";
private String testStringRef2 = "一二三四";
//CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
private String testStringRef1 = new String("abc");
//编译后code里都是去泛型信息的,但会在方法表、相关字段表、
// Class级的属性列表里增加一项Signature属性,
// Signature属性记录有泛型信息
public <Pm> int inc(Pm pt,Tc tc) {
//已经去了泛型信息
this.t = tc;
//在本类中有了使用(赋值语句等),在常量池里就会有CONSTANT_Fieldref_info和CONSTANT_Class_info信息
//CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
//1. new #2 <org/fenixsoft/clazz/TestInterfaceImplRef>
//#2 : CONSTANT_Class_info : Class name : cp_info_#58 <org/fenixsoft/clazz/TestInterfaceImplRef>
//2. invokespecial #3 <org/fenixsoft/clazz/TestInterfaceImplRef.<init>>
//#3 : CONSTANT_MethodRef_info : Class name : cp_info_#2 <org/fenixsoft/clazz/TestInterfaceRef>
// this.testInterfaceRef = new TestInterfaceImplRef();
//引用其他类实例的字段并赋值,
/**
6 new #2 <org/fenixsoft/clazz/TestInterfaceImplRef> #2 : 同上
9 dup
10 invokespecial #3 <org/fenixsoft/clazz/TestInterfaceImplRef.<init>> #3 : 同上
13 getfield #12 <org/fenixsoft/clazz/TestInterfaceImplRef.ref> 生成一个要引用的CONSTANT_FiledRef_info#12
#12 : CONSTANT_FiledRef_info : Class name : cp_info_#58 <org/fenixsoft/clazz/TestInterfaceImplRef>
16 putfield #13 <org/fenixsoft/clazz/TestClass.testInterfaceRef>
#13 : CONSTANT_FiledRef_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
*/
this.testInterfaceRef = new TestInterfaceImplRef().ref;
//在本类中有了使用testInterfaceRef,在常量池里就会有CONSTANT_Fieldref_info和CONSTANT_Class_info信息
//还会出现CONSTANT_InterfaceMethodRef_info(含有ClassIndex)
// invokeinterface #13 <org/fenixsoft/clazz/TestInterfaceRef.test>
//#13 : CONSTANT_InterfaceMethodRef_info : Class name : cp_info_#68 <org/fenixsoft/clazz/TestInterfaceRef>
testInterfaceRef.test();
//此代码会在常量池里出现相应的CONSTANT_Method_info和CONSTANT_Class_info信息
// invokestatic #13 <java/util/Collections.sort>
//#13 : CONSTANT_Method_info : Class name : cp_info_#70 <java/utils/Collections>
Collections.sort(null);
//下面测试Class文件中的异常处理表
int x;
try {
x = 1;
return x;
} catch (Exception e) {
x = 2;
return x;
} finally {
x = 3;
}
}
}
inc方法的字节码有:
0 aload_0
1 aload_2
2 putfield #13 <org/fenixsoft/clazz/TestClass.t>
5 aload_0
6 new #2 <org/fenixsoft/clazz/TestInterfaceImplRef>
9 dup
10 invokespecial #3 <org/fenixsoft/clazz/TestInterfaceImplRef.<init>>
13 getfield #14 <org/fenixsoft/clazz/TestInterfaceImplRef.ref>
16 putfield #15 <org/fenixsoft/clazz/TestClass.testInterfaceRef>
19 aload_0
20 getfield #15 <org/fenixsoft/clazz/TestClass.testInterfaceRef>
23 invokeinterface #16 <org/fenixsoft/clazz/TestInterfaceRef.test> count 1
28 aconst_null
29 invokestatic #17 <java/util/Collections.sort>
32 iconst_1
33 istore_3
34 iload_3
35 istore 4
37 iconst_3
38 istore_3
39 iload 4
41 ireturn
42 astore 4
44 iconst_2
45 istore_3
46 iload_3
47 istore 5
49 iconst_3
50 istore_3
51 iload 5
53 ireturn
54 astore 6
56 iconst_3
57 istore_3
58 aload 6
60 athrow
类加载器
判断一个类和另一个类在虚拟机中是否相等,取决于该类是否是同一个加载器加载,是否来自于同一份Class文件。这里的相等包括了Class对象的equals、isAssignableFrom、isInstance方法,也包括instanceof关键字等做对象所属关系判定情况。
双亲委派
双亲委派的创新
线程上下文加载器(SPI类加载模式)
正统的classloder:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
SPI设计规则被提出的需求是:系统已经规范好了各种服务接口的内容,各个第三方的服务提供商根据系统的接口实现自己的服务类,然后我们选择一些服务类向系统注册,并只是使用系统的接口来享受服务。
那么我们享受服务的时候,第三方的服务类一般情况下应该处于classpath下,应该由Appclassloader加载,系统的类由BootStrapLoader加载或者扩展类加载器加载总之是Appclassloader的父加载器,这时候系统的类要使用第三方服务类先要加载第三方的服务类,问题是如何加载?
答案应该是系统的类使用Class.forName(class.getName(), true or false, classLoader);并指定classLoader为Appclassloader,没错,也就是线程上下文类加载器,因为线程上下文加载器在JVM初始化时被设置成了Appclassloader。
启动、扩展、应用、自定义类加载器的关系已经铁打,然而不要忘了,线程上下文类加载器是对他们其中之一的引用,个人感觉这是一个让人揪心的设计。
OSGI
OSGI的类加载器不再是双亲委托机制的树状结构,而是复杂的网状结构。需要深入理解请参考:《深入理解OSGI:Equinox原理、应用与最佳实践》
你的打赏是我奋笔疾书的动力!![](https://i-blog.csdnimg.cn/blog_migrate/4f85cfe3e750342ef39a068e8582aae2.png)
支付宝打赏:
微信打赏: