类加载的生命周期
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段,其中验证、准备、解析3个部分称为连接
加载、验证、准备、解析、初始化、卸载执行顺序
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,而解析不确定,有时在初始化之后执行
初始化5种情况
1)遇到new、getstatic、putstatic或invokestatic这4个指令时,如果类没有进行初始化,则需要先触发其初始化
2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化
3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化(接口是在使用父接口时才会初始化)
4)当虚拟机启动时,用户需要制定一个要执行的主类(包括main()方法的那个类),虚拟机会先初始化这个主类
5)当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic、REF_putstatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化
加载
获取类的二进制字节流,将这个字节所代表的静态存储结构转化为方法区的运行时数据结构,并且在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口
验证
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全(非必要,可以使用-Xverify:none关闭大部分类验证措施)
文件格式验证(是否有魔数、主次版本、常量是否支持等等)、元数据验证(字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言过饭,如是否有父类、是否继承不被允许的类等等)、字节码验证(通过数据流和控制流分析,确认程序语义是合法的、符合逻辑的)、符号引用验证(对类自身以外的信息(常量池中的各种符号引用)进行匹配性校检)
准备
为类变量(不包括实例变量)分配内存并设置变量初始值阶段( 一般情况下:如public static int value=123; 初始值为0,初始化阶段,putstatic指令被编译后,赋值为123;
特殊:public static final int value=123; value初始值为123)
解析
将常量池内的符号引用替换为直接引用的过程
初始化
开始执行类中定义的JAVA程序代码(字节码)
类加载器的定义:
类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块成为“类加载器”
双亲委派模型
如图展示的类加载器之间的层次关系,称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动器加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的斧子关系一般不会以继承的关系来实现,而是都使用组合关系来复用父加载器的代码。不是强制性的约束模型,而是Java设计者推荐的一种类加载实现方式。
双亲委派模型工作过程是:如果一个类加载器收到了类加载类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个请求(它的搜索范围中没有找到所需额类)时,子加载器才会尝试自己去加载。
双亲委派模型对于Java程序的稳定运作很重要,但它的实现确非常简单,实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()的方法中:先检查是否已经被加载过,若没有加载则调用父加载器的
loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
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;
}
}
OSGI规则
1)将以java.*开头的类委派给父类加载器加载
2)否则,将委派给列表名单内的类委派给父类加载器加载
3)否则,将Import列表中的类委派给Emport这个类的Bundle的类加载器加载
4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载
5)否则,查找类是否在再记得Fragmengt Bundle中,如果在,则委派给Fragmengt Bundle的类加载器中加载
6)否则,查找Dynamic Import列表的Bundle,委派给对应的类加载器加载
7)否则,类查找失败
参考:深入理解Java虚拟机
图片摘自:百度图片