导航菜单

你知道 Java 类是如何被加载的吗?

  最近给一个非Java方向的朋友讲了下双亲委派模型我的朋友让我写一篇关于JVM的ClassLoader的文章。我很长时间没有写过与JVM相关的文章。它有点痒,不能被皮炎抑制。

当我向朋友解释时,这就是我所说的:在父代委托模型中,ClassLoader将在加载类时由其父ClassLoader加载。只有当父ClassLoader无法加载时,它才会尝试加载自身。这可以实现某些类的重用,并且还可以隔离某些类,因为由不同的ClassLoader加载的类彼此隔离。

但是,轻率地向其他人解释父母授权模式是不恰当的。如果您不了解JVM的类加载机制,您如何理解“由不同的ClassLoader加载的类彼此隔离”这一短语?因此,为了理解父母授权,最好的方法是了解ClassLoader的加载过程。

2.1:何时加载类

我们需要知道的第一件事是何时加载Java类?

《深入理解Java虚拟机》答案是:

1:遇到新的,getstatic,putstatic和其他说明。

2:当班级在通话中反映出来时。

3:初始化类的子类时。

4:虚拟机启动时首先加载设置的程序主类。

5:使用JDK1.7的动态语言支持时。

实际上,我想说最容易理解的答案是:在运行过程中需要这个类时。

那么让我们从如何加载类开始。

2.2:如何加载类

使用ClassLoader加载类非常简单。您可以直接调用ClassLoder的loadClass()方法。我相信每个人都会,但仍然有栗子:

上面的代码实现了ClassLoader来加载类“com.wangxiandeng.test.Dog”,并不是那么容易。但JDK提供的API只是冰山一角。这似乎是一个非常简单的电话。事实上,它隐藏了很多细节。我是喜欢它的人。最喜欢的事情是解压缩API并找出答案。

2.3:JVM如何加载类?

JVM默认将用户程序的ClassLoader加载到AppClassLoader,但无论ClassLoader如何,其根父类都是java.lang.ClassLoader。在上面的示例中,loadClass()方法最终将被调用到ClassLoader.definClass1()中,这是一个Native方法。

看到Native方法很慌张,不用担心,打开OpenJDK源代码,我会继续关注它!

与definClass1()对应的JNI方法是Java_java_lang_ClassLoader_defineClass1()

Java_java_lang_ClassLoader_defineClass1主要调用JVM_DefineClassWithSource()来加载类。在源代码之后,您将发现调用jvm.cpp中的jvm_define_class_common()方法。

上面的逻辑主要是使用ClassFileStream将要加载的类文件转换为文件流,然后调用SystemDictionary: resolve_from_stream()以在JVM中生成Class的代表:Klass。对于Klass,你可能不熟悉它,但你必须在这里理解它。说白了,它是JVM用来定义JavaClass的数据结构。但是,Klass只是一个基类,JavaClass的真实数据结构在InstanceKlass中定义。

可以看出,InstanceKlass记录了Java类的所有属性,包括注释,方法,字段,内部类,常量池等。此信息最初记录在类文件中,因此InstanceKlass是加载到内存中的JavaClass文件的一种形式。

回到上面的类加载过程,这里调用SystemDictionary: resolve_from_stream()将类文件加载到内存中的Klass。

Resolve_from_stream()是首要任务!主要逻辑包括以下步骤:

1:确定是否允许并行加载类,并根据判断结果进行锁定。

如果允许并行加载,则ClassLoader将不会被锁定,只会锁定SystemDictionary。否则,ObjectLocker用于锁定ClassLoader,确保同一个ClassLoader一次只能加载一个类。 ObjectLocker在其构造函数中获取锁,并在析构函数中释放锁。

允许并行加载的好处是微调锁粒度,以便可以同时加载多个Class文件。

2:解析文件流并生成InstanceKlass。

3:使用SystemDictionary注册生成的Klass。

SystemDictionary用于帮助保存由ClassLoader加载的类信息。确切地说,SystemDictionary不是容器。用于存储类信息的容器是Dictionary。每个ClassLoaderData都包含一个私有字典。 SystemDictionary只是一个包含许多静态方法的工具类。

我们来看看已注册的代码:

如果允许并行加载,则之前不会锁定ClassLoader,因此同时可能会多次加载相同的Class文件。但是,同一个Class在同一个ClassLoader中必须是唯一的,因此首先使用SystemDictionary来查询ClassLoader是否加载了相同的Class。

如果已加载,则将当前线程刚加载的InstanceKlass添加到要回收的列表中,并将InstanceKlass * k重定向到SystemDictionary查询的InstanceKlass。

如果未查询,则刚刚加载的InstanceKlass将在ClassLoader的Dictionary中注册。

尽管并行加载不会锁定ClassLoader,但它会在注册InstanceKlass时锁定SystemDictionary,因此在注册时无需担心InstanceKlass的并发操作。

如果禁用并行加载,则直接使用SystemDictionary将InstanceKlass注册到ClassLoader的Dictionary中。

resolve_from_stream()的主要过程是以上三个步骤。显然,最重要的一步是第二步,从文件流生成InstanceKlass。

InstanceKlass调用由KlassFactory: create_from_stream()方法生成。这个的主要逻辑是以下代码。

最初的ClassFileParser是真正的主角!这是将Class文件升华为InstanceKlass的幕后故事!

2.4:不得不说ClassFileParser

加载Class文件的ClassFileParser入口是create_instance_klass()。顾名思义,用于创建InstanceKlass。

Create_instance_klass()主要做两件事:

(1):为InstanceKlass分配内存

(2):分析类文件并填充InstanceKlass内存区域

Fill_instance_klass(IK,changed_by_loadhook,CHECK_NULL);

让我们先谈谈第一件事,为InstanceKlass分配内存。

内存分配代码如下:

这里我们首先计算内存中InstanceKlass的大小,你知道,这个大小是在编译Class文件后确定的。

然后创建一个新的InstanceKlass对象。这不是堆上内存的简单分配,应该注意Klass重载了new运算符:

Metaspace:在分配InstanceKlass时调用allocate():

因此,InstanceKlass分配在ClassLoader的Metaspace的方法区域中。从JDK8开始,HotSpot没有永久生成,并且在Metaspace中分配了类。 Metaspace与永久世代不同。使用NativeMemory。由于MaxPermSize的限制,当内存不足时,永久内存会溢出。

在分配InstanceKlass内存之后,你必须开始第二件事。解析Class文件并填充InstanceKlass内存区域。

ClassFileParser在构造时将开始解析Class文件,因此只需填写fill_instance_klass()。填充完成后,还会创建java_lang_Class: create_mirror()以在Java层中创建InstanceKlass的Class对象。

顺便说一句,对于那些不熟悉Class文件结构的学生,你可以阅读我两年前写的一篇文章:

《汪先生:Jvm之用java解析class文件》

此时,Class文件已经完成了一个华丽的转向,从冷二进制文件到内存中的活动InstanceKlass。

如果您耐心地阅读了上面的源代码分析,那么您必须先理解“不同ClassLoader加载的类是彼此隔离的”这一短语。

我们得出结论,每个ClassLoader都有一个Dictionary来保存它加载的InstanceKlass信息。并且,每个ClassLoader都传递锁,这保证了对于同一个类,它只会将一个InstanceKlass注册到它自己的Dictionary。

由于上述原因,如果所有ClassLoader都自己加载Class文件,则会为同一个Class文件生成多个InstanceKlass,因此即使它是相同的Class文件,从不同InstanceKlass派生的实例类型也是不同的。的。

对于栗子,我们自定义一个ClassLoader来打破父母授权模型:

尝试再次加载Studen类并实例化它:

运行后,它将抛出一种强烈的异常:

为什么?

因为实例化Student对象的实例化InstanceKlass是由CustomClassLoader生成的,并且我们要强制的类型Student.Class对应的InstanceKlass是由系统的默认ClassLoader生成的,它们本质上是两个不相关的InstanceKlass。当然,你不能强迫它。

一些学生问:为什么“InstanceKlass对应于强制由系统默认ClassLoader生成的Student.Class类型”?

实际上,它非常简单,我们反编译字节码:

您可以看到在使用加载的Class初始化实例后,将调用checkcast进行类型转换。 checkcast之后的操作数#12是常量池中Student类的索引:#12=Class#52 //com/wangxiandeng/Student

下面我们来看看HotSpot中checkcast的实现。

HotSpot目前有三个字节码执行引擎,目前使用模板解释器,所以让我读一下这篇文章:《汪先生:JVM之模板解释器》。

早期的HotSpot使用了字节码解释器。模板解释器用汇编语言编写,字节码解释器使用C ++进行转换。为了看起来舒服,我们不看装配。只需看看字节码解释器。如果你的装配技巧非常好,当然你也可以直接看模板解释器。我之前写的文章《汪先生:JVM之创建对象源码分析》在这里分析模板解释器以实现新指令。

没什么废话,让我们来看看用于checkcast实现的字节码解释器,代码在bytecodeInterpreter.cpp中

通过对上述代码的分析,我相信您已经理解了“强类型Student.Class的InstanceKlass由系统的默认ClassLoader生成”这一短语。

父代委托的优点是确保同一个Class文件只生成一个InstanceKlass,但在某些情况下,我们必须打破父代表,例如当我们想要实现Class隔离时。

//如果常量池尚未解析,首先解析,用直接引用替换常量池中的符号引用,

//这将触发加载Student.Class

如果(方法 - >常数() - >Tag_at(指数).is_unresolved_klass()){

CALL_VM(InterpreterRuntime: quicken_io_cc(THREAD),handle_exception);

}

对不起,为什么Student.Class重新载入这里? jvm是否有自己的类来加载链接,然后系统跟随链接以查明该类是否已加载?那么如何在此查询链接中添加自定义CustomClassloader?

第一种方法:设置启动参数java-Djava.system.class.loader

第二种方法:使用Thread.setContextClassLoder

这有点棘手,请看代码:

应该注意的是,在设置线程的ClassLoader之后,它不会直接调用newClassTest()。test()。为什么?由于直接强引用,当解析Test.Class的常量池时,ClassTest由系统默认的ClassLoader加载,这会触发ClassTest.Class的解析。为了避免这种情况,这里使用CustomClassLoader加载ClassTest.Class,然后使用反射机制调用test()。此时,在解析ClassTest.Class的常量池时,它将使用CustomClassLoader加载Class常量池项。没有异常。

写完这篇文章后,手不痒,非常酷!这篇文章从父母委托加载了Class文件,最后返回给父代委托,貌似有点左右,其实只能理解类加载机制,以便更好地理解类似于父母委托的机制,否则只记得精装理论和一些空洞无法从内到外理解。

作者:中间件兄弟

阅读原文

本文是云栖社区的原创内容,未经许可,不得转载。

96

阿里云云栖社区

96daa9ff5e2d4ab8976904a0eb468046

0.7

2019.07.2410: 39

字号3331

我最近告诉一个非Java的朋友关于父代理模型。我的朋友让我写一篇关于JVM的ClassLoader的文章。我很长时间没有写过与JVM相关的文章。它有点痒,被皮炎抑制。不能活。

当我向朋友解释时,这就是我所说的:在父代委托模型中,ClassLoader将在加载类时由其父ClassLoader加载。只有当父ClassLoader无法加载时,它才会尝试加载自身。这可以实现某些类的重用,并且还可以隔离某些类,因为由不同的ClassLoader加载的类彼此隔离。

但是,轻率地向其他人解释父母授权模式是不恰当的。如果您不了解JVM的类加载机制,您如何理解“由不同的ClassLoader加载的类彼此隔离”这一短语?因此,为了理解父母授权,最好的方法是了解ClassLoader的加载过程。

2.1:何时加载类

我们需要知道的第一件事是何时加载Java类?

《深入理解Java虚拟机》答案是:

1:遇到新的,getstatic,putstatic和其他说明。

2:当班级在通话中反映出来时。

3:初始化类的子类时。

4:虚拟机启动时首先加载设置的程序主类。

5:使用JDK1.7的动态语言支持时。

实际上,我想说最容易理解的答案是:在运行过程中需要这个类时。

那么让我们从如何加载类开始。

2.2:如何加载类

使用ClassLoader加载类非常简单。您可以直接调用ClassLoder的loadClass()方法。我相信每个人都会,但仍然有栗子:

上面的代码实现了ClassLoader来加载类“com.wangxiandeng.test.Dog”,并不是那么容易。但JDK提供的API只是冰山一角。这似乎是一个非常简单的电话。事实上,它隐藏了很多细节。我是喜欢它的人。最喜欢的事情是解压缩API并找出答案。

2.3:JVM如何加载类?

JVM默认将用户程序的ClassLoader加载到AppClassLoader,但无论ClassLoader如何,其根父类都是java.lang.ClassLoader。在上面的示例中,loadClass()方法最终将被调用到ClassLoader.definClass1()中,这是一个Native方法。

看到Native方法很慌张,不用担心,打开OpenJDK源代码,我会继续关注它!

与definClass1()对应的JNI方法是Java_java_lang_ClassLoader_defineClass1()

Java_java_lang_ClassLoader_defineClass1主要调用JVM_DefineClassWithSource()来加载类。在源代码之后,您将发现调用jvm.cpp中的jvm_define_class_common()方法。

上面的逻辑主要是使用ClassFileStream将要加载的类文件转换为文件流,然后调用SystemDictionary: resolve_from_stream()以在JVM中生成Class的代表:Klass。对于Klass,你可能不熟悉它,但你必须在这里理解它。说白了,它是JVM用来定义JavaClass的数据结构。但是,Klass只是一个基类,JavaClass的真实数据结构在InstanceKlass中定义。

可以看出,InstanceKlass记录了Java类的所有属性,包括注释,方法,字段,内部类,常量池等。此信息最初记录在类文件中,因此InstanceKlass是加载到内存中的JavaClass文件的一种形式。

回到上面的类加载过程,这里调用SystemDictionary: resolve_from_stream()将类文件加载到内存中的Klass。

Resolve_from_stream()是首要任务!主要逻辑包括以下步骤:

1:确定是否允许并行加载类,并根据判断结果进行锁定。

如果允许并行加载,则ClassLoader将不会被锁定,只会锁定SystemDictionary。否则,ObjectLocker用于锁定ClassLoader,确保同一个ClassLoader一次只能加载一个类。 ObjectLocker在其构造函数中获取锁,并在析构函数中释放锁。

允许并行加载的好处是微调锁粒度,以便可以同时加载多个Class文件。

2:解析文件流并生成InstanceKlass。

3:使用SystemDictionary注册生成的Klass。

SystemDictionary用于帮助保存由ClassLoader加载的类信息。确切地说,SystemDictionary不是容器。用于存储类信息的容器是Dictionary。每个ClassLoaderData都包含一个私有字典。 SystemDictionary只是一个包含许多静态方法的工具类。

我们来看看已注册的代码:

如果允许并行加载,则之前不会锁定ClassLoader,因此同时可能会多次加载相同的Class文件。但是,同一个Class在同一个ClassLoader中必须是唯一的,因此首先使用SystemDictionary来查询ClassLoader是否加载了相同的Class。

如果已加载,则将当前线程刚加载的InstanceKlass添加到要回收的列表中,并将InstanceKlass * k重定向到SystemDictionary查询的InstanceKlass。

如果未查询,则刚刚加载的InstanceKlass将在ClassLoader的Dictionary中注册。

尽管并行加载不会锁定ClassLoader,但它会在注册InstanceKlass时锁定SystemDictionary,因此在注册时无需担心InstanceKlass的并发操作。

如果禁用并行加载,则直接使用SystemDictionary将InstanceKlass注册到ClassLoader的Dictionary中。

resolve_from_stream()的主要过程是以上三个步骤。显然,最重要的一步是第二步,从文件流生成InstanceKlass。

InstanceKlass调用由KlassFactory: create_from_stream()方法生成。这个的主要逻辑是以下代码。

最初的ClassFileParser是真正的主角!这是将Class文件升华为InstanceKlass的幕后故事!

2.4:不得不说ClassFileParser

加载Class文件的ClassFileParser入口是create_instance_klass()。顾名思义,用于创建InstanceKlass。

Create_instance_klass()主要做两件事:

(1):为InstanceKlass分配内存

(2):分析类文件并填充InstanceKlass内存区域

Fill_instance_klass(IK,changed_by_loadhook,CHECK_NULL);

让我们先谈谈第一件事,为InstanceKlass分配内存。

内存分配代码如下:

这里我们首先计算内存中InstanceKlass的大小,你知道,这个大小是在编译Class文件后确定的。

然后创建一个新的InstanceKlass对象。这不是堆上内存的简单分配,应该注意Klass重载了new运算符:

Metaspace:在分配InstanceKlass时调用allocate():

因此,InstanceKlass分配在ClassLoader的Metaspace的方法区域中。从JDK8开始,HotSpot没有永久生成,并且在Metaspace中分配了类。 Metaspace与永久世代不同。使用NativeMemory。由于MaxPermSize的限制,当内存不足时,永久内存会溢出。

在分配InstanceKlass内存之后,您需要启动第二件事,分析Class文件,并填充InstanceKlass内存区域。

ClassFileParser在构造时将开始解析Class文件,因此只需填写fill_instance_klass()。填充完成后,还会创建java_lang_Class: create_mirror()以在Java层中创建InstanceKlass的Class对象。

顺便说一句,对于那些不熟悉Class文件结构的学生,你可以阅读我两年前写的一篇文章:

《汪先生:Jvm之用java解析class文件》

此时,Class文件已经完成了一个华丽的转向,从冷二进制文件到内存中的活动InstanceKlass。

如果您耐心地阅读了上面的源代码分析,那么您必须先理解“不同ClassLoader加载的类是彼此隔离的”这一短语。

我们得出结论,每个ClassLoader都有一个Dictionary来保存它加载的InstanceKlass信息。并且,每个ClassLoader都传递锁,这保证了对于同一个类,它只会将一个InstanceKlass注册到它自己的Dictionary。

由于上述原因,如果所有ClassLoader都自己加载Class文件,则会为同一个Class文件生成多个InstanceKlass,因此即使它是相同的Class文件,从不同InstanceKlass派生的实例类型也是不同的。的。

对于栗子,我们自定义一个ClassLoader来打破父母授权模型:

尝试再次加载Studen类并实例化它:

运行后,它将抛出一种强烈的异常:

为什么?

因为实例化Student对象的实例化InstanceKlass是由CustomClassLoader生成的,并且我们要强制的类型Student.Class对应的InstanceKlass是由系统的默认ClassLoader生成的,它们本质上是两个不相关的InstanceKlass。当然,你不能强迫它。

一些学生问:为什么“InstanceKlass对应于强制由系统默认ClassLoader生成的Student.Class类型”?

实际上,它非常简单,我们反编译字节码:

您可以看到在使用加载的Class初始化实例后,将调用checkcast进行类型转换。 checkcast之后的操作数#12是常量池中Student类的索引:#12=Class#52 //com/wangxiandeng/Student

下面我们来看看HotSpot中checkcast的实现。

HotSpot目前有三个字节码执行引擎,目前使用模板解释器,所以让我读一下这篇文章:《汪先生:JVM之模板解释器》。

早期的HotSpot使用了字节码解释器。模板解释器用汇编语言编写,字节码解释器使用C ++进行转换。为了看起来舒服,我们不看装配。只需看看字节码解释器。如果你的装配技巧非常好,当然你也可以直接看模板解释器。我之前写的文章《汪先生:JVM之创建对象源码分析》在这里分析模板解释器以实现新指令。

没什么废话,让我们看一下用于checkcast实现的字节码解释器,代码在bytecodeInterpreter.cpp中

通过对上述代码的分析,我相信您已经理解了“强类型Student.Class的InstanceKlass由系统的默认ClassLoader生成”这一短语。

父代委托的优点是确保同一个Class文件只生成一个InstanceKlass,但在某些情况下,我们必须打破父代表,例如当我们想要实现Class隔离时。

//如果常量池尚未解析,首先解析,用直接引用替换常量池中的符号引用,

//这将触发加载Student.Class

如果(方法 - >常数() - >Tag_at(指数).is_unresolved_klass()){

CALL_VM(InterpreterRuntime: quicken_io_cc(THREAD),handle_exception);

}

对不起,为什么Student.Class重新载入这里? jvm是否有自己的类来加载链接,然后系统跟随链接以查明该类是否已加载?那么如何在此查询链接中添加自定义CustomClassloader?

第一种方法:设置启动参数java-Djava.system.class.loader

第二种方法:使用Thread.setContextClassLoder

这有点棘手,请看代码:

应该注意的是,在设置线程的ClassLoader之后,它不会直接调用newClassTest()。test()。为什么?由于直接强引用,当解析Test.Class的常量池时,ClassTest由系统默认的ClassLoader加载,这会触发ClassTest.Class的解析。为了避免这种情况,这里使用CustomClassLoader加载ClassTest.Class,然后使用反射机制调用test()。此时,在解析ClassTest.Class的常量池时,它将使用CustomClassLoader加载Class常量池项。没有异常。

写完这篇文章后,手不痒,非常酷!这篇文章从父母委托加载了Class文件,最后返回给父代委托,貌似有点左右,其实只能理解类加载机制,以便更好地理解类似于父母委托的机制,否则只记得精装理论和一些空洞无法从内到外理解。

作者:中间件兄弟

阅读原文

本文是云栖社区的原创内容,未经许可,不得转载。

我最近告诉一个非Java的朋友关于父代理模型。我的朋友让我写一篇关于JVM的ClassLoader的文章。我很长时间没有写过与JVM相关的文章。它有点痒,被皮炎抑制。不能活。

当我向朋友解释时,这就是我所说的:在父代委托模型中,ClassLoader将在加载类时由其父ClassLoader加载。只有当父ClassLoader无法加载时,它才会尝试加载自身。这可以实现某些类的重用,并且还可以隔离某些类,因为由不同的ClassLoader加载的类彼此隔离。

但是,轻率地向其他人解释父母授权模式是不恰当的。如果您不了解JVM的类加载机制,您如何理解“由不同的ClassLoader加载的类彼此隔离”这一短语?因此,为了理解父母授权,最好的方法是了解ClassLoader的加载过程。

2.1:何时加载类

我们需要知道的第一件事是何时加载Java类?

《深入理解Java虚拟机》答案是:

1:遇到新的,getstatic,putstatic和其他说明。

2:当班级在通话中反映出来时。

3:初始化类的子类时。

4:虚拟机启动时首先加载设置的程序主类。

5:使用JDK1.7的动态语言支持时。

实际上,我想说最容易理解的答案是:在运行过程中需要这个类时。

那么让我们从如何加载类开始。

2.2:如何加载类

使用ClassLoader加载类非常简单。您可以直接调用ClassLoder的loadClass()方法。我相信每个人都会,但仍然有栗子:

上面的代码实现了ClassLoader来加载类“com.wangxiandeng.test.Dog”,并不是那么容易。但JDK提供的API只是冰山一角。这似乎是一个非常简单的电话。事实上,它隐藏了很多细节。我是喜欢它的人。最喜欢的事情是解压缩API并找出答案。

2.3:JVM如何加载类?

JVM默认将用户程序的ClassLoader加载到AppClassLoader,但无论ClassLoader如何,其根父类都是java.lang.ClassLoader。在上面的示例中,loadClass()方法最终将被调用到ClassLoader.definClass1()中,这是一个Native方法。

看到Native方法很慌张,不用担心,打开OpenJDK源代码,我会继续关注它!

与definClass1()对应的JNI方法是Java_java_lang_ClassLoader_defineClass1()

Java_java_lang_ClassLoader_defineClass1主要调用JVM_DefineClassWithSource()来加载类。在源代码之后,您将发现调用jvm.cpp中的jvm_define_class_common()方法。

上面的逻辑主要是使用ClassFileStream将要加载的类文件转换为文件流,然后调用SystemDictionary: resolve_from_stream()以在JVM中生成Class的代表:Klass。对于Klass,你可能不熟悉它,但你必须在这里理解它。说白了,它是JVM用来定义JavaClass的数据结构。但是,Klass只是一个基类,JavaClass的真实数据结构在InstanceKlass中定义。

可以看出,InstanceKlass记录了Java类的所有属性,包括注释,方法,字段,内部类,常量池等。此信息最初记录在类文件中,因此InstanceKlass是加载到内存中的JavaClass文件的一种形式。

回到上面的类加载过程,这里调用SystemDictionary: resolve_from_stream()将类文件加载到内存中的Klass。

Resolve_from_stream()是首要任务!主要逻辑包括以下步骤:

1:确定是否允许并行加载类,并根据判断结果进行锁定。

如果允许并行加载,则ClassLoader将不会被锁定,只会锁定SystemDictionary。否则,ObjectLocker用于锁定ClassLoader,确保同一个ClassLoader一次只能加载一个类。 ObjectLocker在其构造函数中获取锁,并在析构函数中释放锁。

允许并行加载的好处是微调锁粒度,以便可以同时加载多个Class文件。

2:解析文件流并生成InstanceKlass。

3:使用SystemDictionary注册生成的Klass。

SystemDictionary用于帮助保存由ClassLoader加载的类信息。确切地说,SystemDictionary不是容器。用于存储类信息的容器是Dictionary。每个ClassLoaderData都包含一个私有字典。 SystemDictionary只是一个包含许多静态方法的工具类。

我们来看看已注册的代码:

如果允许并行加载,则之前不会锁定ClassLoader,因此同时可能会多次加载相同的Class文件。但是,同一个Class在同一个ClassLoader中必须是唯一的,因此首先使用SystemDictionary来查询ClassLoader是否加载了相同的Class。

如果已加载,则将当前线程刚加载的InstanceKlass添加到要回收的列表中,并将InstanceKlass * k重定向到SystemDictionary查询的InstanceKlass。

如果未查询,则刚刚加载的InstanceKlass将在ClassLoader的Dictionary中注册。

尽管并行加载不会锁定ClassLoader,但它会在注册InstanceKlass时锁定SystemDictionary,因此在注册时无需担心InstanceKlass的并发操作。

如果禁用并行加载,则直接使用SystemDictionary将InstanceKlass注册到ClassLoader的Dictionary中。

resolve_from_stream()的主要过程是以上三个步骤。显然,最重要的一步是第二步,从文件流生成InstanceKlass。

InstanceKlass调用由KlassFactory: create_from_stream()方法生成。这个的主要逻辑是以下代码。

最初的ClassFileParser是真正的主角!这是将Class文件升华为InstanceKlass的幕后故事!

2.4:不得不说ClassFileParser

加载Class文件的ClassFileParser入口是create_instance_klass()。顾名思义,用于创建InstanceKlass。

Create_instance_klass()主要做两件事:

(1):为InstanceKlass分配内存

(2):分析类文件并填充InstanceKlass内存区域

Fill_instance_klass(IK,changed_by_loadhook,CHECK_NULL);

让我们先谈谈第一件事,为InstanceKlass分配内存。

内存分配代码如下:

这里我们首先计算内存中InstanceKlass的大小,你知道,这个大小是在编译Class文件后确定的。

然后创建一个新的InstanceKlass对象。这不是堆上内存的简单分配,应该注意Klass重载了new运算符:

Metaspace:在分配InstanceKlass时调用allocate():

因此,InstanceKlass分配在ClassLoader的Metaspace的方法区域中。从JDK8开始,HotSpot没有永久生成,并且在Metaspace中分配了类。 Metaspace与永久世代不同。使用NativeMemory。由于MaxPermSize的限制,当内存不足时,永久内存会溢出。

在分配InstanceKlass内存之后,您需要启动第二件事,分析Class文件,并填充InstanceKlass内存区域。

ClassFileParser在构造时将开始解析Class文件,因此只需填写fill_instance_klass()。填充完成后,还会创建java_lang_Class: create_mirror()以在Java层中创建InstanceKlass的Class对象。

顺便说一句,对于那些不熟悉Class文件结构的学生,你可以阅读我两年前写的一篇文章:

《汪先生:Jvm之用java解析class文件》

此时,Class文件已经完成了一个华丽的转向,从冷二进制文件到内存中的活动InstanceKlass。

如果您耐心地阅读了上面的源代码分析,那么您必须先理解“不同ClassLoader加载的类是彼此隔离的”这一短语。

我们得出结论,每个ClassLoader都有一个Dictionary来保存它加载的InstanceKlass信息。并且,每个ClassLoader都传递锁,这保证了对于同一个类,它只会将一个InstanceKlass注册到它自己的Dictionary。

由于上述原因,如果所有ClassLoader都自己加载Class文件,则会为同一个Class文件生成多个InstanceKlass,因此即使它是相同的Class文件,从不同InstanceKlass派生的实例类型也是不同的。的。

对于栗子,我们自定义一个ClassLoader来打破父母授权模型:

尝试再次加载Studen类并实例化它:

运行后,它将抛出一种强烈的异常:

为什么?

因为实例化Student对象的实例化InstanceKlass是由CustomClassLoader生成的,并且我们要强制的类型Student.Class对应的InstanceKlass是由系统的默认ClassLoader生成的,它们本质上是两个不相关的InstanceKlass。当然,你不能强迫它。

一些学生问:为什么“InstanceKlass对应于强制由系统默认ClassLoader生成的Student.Class类型”?

实际上,它很简单,我们反编译字节码:

您可以看到在使用加载的Class初始化实例后,将调用checkcast进行类型转换。 checkcast之后的操作数#12是常量池中Student类的索引:#12=Class#52 //com/wangxiandeng/Student

下面我们来看看HotSpot中checkcast的实现。

HotSpot目前有三个字节码执行引擎,目前使用模板解释器,所以让我读一下这篇文章:《汪先生:JVM之模板解释器》。

早期的HotSpot使用了字节码解释器。模板解释器用汇编语言编写,字节码解释器使用C ++进行转换。为了看起来舒服,我们不看装配。只需看看字节码解释器。如果你的装配技巧非常好,当然你也可以直接看模板解释器。我之前写的文章《汪先生:JVM之创建对象源码分析》在这里分析模板解释器以实现新指令。

没什么废话,让我们来看看用于checkcast实现的字节码解释器,代码在bytecodeInterpreter.cpp中

通过对上述代码的分析,我相信您已经理解了“强类型Student.Class的InstanceKlass由系统的默认ClassLoader生成”这一短语。

父代委托的优点是确保同一个Class文件只生成一个InstanceKlass,但在某些情况下,我们必须打破父代表,例如当我们想要实现Class隔离时。

//如果常量池尚未解析,首先解析,用直接引用替换常量池中的符号引用,

//这将触发加载Student.Class

如果(方法 - >常数() - >Tag_at(指数).is_unresolved_klass()){

CALL_VM(InterpreterRuntime: quicken_io_cc(THREAD),handle_exception);

}

对不起,为什么Student.Class重新载入这里? jvm是否有自己的类来加载链接,然后系统跟随链接以查明该类是否已加载?那么如何在此查询链接中添加自定义CustomClassloader?

第一种方法:设置启动参数java-Djava.system.class.loader

第二种方法:使用Thread.setContextClassLoder

这有点棘手,请看代码:

应该注意的是,在设置线程的ClassLoader之后,它不会直接调用newClassTest()。test()。为什么?由于直接强引用,当解析Test.Class的常量池时,ClassTest由系统默认的ClassLoader加载,这会触发ClassTest.Class的解析。为了避免这种情况,这里使用CustomClassLoader加载ClassTest.Class,然后使用反射机制调用test()。此时,在解析ClassTest.Class的常量池时,它将使用CustomClassLoader加载Class常量池项。没有异常。

写完这篇文章后,手不痒,非常酷!这篇文章从父母委托加载了Class文件,最后返回给父代委托,貌似有点左右,其实只能理解类加载机制,以便更好地理解类似于父母委托的机制,否则只记得精装理论和一些空洞无法从内到外理解。

作者:中间件兄弟

阅读原文

本文是云栖社区的原创内容,未经许可,不得转载。