你不能不知道的JVM系列之类加载器

你不能不知道的JVM系列之类加载器

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

类加载器

作用

  • 在加载阶段(Load),读取字节码,将字节码加载进JVM,转换成java.lang.Class对象
  • 确定类在JVM中的唯一性。(子父级的关系限制了类的唯一性,全限定名一致的类,如果类加载器不同,也并不是同一个类)

分类/分层

java1.2版本中,只有Bootstrap的类加载器,这种情况下,很容易造成java自身的一些核心类由于同全限定名而被覆盖,不安全,因此对于类的安全性,信任考虑,进行了分层,java核心类>安装扩展类>自定义类

加载器分类层级(数字越小,层级越高)加载内容备注
Bootstrap ClassLoader1负责加载$JAVA_HOME中 jre/lib/rt.jar 里所有的class或Xbootclassoath选项指定的jar包。C++实现,不可访问
Extension ClassLoader2负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。
App ClassLoader3负责加载classpath中指定的jar包及 Djava.class.path 所指定目录下的类和jar包。
Custom ClassLoader4通过java.lang.ClassLoader的子类自定义加载classTomcat使用的这种方式

主要特性

全盘负责
  • 当一个类加载器负责加载当前类的时候,当前类所依赖引用的其他类,也将有该类加载器负责加载引入,除非显式的指定类加载器
  • 类加载器加载引入的含义是指:指定类的加载入口,实际上究竟由谁加载,则由父类委托机制完成
父类委托(双亲委派)
  • 子类如果没有加载过类,则交由父类加载
  • 父类无法加载,则交由子类查找加载

父类委托.png

缓存机制
  • 缓存机制保证加载过的Class在内存中缓存
  • 优先从缓存中读取,找不到再Load
  • 缓存很难被卸载,因此需要很多时候更改了字节码文件,需要重启JVM
  • 相同的Class的loadClass();只会调用一次,类变量因此只初始化一次
loadClass源码
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
复制代码

双亲委派问题

  • 三方服务的问题,例如数据库驱动包java.sql.Driver

打破双亲委派

SPI(service provider interface)

Java核心类中定义了很多接口和调用逻辑,但是没有具体的实现。SPI的方式,是自定义实现该接口的实现类,并且在META-INF/services下注册实现类,供核心类库使用,例如JDBC的DriverManager。

OSGI

通过卸载更新类加载器,同事卸载和替换类,实现代码的热部署。

自定义类加载器

自定义字节码的查找流程,重写loadClass

分类:
后端
标签: