了解Java类加载机制

一.什么是类加载机制

JVM虚拟机把描述类的数据从Class字节码文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。

二.类加载过程

java类加载、链接和初始化过程都是在程序运行期间完成的
1.类加载生命周期
在这里插入图片描述
如图类加载生命周期分为7个阶段,加载、验证、准备、初始化和卸载这5个阶段必须按照这种顺序按部就班地开始。
1.1加载
在这个阶段,虚拟机会完成三件事
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结果转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang. Class对象,作为方法区这个类的各种数据的访问入口。(注意:这里并不一定非得从一个Class文件获取,这里既可以从ZIP包中读取(比如从Jar包和war包读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换对应的Class类))。
1.2 验证
这一阶段只要是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
验证主要从四个方面验证,文件格式验证、元数据验证、字节码验证、符号引用验证。

1.2 准备
这个阶段是正式为类变量分配内存并设置类变量初始值的阶段,即在方法区中分配这些变量所使用的内存空间。这里所说的初始值概念,比如一个类变量定义为:
public static int v =123;
实际上变量V在准备阶段过后的初始值是0而不是123,将V赋值为123的put static指令是程序被编译后,存放于类构造器方法之中。
但是注意如果声明为
public static final int v =123;
在编译阶段就会为v生成ConstantValue属性,在准备阶段虚拟机会根据ConstantValue属性将v赋值为123。

1.3 解析
这个阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
1.3.1 符号引用:
符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中
1.3.2直接引用:
直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。
1.4 初始化:
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序。
初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证子方法执行之前,父类的方法已经执行完毕,如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。

注意以下几种情况不会执行类初始化:

  1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
  2. 定义对象数组,不会触发该类的初始化。
  3. 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
  4. 通过类名获取 Class 对象,不会触发类的初始化。
  5. 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
  6. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。

三.类加载器

JVM提供了三种类加载器
1.启动类加载器(Bootstrap ClassLoade)
负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath 参数指定路径中的,且被虚拟机认可(按文件名识别,如 rt.jar)的类。
2.扩展类加载器(Extension ClassLoader)
负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库。
3.应用程序类加载器(Application ClassLoader)
负责加载用户路径(classpath)上的类库。JVM 通过双亲委派模型进行类的加载,当然我们也可以通过继承 java.lang.ClassLoader实现自定义的类加载器

双亲委派模型
避免同一个类被多次加载;
每个加载器只能加载自己范围内的类;
在这里插入图片描述
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象。
在这里插入图片描述
在这里插入图片描述
有个面试题:可不可以自己写个String类
答案:不可以,因为 根据类加载的双亲委派机制,会去加载父类,父类发现冲突了String就不再加载了;

四.总结

对Java类加载机制做了一次梳理,整理,想深入了解JAVA这些知识是少不了的,每天总结一篇,争取更大进步。
参考:
[1]:https://www.cnblogs.com/aspirant/p/7200523.html
[2]: 《JAVA核心知识点整理》
[3]: 《深入理解Java虚拟机JVM高级特性与最佳实践》

猜你喜欢

转载自blog.csdn.net/cjy_win/article/details/88716710