类加载过程图解:

java 动态查看新加载的类_加载

简介:

以上这张图是博主经过阅读周志明的深入理解Java虚拟机一书,并在网上看了若干帖子之后画出来的,我争取用一句话来
描述每个阶段都做了什么,因为每个阶段做的事情实在有一些多,而我们做为开发者也着实想理解的深入一些,但是我尽量用少一些理论文字,多一些图解和案例来说明每个阶段自己的理解。
首先澄清一点。基于博主的理解有限,加上主观判定有可能错误。所以欢迎大家指正。一起学习,我会及时的更新本篇纠正错误。

编译阶段

编译阶段严格意义上来说,并不是类加载阶段中的一环,它是由Java Code转换成class字节码的一部分。关于class文件结构的说明,在书中也有提到,class文件是严格紧凑的,在字节码文件中,从第一位开始,到最后一位结束,按照既有的文件结构依次往下捋顺即可。

编译阶段,值得一提的是静态资源的引用,例如:

class A{
        public static final int a = 1;
}

class B{
        public static final int b = A.a
}

案例我没有测试,意思很明确,如果在类B中引用了类A的静态变量,那么编译完成后。在类B的class字节码常量池中会
有b=1这样的编译案例,也就是说。在编译阶段结束后。静态变量的引用,将会与原来的类彻底无关!

Class文件结构
一张简单的图,来说明java到class文件结构的转换。本篇不做过多说明

java 动态查看新加载的类_字节码_02

加载阶段

通过一个类的全限类名获取定义此类的二进制流,
将这个字节流所代表的静态存储结构,转换成为方法区的运行时数据结构
在堆中生成代表这个类的class对象,开放出去作为数据的访问入口。

java 动态查看新加载的类_初始化_03

验证阶段

此阶段不做过多说明,验证文件合法性逻辑,理解即可。

文件格式验证:class文件结构验证,比如开头的魔数,版本号,常量池等等
元数据验证:继承或实现,是否实现了abstract方法或者是接口中的方法?
字节码验证:泛型,类型转换是否有效等等
符号引用验证:除了自身常量池之外的引用,比如权限类名是否可以被找到

准备阶段

变量内存初始化,并且赋初始化值,final类型为直接赋值,不必在等初始化阶段

java 动态查看新加载的类_加载_04

解析阶段

此阶段还有静态解析,和动态解析一说。当接口类型,或者方法类型不确定时,则为动态解析
动态解析必须要等到运行code时,才能够解析出来

java 动态查看新加载的类_字节码_05

初始化阶段

简而言之,就是执行字节码指令。一般为static变量和代码块或者加载器的声明或逻辑

java 动态查看新加载的类_JVM_06

使用new,getstatic,putstatic或invokestatic字节码指令的时候
使用反射类对象实例的时候
当初始化一个类,发现其父类还没有初始化,则首先初始化其父类
main函数的类,必然首先被初始化
当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getstatic,REF_putstatic,REF_invokestatic方法句柄,并且这个方法句柄所对象的类,没有进行过初始化,则首先触发其初始化.

5个情况对应的字节码指令

anewarray

checkcast

getfield

getstatic

instanceof

invokespecial

invokestatic

invokevirtual

ldc

ldc_w

multianewarray

new

putfield

putstatic

被动引用不会触发立即初始化的2种情况

public class SuperClass {
    static{
        System.out.println("Super class Init");
    }
    public static int value = 3;
}

public class SubClass extends SuperClass{
    static{
        System.out.println("SubClass Init");
    }
}
public class NotInitialialization {


/****
     * 被动引用一:引用父类初始化
     * 通过子类引用父类,只会触发父类的初始化阶段,而不会触发子类的初始化阶段
     * 输出结果为
     * Super class Init
     * 3
      */
    public static void main(String[] args) {
         System.out.println(SubClass.value);
    }


    /****
     * 被动引用二:数组初始化
     * 被动引用案例  数组案例
     * 并不会触发SuperClass的初始化阶段
     * 实际为 new Array,和类本身无关
     * @param args
     */
    public static void main(String[] args) {
       SuperClass[] sca = new SuperClass[10];
   }
}

类加载总结:

类加载阶段开始顺序不变,加载,验证,准备,初始化,卸载。这5个阶段的顺序是依次开始的。
类加载各阶段进行是交叉执行的。
一切以class字节码描述为依据,执行相关流程

加载器的双亲委派模型

java 动态查看新加载的类_java 动态查看新加载的类_07

图中指定了每个加载器负责加载的目录.所谓双亲委派,也就是先让父类先加载,可以理解Bootstrap是Extension的爸爸,Extension对于Application亦然。而我们自己编写的类加载器,这三位加载器的后代了。说白了,加载一个类,任务给到你,你自己不加载,先给爸爸,爸爸给爷爷,爷爷给太爷爷。一个概念。但是不一样的是,这几个加载器每个只加载自己固定的范畴,不属于自己任务的,他们不会加载。如果三个加载器走完,都没有检索到要加载的类,那么就会向下调用到自己这里来,自己才会去尝试加载。这样的加载机制避免了重复加载

破坏双亲委派

类似与JDBC,Java定义标准,由厂商负责实现,如果采用双亲委派,那么BootStrap检查到Driver类的实现类的时候,它无法判定究竟是哪个类,包括mysql,oracle,sql server 每一个都有自己的实现。由此为了保证类加载器能够加载实现类,双亲委派模型被破坏了。
热部署模块,使得模块可以拆卸,热插拔。OSGi被提了出来。

OSGi有待研究!!!!