探秘Java9之类加载

简介:

Java9带来了模块化系统,同时类加载机制也进行了调整,Java9中的类加载器,变化仅仅是ExtClassLoader消失了且多了PlatformClassLoader,JVM规范里5.3 Creation and Loading部分详细描述了类加载,这里简单说下,规范里把类加载器分为两类,一类是由虚拟机提供的启动类加载器,另一类是由用户自定义的类加载器,注意数组的创建不是类加载器创建的,而是由虚拟机直接创建的。而加载又分为两种情况:defining loader和initiating loader,defining loader只加载不初始化,initiating loader是加载并初始化。在运行时一个类或接口是否唯一不是取决于其二进制名称,而是二进制名称和defining其的类加载器的组合,这些和之前保持一致的,那具体区别在哪?其中JVM规范5.3.6 Modules and Layers有详细说明,增加了Layer(层)的概念,用Layer表示模块集,其实Layer和类加载器是对应的,将启动类加载器加载的模块归一Layer,用户自定义类加载器加载的模块归到另一Layer,Layer也有委托的概念。本文从JDK9的源码入手一窥Java9中类加载有了哪些变化。先宏观看下Java中的类加载器:


1、关于类加载

Java9之前的类加载已经有很多详细的介绍了,这里主要说明Java9中的类加载机制。

2、类加载核心代码(参见jdk.internal.loader.BuiltinClassLoader#loadClassOrNull(java.lang.String, boolean)):

    protected Class<?> loadClassOrNull(String cn, boolean resolve) {
        synchronized (getClassLoadingLock(cn)) {
            // check if already loaded
            Class<?> c = findLoadedClass(cn);

            if (c == null) {

                // find the candidate module for this class
                LoadedModule loadedModule = findLoadedModule(cn);
                if (loadedModule != null) {

                    // package is in a module
                    BuiltinClassLoader loader = loadedModule.loader();
                    if (loader == this) {
                        if (VM.isModuleSystemInited()) {
                            c = findClassInModuleOrNull(loadedModule, cn);
                        }
                    } else {
                        // delegate to the other loader
                        c = loader.loadClassOrNull(cn);
                    }

                } else {

                    // check parent
                    if (parent != null) {
                        c = parent.loadClassOrNull(cn);
                    }

                    // check class path
                    if (c == null && hasClassPath() && VM.isModuleSystemInited()) {
                        c = findClassOnClassPathOrNull(cn);
                    }
                }

            }

            if (resolve && c != null)
                resolveClass(c);

            return c;
        }
    }

这段代码可以看出,原有的双亲委派机制受到了模块化的影响,首先如果当前类已经加载了则直接返回,如果没加载,则根据名称找到对应的模块有没有加载,如果对应模块没有加载,则委派给父加载器去加载。如果对应模块已经加载了,则委派给对应模块的加载器去加载,这里需要注意下,在模块里即使使用java.lang.Thread#setContextClassLoader方法改变当前上下文的类加载器,或者在模块里直接使用非当前模块的类加载器去加载当前模块里的类,最终使用的还是加载当前模块的类加载器

3、Java9虚拟机初始化系统类分成了3个阶段

Java9之前
Initialize the system class.  Called after thread initialization.
java.lang.System#initializeSystemClass
Java9
阶段1
Initialize the system class.  Called after thread initialization.
java.lang.System#initPhase1
阶段2
Invoked by VM.  Phase 2 module system initialization.Only classes in java.base can be loaded in this phase.
java.lang.System#initPhase2
阶段3
Invoked by VM.  Phase 3 is the final system initialization:
1. set security manager
2. set system class loader
3. set TCCL
java.lang.System#initPhase3

Java9之前的版本中没有模块化时只有一个初始化,Java9中分成了3个阶段,阶段2是模块化的初始化工作,主要是boot layer的加载,bootlayer里包含的是平台系统依赖的一些模块,阶段3是访问控制设置,类加载器的状态变更等,Java9里的获取系统类加载器时是根据不同的initLevel来做安全校验,Level为4是表示系统初始化ok了,应用调用此方法获取AppClassLoader时校验反射安全,而虚拟机在0-2的状态里则不校验,直接返回AppClassLoader。

initLevel从0到1的过程也就是上面说的阶段1,1到2的过程就是阶段2,2到3再到4是在阶段3里做的,3到4的过程如下:

        // initializing the system class loader
        VM.initLevel(3);

        // system class loader initialized
        ClassLoader scl = ClassLoader.initSystemClassLoader();

        // set TCCL
        Thread.currentThread().setContextClassLoader(scl);

        // system is fully initialized
        VM.initLevel(4);

Java7里的java.lang.ClassLoader#getSystemClassLoader的实现是:

    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

Java9里的java.lang.ClassLoader#getSystemClassLoader的实现是:

    public static ClassLoader getSystemClassLoader() {
        switch (VM.initLevel()) {
            case 0:
            case 1:
            case 2:
                // the system class loader is the built-in app class loader during startup
                return getBuiltinAppClassLoader();
            case 3:
                String msg = "getSystemClassLoader should only be called after VM booted";
                throw new InternalError(msg);
            case 4:
                // system fully initialized
                assert VM.isBooted() && scl != null;
                SecurityManager sm = System.getSecurityManager();
                if (sm != null) {
                    checkClassLoaderPermission(scl, Reflection.getCallerClass());
                }
                return scl;
            default:
                throw new InternalError("should not reach here");
        }
    }

3、BootClassLoader

启动类加载器,用于加载启动的基础模块类。运行时内存模型如下截图:

4、PlatformClassLoader

平台类加载器,用于加载一些平台相关的模块,双亲是BootClassLoader。运行时内存模型如下截图:

5、AppClassLoader

应用模块加载器,用于加载应用级别的模块,双亲是PlatformClassLoader。运行时内存模型如下截图:

6、packageToModule

全局的已经加载的boot layer的模块集记录Map,key是包名。

7、nameToModule

每个ClassLoader都有一个nameToModule,是用于记录当前ClassLoader加载的模块,一个模块里的类只会由一个ClassLoader来加载。nameToModule是一个MAP,name是模块名,和packageToModule不同。nameToModule的运行时模型如图:

8、关于废弃的java.lang.Class#newInstance

Java9之前和Java9使用ClassLoader的方式对比:

package test;

import java.lang.reflect.InvocationTargetException;

public class ClassLoaderTest {

    public static void main(String[] args) {


        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        try {
            /** Java9之前的使用方式 */
            Class clazz = loader.loadClass("test.Car");
            Object obj = clazz.newInstance();
            Car car = (Car) obj;
            car.run();

        } catch (ClassNotFoundException e) {
            System.err.println(e);
        } catch (InstantiationException e) {
            System.err.println(e);
        } catch (IllegalAccessException e) {
            System.err.println(e);
        }

        try {
            /** Java9的使用方式 */
            Class clazz = loader.loadClass("test.Car");
            Object obj = clazz.getDeclaredConstructor(String.class).newInstance("Benz");
            Car car = (Car) obj;
            car.run();

        } catch (ClassNotFoundException e) {
            System.err.println(e);
        } catch (NoSuchMethodException e) {
            System.err.println(e);
        } catch (SecurityException e) {
            System.err.println(e);
        } catch (InstantiationException e) {
            System.err.println(e);
        } catch (IllegalAccessException e) {
            System.err.println(e);
        } catch (IllegalArgumentException e) {
            System.err.println(e);
        } catch (InvocationTargetException e) {
            System.err.println(e);
        }

    }
}

Car类:

package test;

public class Car {

    public Car(String brand) {
        this.brand = brand;
    }

    private String brand;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public void run() {
        System.out.println("run....");
    }
}

注意,Car类没有参数为空的构造方式,只有一个带参的构造方法。

运行结果如下:

java.lang.InstantiationException: test.Car
run....

这种按构造方法实例化的方式应该是比较方便的技能了,而且可以看出异常也更细分了。如果Car类没有构造方法,两种方式都可以运行,但明显Java9的这种方式更强大,原先的方式自然会被申明废弃了。

目录
相关文章
|
4月前
|
前端开发 Java API
类加载器“如果我定义了一个类名与Java核心类类名相同,那它还能被加载吗?”
类加载器“如果我定义了一个类名与Java核心类类名相同,那它还能被加载吗?”
|
4月前
|
IDE Java 开发工具
java编译通过,运行却提示找不到或无法加载主类的解决方案
java编译通过,运行却提示找不到或无法加载主类的解决方案
401 0
|
9月前
|
缓存 NoSQL Java
Java项目启动时先加载某些方法可用于redis缓存预热
Java项目启动时先加载某些方法可用于redis缓存预热
88 0
|
1月前
|
Java Linux Maven
java依赖冲突解决问题之容器加载依赖jar包如何解决
java依赖冲突解决问题之容器加载依赖jar包如何解决
|
1月前
|
SQL Java 数据库连接
java连接数据库加载驱动到java项目
该博客文章介绍了如何在Java项目中通过代码加载数据库驱动并连接SQL Server数据库,包括具体的加载驱动和建立数据库连接的步骤,以及如何将驱动包添加到Java项目的构建路径中。
|
1月前
|
Java 测试技术 Spring
Java SpringBoot 加载 yml 配置文件中字典项
Java SpringBoot 加载 yml 配置文件中字典项
28 0
|
3月前
|
Java 编译器
Java健壮性 Java可移植性 JDK, JRE, JVM三者关系 Java的加载与执行原理 javac编译与JAVA_HOME环境变量介绍 Java中的注释与缩进 main方法的args参数
Java健壮性 Java可移植性 JDK, JRE, JVM三者关系 Java的加载与执行原理 javac编译与JAVA_HOME环境变量介绍 Java中的注释与缩进 main方法的args参数
41 1
|
2月前
|
存储 算法 Java
JAVA程序运行问题之Java类加载到JVM中加载类时,实际上加载的是什么如何解决
JAVA程序运行问题之Java类加载到JVM中加载类时,实际上加载的是什么如何解决
|
3月前
|
前端开发 Java
java加载class文件的原理
java加载class文件的原理
|
3月前
|
Java API 数据库
Java一分钟之-JPA的懒加载与即时加载
【6月更文挑战第15天】**JPA中的懒加载与即时加载影响应用性能。懒加载推迟关联对象加载,减少初始数据量,但可能导致N+1查询。即时加载则在主实体加载时加载关联数据,适用于急需的情况,但会增加内存使用。选择合适的加载策略,如通过JOIN FETCH优化查询,是性能调优的关键。代码示例展示了`FetchType.LAZY`与`FetchType.EAGER`的使用。**
50 6