深入理解JVM虚拟机笔记三

一、类加载过程:

        1.类加载的时机:

            Java虚拟机规范对类的加载没有做限定,将由虚拟机自行把握时机。但是对类的初始化有且仅有五个时机:

            1)遇到new,getstatic,putstatic或invokestatic这四条字节码指令时。常见的情景:new一个对象、读取或修改一个类的静态字段(被final修饰,在编译期放入常量池的静态字段除外)、调用一个静态方法的时候。

            2)遇到java.lang.reflect包的反射时。

            3)子类初始化时对父类进行初始化。

            4)虚拟机启动,执行主类(main()方法所在类)的初始化。

            5)java.lang.invoke.MethodHandle实例的解析结果是REF_getstatic,REF_putstatic,REF_invokestatic时句柄对应的类初始化。

            注意以上所说的初始化都只有一次,如果已经初始化过则跳过,需要注意以下三种被动引用不会导致类初始化:

            1)只有直接定义静态字段的类在调用该字段时会被初始化!如果子类调用父类静态字段,子类是不会初始化的。

            2)final static修饰的字面常量会在编译期优化放入常量池,因此,调用这种常量值也不会使类初始化。


        

        2.加载过程:(类加载和加载不是一回事儿)

            1)通过类的全限定名获取(可以从本地字节码文件获取、可以从网络获取->applet,可以从数据库获取,可以从代理获取->proxy...)二进制字节码流。

            2)将字节流的静态数据结构转换为方法区的运行时数据。

            3)在内存中生成Class对象作为数据访问的入口。

            注意:对于数组,将由虚拟机直接创建,而对于非数组的其他类,开发人员可以自定义类加载器(ClassLoader),也可以用默认类加载器对类进行加载。            


        3.验证:验证字节码的规范性。(不详述,较复杂)


        4.准备:在方法区开辟内存为类变量赋予初值(零值,编译期放入常量池final static除外)。


        5.初始化:真正意义上的java代码从这里开始:<cliinit>方法的执行,有以下几点:

            1).静态变量必须定义在静态块之前才能访问。静态块在静态变量之前只能对其赋值,其最终值由最后一个赋值决定。

            2).父类的<clinit>在子类<clinit>之前执行。(接口类似,但是接口不能有静态块,对于静态变量的处理相同。)


        6.解析*(通常是将常量池的符号引用替换成直接引用,在Class字节码文件中_info结尾的引用即为符号引用)


二、类加载器:

        1.陷阱:比较两个类是否相同(equals()、isAssaginableFrom()、isInstance()、instanceof关键字),两个类的类加载器必须来源相同,否则必然不相同。

        2.启动类加载器(Bootstrap ClassLoader):将%JAVA_HOME%/bin下的,或者被-Xbootclasspath,且被虚拟机识别的类进行加载。启动类加载器无法直接被java程序引用,但是如果想委派给启动类加载器,返回null即可,如下片段:

        

        3.扩展类加载器,加载%JAVA_HOME%/bin/ext下的类,以及被java.ext.dirs系统变量所指定的路径下的类库。可以被直接使用。

        4.应用程序类加载器,加载classpath上的类库,当不存在自定义类加载器时,默认使用该加载器。

        5.双亲委派模型:

            总是将任务交给双亲,双亲不能完成,则自己完成实现。(不采用继承的机构,采用组合的结构)。保证了java程序的稳定运作。试想java.lang.Object,总是会被交付给启动类加载器完成加载,否则,整个程序到处都是不一样的Object类了。

            

        6.破坏双亲委派模型:OSGi(后文研究)。

        

猜你喜欢

转载自blog.csdn.net/zkANewer/article/details/79799138