探秘Java9之类加载-阿里云开发者社区

开发者社区> 凌峯> 正文

探秘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的这种方式更强大,原先的方式自然会被申明废弃了。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
类加载器原理
1.类的加载过程   JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)链接又分为三个步骤,如下图所示: 1) 装载:查找并加载类的二进制数据; 2)链接: 验证:确保被加载类的正确性; 准备:为类的静态变量分配内存,并将其初始化为默认值; 解析:把类中的符号引用转换为直接引用;
1060 0
Java Class类的使用 和 动态加载类
Class类 1)在面向对象的世界里,万事万物皆对象。 类是对象,类是java.lang.Class类的实例对象。 There is a class named Class 2)Foo的实例对象如何表示 Foo foo1=new Foo();//foo1就表示出来了 ...
622 0
JVM的类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。  类加载的规则: 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非...
690 0
你知道 Java 类是如何被加载的吗?
一:前言 最近给一个非Java方向的朋友讲了下双亲委派模型,朋友让我写篇文章深度研究下JVM的ClassLoader,我确实也好久没写JVM相关的文章了,有点手痒痒,涂了皮炎平也抑制不住。 我在向朋友解释的时候是这么说的:双亲委派模型中,ClassLoader在加载类的时候,会先交由它的父ClassLoader加载,只有当父ClassLoader加载失败的情况下,才会尝试自己去加载。
10175 0
+关注
39
文章
7
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载