本篇是对《深入理解Java虚拟机----JVM高级特性与最佳实践》周志明作的第二版对应内容的一个读书笔记。
JVM虚拟机类的加载机制是说:
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。类加载说的就是下图中的:加载+验证+准备+解析+初始化,这5个阶段。
简而言之一句话:Class文件怎么变成内存中可用的一个类/接口的。
类从被加载到虚拟机内存中,到最后从内存中卸载,一共有7个阶段:
加载阶段,虚拟机需要完成一下3件事:
1.通过一个类的全限定名来获取定义此类的二进制字节流
2.将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
大致会完成4中验证:
1.文件格式验证
2.元数据验证
3.字节码验证
4.符号引用验证
该阶段做的事就是:
1.为类变量(即是static修饰的变量)分配内存,这些static变量使用的内存在方法区中分配。
2.设置类变量初始值。这里设置的初始值一般情况是零值。
比如 public static int value = 55; 这里只是把value设置为0.
特殊情况则是final修饰的情况
比如 public static final int value = 55; 这里就会把value设置为55
基础数据类型对应的零值
数据类型 | 零值 | 数据类型 | 零值 |
int | 0 | boolean | false |
long | 0L | float | 0.0f |
short | (short)0 | double | 0.0d |
char | '\u0000' | reference | null |
byte | (btye)0 |
注意:在Java运行时绑定(一个接口有多个实现,运行时绑定到底用哪个实现)的情况下,解析可能在初始化之后。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
解析动作主要针对:
1.类或接口
2.字段
3.类方法
4.接口方法
5.方法类型
6.方法句柄
7.调用点限定符
初始化阶段主要做的事就是执行类构造器
1.
()方法是什么: 它是编译器自动收集类中的所有类变量(static变量)的赋值动作和静态语句块(static{}块)中的语句合并而成。
编译器收集的顺序是由语句在源文件中出现的顺序所决定的。
强调,它不是构造函数!这里说的是类加载!不是实例化对象!
2.
()方法不需要显示调用,虚拟机会保证在子类的 ()方法执行前,父类的 ()方法已经执行完毕。所以在虚拟机中第一个被执行的 ()方法的肯定是java.lang.Object。 但是执行接口的
()方法时,不需要先执行其父接口的 ()方法。 执行接口的实现类的初始化时,也不会执行该接口的
()方法。 3.
()方法对于类或接口来说不是必须的,如果一个类中没有静态语句块(static{}块),也没有对static变量赋值操作,那么编译器可以不为这个类生成 ()方法
对一个类进行主动引用,会触发初始化。
主动引用有且只有以下5中场景,除此以外都是被动引用,被动引用不会触发初始化方法。
1.遇到new、getstatic、putstatic或者invokestatic这4条字节码指令时,如果没有进行过初始化则需要先对其初始化。
这4个指令常见场景:new实例化对象,读取、设置一个类的静态static字段(被final修饰的静态static字段除外,它在编译器就被放到常量池中了),调用一个静态方法。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果没有进行过初始化则需要先对其初始化。
3.当初始化一个类的时候,其父类还没有初始化,则需要先初始化其父类。但是,当初始化一个接口的时候,并不要求其父接口已经初始化。
4.执行主类(有main方法那个类)。
5.当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先对其初始化。
刚不是说了,在加载阶段要做的事,第一件事就是:通过一个类的全限定名来获取定义此类的二进制字节流。
虚拟机把这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何获取所需要的类。
而这个动作的代码模块就是类加载器。
比较两个类是否相等,只有在这两个类是由同一个类加载器加载出来的前提下,比较才有意义。
两个类即使来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不一同,那这两个类就不相等。
这里的“相等”包括equals()方法、isAssignableFrom()方法、inInstance()方法,也包括使用instanceof关键字做对象所属关系判定。
1.Bootstrap ClassLoader启动类加载器,这个类加载器使用C++语言实现,是虚拟机自身的一部分
2.其他又Java语言实现的类加载器,独立于虚拟机外部,继承自抽象类Java.lang.ClassLoader
1.Bootstrap ClassLoader启动类加载器。
2.Extension ClassLoader扩展类加载器。
3.Application ClassLoader应用程序类加载器,它是ClassLoader中的getSystemClassLoader方法的返回值,也称为系统类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下它就是程序中默认的类加载器。
名字 | 加载哪个目录下的类到虚拟机内存中 | 加载哪个参数指定的路径下的类 | 能否被开发人员直接使用 |
Bootstrap ClassLoader 启动类加载器 |
-Xbootclasspath | 不能 | |
Extension ClassLoader 扩展类加载器 |
java.ext.dirs系统变量指定路径 | 能 | |
Application ClassLoader 应用程序类加载器 |
用户路径ClassPath | 能 |
1.每个类加载器收到了类加载请求,会先检查该类是否已经被加载过了。
2.如果该类没有被加载过,它会把请求委派给父类加载器(调用父类加载器的loadClass()方法)去完成。
如果父类加载器为空,默认使用启动类加载器作为父类加载器。
3.只有父类加载器无法加载这个类(它的搜索范围中没有找到所需的类),子类加载器才会尝试自己加载该类。
Java类随着它的类加载器一起具备了一种带有优先级的层级关系。
例如类java.lang.Object,它存放在rt.jar包中,无论哪一个类加载器要加载这个类,最终都会委派给处于模型最顶端的启动类加载器来加载。
因此保证了Object类在程序的各种类加载器环境中始终都是同一个类。