JVM类加载机制知识点

Java运行时一个类是什么时候被加载的?

一个类在什么时候开始被加载,《Java虚拟机规范》中并没有进行强制约束,交给了虚拟机自己去自由实现,HotSpot虚拟机是按需加载,在需要用到该类的时候加载这个类。

使用 -XX:+TraceClassLoading 开启追踪类加载的细节。

JVM一个类的加载过程?

一个类从加载到jvm内存,到从jvm内存卸载,它的整个生命周期会经历7个阶段:

  1. 加载(Loading)

  2. 验证(Verification)

  3. 准备(Preparation)

  4. 解析(Resolution)

  5. 初始化(Initialization)

  6. 使用(Using)

  7. 卸载(Unloading)

其中验证、准备、解析三个阶段统称为连接(Linking);

加载:classpath、jar包、网络、某个磁盘位置下的类的class二进制字节流读进来,在内存中生成一个代表这个类的java.lang.Class对象放入元空间,此阶段我们程序员可以干预,我们可以自定义类加载器来实现类的加载;

验证:验证Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证虚拟机的安全;

准备:类变量赋默认初始值,int为0,long为0L,boolean为false,引用类型为null;常量赋正式值;

解析:把符号引用翻译为直接引用;

初始化:当我们new一个类的对象,访问一个类的静态属性,修改一个类的静态属性,调用一个类的静态方法,用反射API对一个类进行调用,初始化当前类,其父类也会被初始化… 那么这些都会触发类的初始化;

使用:使用这个类;

卸载

  1. 该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例;
  2. 加载该类的ClassLoader已经被GC;
  3. 该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法;

一个类被初始化的过程?

类的初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码;

进行准备阶段时,变量已经赋过一次系统要求的初始零值,而在初始化阶段,才真正初始化类变量和其他资源;

继承时父子类的初始化顺序是怎样的?

父类–静态变量

父类–静态初始化块

子类–静态变量

子类–静态初始化块

父类–变量

父类–初始化块

父类–构造器

子类–变量

子类–初始化块

子类–构造器

究竟什么是类加载器?

在类“加载”阶段,通过一个类的全限定名来获取描述该类的二进制字节流的这个动作的“代码”被称为“类加载器”(Class Loader),这个动作是可以自定义实现的。

JVM有哪些类加载器?

站在Java虚拟机的角度来看,只存在两种不同的类加载器:

  1. 启动类加载器(Bootstrap ClassLoader),使用C++语言实现,是虚拟机自身的一部分;

  2. 其他所有的类加载器,由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader

站在Java开发者的角度来看,自 JDK 1.2 开始,Java一直保持着三层类加载器架构;

JVM中不同的类加载器加载哪些文件?

  1. 启动类加载器(Bootstrap ClassLoader)
    (根的类加载器)C++语言实现的
    <JAVA_HOME>\jre\lib\rt.jar,resources.jar、charsets.jar
    被-Xbootclasspath参数所指定的路径中存放的类库;
  2. 扩展类加载器(Extension ClassLoader)
    sun.misc.Launcher$ExtClassLoader
    <JAVA_HOME>\jre\lib\ext
    java.ext.dirs 系统变量所指定的路径中所有的类库;
  3. 应用程序类加载器(Application ClassLoader)
    系统的类加载器
    sun.misc.Launcher$AppClassLoader
    加载用户类路径(ClassPath)上所有的类库;

JVM三层类加载器之间的关系是继承吗?

不是。Bootstrap ClassLoader 是由 C++ 实现的,无法继承;Extension ClassLoader 和 Application ClassLoader 都继承自 UrlClassLoader, ClassLoader,他们俩是兄弟关系。

你了解JVM类加载的双亲委派模型吗?

双亲委派模型的工作过程是:

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当上一层类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到这个类)时,下一层类加载器才会尝试自己去加载

JDK为什么要设计双亲委派模型,有什么好处?

  1. 确保安全,避免Java核心类库被修改;
  2. 避免重复加载;
  3. 保证类的唯一性;

如果你写一个 java.lang.String 的类去运行,发现会抛出如下异常

可以打破JVM双亲委派模型吗?如何打破JVM双亲委派模型?

想要打破这种模型,那么就自定义一个类加载器,重写其中的 loadClass 方法,使其不进行双亲委派即可;

如何自定义自己的类加载器?

  1. 继承 ClassLoader
  2. 覆盖 findClass(String name) 方法 或者 loadClass() 方法;

findClass(String name) 方法不会打破双亲委派;

loadClass() 方法 可以打破双亲委派;

ClassLoader中的loadClass()、findClass()、defineClass()区别?

loadClass() 就是主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中;

findClass() 根据名称或位置加载.class字节码;

definclass() 把字节码转化为 java.lang.Class

  1. 当我们想要自定义一个类加载器的时候,并且想破坏双亲委派模型时,我们会重写loadClass() 方法;
  2. 如果我们想定义一个类加载器,但是不想破坏双亲委派模型的时候呢?可以可以重写 findClass 方法,findClass() 方法是 JDK1.2 之后的 ClassLoader 新添加的一个方法,这个方法只抛出了一个异常,没有默认实现;

JDK1.2 之后已不再提倡用户直接覆盖 loadClass() 方法,而是建议把自己的类加载逻辑实现到`findClass() 方法中;

所以,如果你想定义一个自己的类加载器,并且要遵守双亲委派模型,那么可以继承 ClassLoader,并且在 findClass() 中实现你自己的加载逻辑即可;


   转载规则


《JVM类加载机制知识点》 Harbor Zeng 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
JVM内存管理知识点 JVM内存管理知识点
Java代码到底是如何运行起来的? Mall.java -->javac --> Mall.class --> java Mall (jvm进程,也就是一个jvm虚拟机) Mall.java -->javac–>Mall.class -->Mall.jar --> java -jar Mall.jar Mall.java --> javac --
2021-11-13
下一篇 
Synchronized关键字分析 Synchronized关键字分析
是关键字 是同步锁 不能被继承,子类中必须显式地加上 synchronized 关键字 修饰 锁类型 称为 作用范围 作用对象 代码块 对象锁 同步语句块 大括号{} 括起来的代码 调用这个代码块的对象 方法 对象锁 同步方法 整个方法 调用这个方法的对象 静态方法 全局锁 同步静态方法 整个静态方法 这个类的所有对象 类 全局锁 同步语句块 大括号{} 括起来的代码
2021-11-08
  目录