什么是JVM类加载器?

类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。

一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。

类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。

每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。

实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

类加载的过程

从类的生命周期而言,一个类包括如下阶段:

 

 

加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序进行,而解析阶段则不一定,它在某些情况下可能在初始化阶段后在开始,因为java支持运行时绑定。

加载过程:将.class文件通过IO流的方式加载到内存当中

  • 1.将.class文件字节码内容加载到内存当中
  • 2.先会将静态数据转换成方法区中的运行的数据结构
  • 3.在堆内存当中生成一个代表这个类的Class对象,这个Class类的对象就是作为方法区数据访问的入口 Class.forName(com.wdksoft.User);

链接过程:

  • 1.验证阶段:验证字节码文件的准确性,包含文件格式,元数据,符号引用,字节码等等
  • 2.准备阶段:给类中的静态变量分配内存,并赋予初始值
  • 3.解析阶段:将虚拟机常量池的符号引用替换成字节引用的过程

初始化过程:

  • 初始化过程就会对类中的静态变量初始化为指定的值,执行静态代码块,执行构造器

类加载时机

  加载(loading)阶段,java虚拟机规范中没有进行约束,但初始化阶段,java虚拟机严格规定了有且只有如下5种情况必须立即进行初始化(初始化前,必须经过加载、验证、准备阶段):

  •     (1)使用new实例化对象时,读取和设置类的静态变量、静态非字面值常量(静态字面值常量除外)时,调用静态方法时。
  •     (2)对内进行反射调用时。
  •     (3)当初始化一个类时,如果父类没有进行初始化,需要先初始化父类。
  •     (4)启动程序所使用的main方法所在类
  •     (5)当使用1.7的动态语音支持时。

 如上5种场景又被称为主动引用,除此之外的引用称为被动引用,被动引用有如下3种常见情况

  • 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
  • 定义对象数组和集合,不会触发该类的初始化
  • 类A引用类B的static final常量不会导致类B初始化(注意静态常量必须是字面值常量,否则还是会触发B的初始化)

 注意:被动引用不会导致类初始化,但不代表类不会经历加载、验证、准备阶段。

类加载器的种类

 

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:

 

  • 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader
  • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

 

除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

类加载器树状组织结构示意图

 

 

 

类加载方式

这里的类加载不是指类加载阶段,而是指整个类加载过程,即类加载阶段到初始化完成。

(1)隐式加载

  • 创建类对象
  • 使用类的静态域
  • 创建子类对象
  • 使用子类的静态域
  • 在JVM启动时,BootStrapLoader会加载一些JVM自身运行所需的class
  • 在JVM启动时,ExtClassLoader会加载指定目录下一些特殊的class
  • 在JVM启动时,AppClassLoader会加载classpath路径下的class,以及main函数所在的类的class文件

(2)显式加载

  • ClassLoader.loadClass(className),只加载和连接、不会进行初始化
  • Class.forName(String name, boolean initialize,ClassLoader loader); 使用loader进行加载和连接,根据参数initialize决定是否初始化。

 

类加载机制

 

1.全盘负责委托机制
当进行类加载的时候,如果手动指定了ClassLoader,那么该类所依赖和引用的类也由这个类加载器进行加载
User->UserParent
指定User使用特定的类加载器,那么跟User类有依赖和引用关系的类也用这个类加载器进行加载
2.双亲委派机制
指先委托父类加载器寻找目标类,如果父类加载器无法进行类的加载则子类加载器自身处理

  • 1.沙箱安全机制:自定义的String.class不会被加载,这样可以防止核心API库被随意篡改
  • 2.避免类重复加载:当附加在其加载了该类是,就没有必要子类加载器也进行加载

3.如何破坏双亲委派机制
为什么要破坏双亲委派机制:父加载器需要委托子加载器在其进行加载
如何破坏:

  • 1.重写ClassLoad类中的loadClass方法,指定加载哪一个类
  • 2.手动调用系统类加载器Thread.currentThread().getContextClassLoader();
  • 3.重写findClass

监控类加载过程

 在当前启动类当中加入-verbose:class参数,启动则可以看到整个类加载的过程

代码

package com.wish;

public class ClassLoaderTest {
    private static Integer i=50;

    static {
        System.out.println("静态代码块");
        i=70;
    }

    public ClassLoaderTest(){
        System.out.println(i);
        System.out.println("类中构造函数");
    }

    public static void main(String[] args) {
        ClassLoaderTest test = new ClassLoaderTest();
    }
}

  

参数

 

 结果

 

 

博客引用:https://blog.csdn.net/zhaocuit/article/details/93038538

     https://www.ibm.com/developerworks/cn/java/j-lo-classloader/

 

posted on 2020-03-12 19:42  wishsaber  阅读(164)  评论(0编辑  收藏  举报