这篇文章我们关注一个问题:Java程序是怎么进入JVM并执行的?经常写Java程序的小伙伴应该都听说过类加载机制,在《深入理解Java虚拟机》里周老师已经讲的很清楚了,这篇随笔把之前的笔记以及一些总结重新梳理一下。前面我们已经知道 .java文件经过编译后变成Class文件,JVM加载的是字节码文件。这其中的细节不知道小伙伴们有么有了解过?
- 通过类的全限定名获取类的二进制字节流
- 将类的静态存储结构转化为方法区的运行时数据结构
- 文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的JVM处理。只有验证通过了,字节流才会进入方法区存储。
- 元数据验证:比如类是否有父类,类是否继承了不能被继承的类等,保证不存在不符合Java语言规范的元数据信息。
- 字节码验证:对类的方法体进行校验分析
private static long a = 1;
- 符号引用: 用一组符号描述所引用的目标,引用的目标不一定已经加载到内存中。
JVM加载字节码文件靠的是类加载器,这个操作是在JVM外部实现的。这样应用程序就可以自己决定如何获取所需的类。如果两个类来自同一个Class文件,但是由不同的类加载器加载,那么者两个类一定是不相等。从JVM角度讲,只有两种加载器。一种是启动类加载器,是虚拟机自身的一部分,由C++语言实现;还有就是其他类加载器,由Java语言实现,全都继承自抽象类java.lang.ClassLoader 独立于虚拟机外部。
从开发角度看,主要分为这三种:
- 启动类加载器(Bootstrap ClassLoader):加载<JAVA_HOME>/jre/lib目录中,或者被 -Xbootclasspath参数所指定的路径中。
- 扩展类加载器(Extension ClassLoader):主要加载<JAVA_HOME>/jre/lib目录中的
- 应用程序类加载器(Application ClassLoader):加载用户类路径(ClassPath)上所指定的类库。如果我们没有自定义过自己的类加载器,那么这就是程序默认的类加载器。
这些类加载器之间的关系为:
在使用类加载器加载类的过程种,最好遵循双亲委派模型。双亲委派的原理是:类加载器收到加载类的请求时,先把这个请求委派给父类加载去完成,每一层次的加载器按这个这个逻辑执行。那么所有的加载请求最终都应该传送到顶层的启动类加载中。父加载器无法加载,子加载器才会自己加载。这样做的好处是可以避免类的重复加载,保证程序运行的稳定性。
我们可以自定义类加载器,总结起来就是:(1)类继承ClassLoader (2) 重写findClass() 方法 (3) 调用defineClass()方法。在loadClass()里如果父类加载失败,调用findClass()方法加载,这样还是符合双亲委派机制的。破坏会双亲委派需要重写loadClass()方法。
参考资料:《深入理解Java虚拟机》第二版 周志明
《深入拆解Java虚拟机》郑雨迪