Java虚拟机类加载机制总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhufenghao/article/details/71375322

1. 概述

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制,其中包括加载,连接(验证,准备,解析),初始化三大阶段,都是在程序运行期间动态完成的,具有高度的灵活性。例如实现了接口的程序在运行时才指定实现类;利用类加载器可以在程序运行时从网络加载一个二进制流作为程序的一部分。

2. 加载

  • 通过类的全名产生对应类的二进制数据流
  • 将这些二进制数据流转换为方法区运行时数据结构
  • 创建代表此类的java.lang.Class对象作为该类在方法区的入口

注意:Class虽然是对象,但是存放在方法区而不是堆。

3. 连接

3.1. 验证

确保类的二进制表示在结构上是否完全正确

  • 文件格式验证,成功后会存放在方法区
  • 元数据验证,检查是否符合Java语言规范
  • 字节码验证,分析类的方法体(数据类型,类型转化)
  • 符号引用验证,在解析之前要匹配类自身以外信息(全限定名找到类、字段和方法,访问权限)

注意:验证错误会抛出java.lang.VerifyError或其子类异常。

3.2. 准备

在方法区中创建类变量(静态变量),并将之设置为默认值(零值),不会执行显式的代码。但是对于final修饰的类变量会直接设置为给定值并放进常量池。

3.3. 解析

确保被引用的类能被正确的找到

  • 类和接口,使用类加载器
  • 字段,按照继承关系从下而上搜索接口、父接口或父类(同名字段出现在接口和父类中会编译失败)
  • 类方法,按照继承关系从下而上搜索接口、父接口或父类
  • 接口方法,按照继承关系从下而上搜索父接口

4. 初始化

显式执行类构造器(静态构造器)<clinit>()方法。

  • 按顺序执行静态变量赋值和静态语句块中的程序
  • 先执行父类<clinit>()方法才能执行子类的<clinit>()方法
  • 执行类的<clinit>()方法不需要先执行接口的<clinit>()方法,同样,执行接口的<clinit>()方法也不需要先执行父接口的<clinit>()方法,只有当接口或父接口的变量被使用时才会执行初始化
  • 只有一个线程执行类的<clinit>()方法,其他线程被阻塞

注意:

  • 初始化子类实例之前,一定会先初始化其父类
  • 子类引用父类的静态字段,只会导致父类初始化而子类不初始化
  • 创建类的引用数组不会导致类的初始化
  • 引用类中的常量(static final)不会导致类的初始化(已经存放在常量池而与类脱离)

5. 类加载器

通过一个类的全限定名获取二进制字节流,实现于虚拟机外部。两个类相等,必须保证类本身相等且使用同一个类加载器。

5.1. 加载器类型

  • 启动类加载器:Bootstrap ClassLoader,它负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。它是虚拟机自身的一部分,无法被Java程序直接引用的。
  • 扩展类加载器:Extension ClassLoader,它由sun.misc.Launcher$ExtClassLoader实现,负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类)。可以直接使用扩展类加载器。
  • 应用程序类加载器:Application ClassLoader,它由sun.misc.Launcher$AppClassLoader来实现,负责加载用户类路径(ClassPath)所指定的类。可以直接使用该类加载器,如果应用程序中没有自定义类加载器,一般情况下这个就是程序中默认的类加载器
  • 自定义类加载器:可以加载扩展的class文件,例如自动验证数字签名,动态地创建定制化类,从数据库中和网络中获取class文件。

5.2. 双亲委派模型

按照上述顺序,每一层上面的类加载器叫做当前层类加载器的父加载器(并非继承而是组合)。
工作流程:如果一个类加载器收到了类加载的请求,它首先把请求委托给父加载器去完成,依次向上,所有的类加载请求最终都应该被传递到顶层的启动类加载器中。只有当父加载器无法完成加载时,子加载器才会尝试自己去加载该类。
作用:产生优先级的层次关系,保证了无论是哪个类加载器要加载此类,最终都会委派给启动类加载器进行加载,使得类在各种类加载器中都是同一个类。

6. 初始化流程示例

Student s = new Student();程序中,内存发生哪些事情?

  1. 加载Student.class文件进内存
  2. 在栈内存为s开辟空间
  3. 在堆内存为学生对象开辟空间
  4. 对学生对象的字段进行默认初始化
  5. 对学生对象的字段进行显示初始化
  6. 通过构造方法对学生对象的字段赋值
  7. 对象初始化完毕,把引用赋值给s变量

猜你喜欢

转载自blog.csdn.net/zhufenghao/article/details/71375322
今日推荐