标签:ali 虚拟机 逻辑 原理 加载失败 tde child 位置 exists
本文将由浅及深,介绍Java类加载的过程和原理,进一步对类加载器的进行源码分析,完成一个自定义的类加载器。
类加载器简言之,就是用于把.class文件中的字节码信息转化为具体的java.lang.Class对象的过程的工具。
具体过程:
JVM会将所有的.class字节码文件中的二进制数据读入内存中,导入运行时数据区的方法区中。Class对象,Class对象封装了类在方法区内的数据结构。Class对象的创建过程描述:
类加载的过程分为三个步骤(五个阶段) :加载 -> 连接(验证、准备、解析)-> 初始化。
加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段可以在初始化阶段之后发生,也称为动态绑定或晚期绑定。
加载:查找并加载类的二进制数据的过程。
.class文件,并获取其二进制字节流。Java堆中生成一个此类的java.lang.Class对象,作为方法区中这些数据的访问入口。连接:包括验证、准备、解析三步。
验证:确保被加载的类的正确性。验证是连接阶段的第一步,用于确保Class字节流中的信息是否符合虚拟机的要求。
Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。准备:为类的静态变量分配内存,并将其初始化为默认值。准备过程通常分配一个结构用来存储类信息,这个结构中包含了类中定义的成员变量,方法和接口信息等。
static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。0、0L、null、false等),而不是被在Java代码中被显式赋值。解析:把类中对常量池内的符号引用转换为直接引用。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符等7类符号引用进行。
初始化:对类静态变量赋予正确的初始值 (注意和连接时的解析过程区分开)。
new关键字);java.lang.reflect包中的方法(如:Class.forName(“xxx”));main方法(如:SpringBoot入口类)。主动引用:在类加载阶段,只执行加载、连接操作,不执行初始化操作。
new关键字);java.lang.reflect包中的方法(如:Class.forName(“xxx”));main方法(如:SpringBoot入口类)。代码示例:
1
|
public class OptimisticReference0 {
|
运行结果:
OptimisticReference0 is referred!
代码示例:
1
|
public class OptimisticReference1 {
|
运行结果:
Parent is referred!
Child is referred!
代码示例:
1
|
public class OptimisticReference2 {
|
运行结果:
Child is referred!
Child
代码示例:
1
|
public class OptimisticReference3 {
|
运行结果:
Child is referred!
代码示例:
1
|
public class OptimisticReference4 {
|
运行结果:
Child is referred!
被动引用: 在类加载阶段,会执行加载、连接和初始化操作。
被动引用的几种形式:
代码示例:
1
|
public class NegativeReference0 {
|
运行结果:
Parent is referred!
Parent
代码示例:
1
|
public class NegativeReference1 {
|
运行结果:
无输出
示例代码:
1
|
public class NegativeReference2 {
|
运行结果:
Child
类加载器:类加载器负责加载程序中的类型(类和接口),并赋予唯一的名字予以标识。
Bootstrap Classloader 是在Java虚拟机启动后初始化的。Bootstrap Classloader 负责加载 ExtClassLoader,并且将 ExtClassLoader的父加载器设置为 Bootstrap ClassloaderBootstrap Classloader 加载完 ExtClassLoader 后,就会加载 AppClassLoader,并且将 AppClassLoader 的父加载器指定为 ExtClassLoader。| Class Loader | 实现方式 | 具体实现类 | 负责加载的目标 |
|---|---|---|---|
| Bootstrap Loader | C++ | 由C++实现 | %JAVA_HOME%/jre/lib/rt.jar以及-Xbootclasspath参数指定的路径以及中的类库 |
| Extension ClassLoader | Java | sun.misc.Launcher$ExtClassLoader | %JAVA_HOME%/jre/lib/ext路径下以及java.ext.dirs系统变量指定的路径中类库 |
| Application ClassLoader | Java | sun.misc.Launcher$AppClassLoader | Classpath以及-classpath、-cp指定目录所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器 |
每个类装载器都有一个自己的命名空间用来保存已装载的类。当一个类装载器装载一个类时,它会通过保存在命名空间里的类全局限定名(Fully Qualified Class Name) 进行搜索来检测这个类是否已经被加载了。
JVM 及 Dalvik 对类唯一的识别是 ClassLoader id + PackageName + ClassName,所以一个运行程序中是有可能存在两个包名和类名完全一致的类的。并且如果这两个类不是由一个 ClassLoader 加载,是无法将一个类的实例强转为另外一个类的,这就是 ClassLoader 隔离性。
为了解决类加载器的隔离问题,JVM引入了双亲委托机制。
核心思想:其一,自底向上检查类是否已加载;其二,自顶向下尝试加载类。
AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。BootStrapClassLoader加载失败(例如在%JAVA_HOME%/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。ClassLoader.class
java.lang.Class对象。
ClassLoader通过loadClass()方法实现了双亲委托机制,用于类的动态加载。
loadClass()本身是一个递归向上调用的过程。
自底向上检查类是否已加载
findLoadedClass()方法从最底端类加载器开始检查类是否已经加载。resolve参数决定是否要执行连接过程,并返回Class对象。parent.loadClass()委托其父类加载器执行相同的检查操作(默认不做连接处理)。parent为空时,由findBootstrapClassOrNull()方法尝试到Bootstrap ClassLoader中检查目标类。自顶向下尝试加载类
Bootstrap ClassLoader开始,通过findClass()方法尝试到对应的类目录下去加载目标类。resolve参数决定是否要执行连接过程,并返回Class对象。ClassNotFoundException。
查找最顶端
Bootstrap类加载器的是否已经加载目标类。同样,findBootstrapClassOrNull()实际调用了底层的native方法findBootstrapClass()。
ClassLoader是java.lang包下的抽象类,也是所有类加载器(除了Bootstrap)的基类,findClass()是ClassLoader对子类提供的加载目标类的抽象方法。注意:
Bootstrap ClassLoader并不属于JVM的层次,它不遵守ClassLoader的加载规则,Bootstrap classLoader并没有子类。
JVM初始化加载;Class.forName()方法动态加载;ClassLoader.loadClass()方法动态加载。.class文件加载到JVM中,对类进行解释的同时执行类中的static静态代码块;JVM中,不会执行static代码块中的内容,只有在newInstance才会去执行。静态变量/静态代码块 -> 普通代码块 -> 构造函数
测试结果表明:JVM在创建对象时,遵守以上对象的初始化顺序。
在源码分析阶段,我们已经解读了如何实现自定义类加载器,现在我们开始怼自己的类加载器。
Step 1:定义待加载的目标类
Parent.java和Children.java。
Parent.java
1
|
package org.ostenant.jdk8.learning.examples.classloader.custom;
|
Children.java
1
|
package org.ostenant.jdk8.learning.examples.classloader.custom;
|
Step 2:实现自定义类加载器
CustomClassLoader
CustomClassLoader.java
1
|
public class CustomClassLoader extends ClassLoader {
|
Step 3:测试类加载器的加载过程
CustomerClassLoaderTester.java
测试程序启动时,逐一拷贝并加载待加载的目标类源文件。
1
|
private static final String CHILDREN_SOURCE_CODE_NAME = SOURCE_CODE_LOCATION + "Children.java";
|
拷贝单一源文件到自定义类加载器的类加载目录。
1
|
protected static File copySourceFile(File f) {
|
对拷贝后的.java源文件执行手动编译,在同级目录下生成.class文件。
1
|
protected static void compileSourceFile(File f) {
|
通过自定义类加载器加载Children的java.lang.Class<?>对象,然后用反射机制创建Children的实例对象。
1
|
|
static代码块,把目标类Children.java和Parent.java拷贝到类加载的目录,然后进行手动编译。Children.java和Parent.java。
测试结果分析:
我们成功创建了
Children对象,并通过反射调用了它的say()方法。
然而查看控制台日志,可以发现类加载使用的仍然是AppClassLoader,CustomClassLoader并没有生效。
类目录下有我们拷贝并编译的
Parent和Chidren文件。
分析原因:
由于项目空间中的
Parent.java和Children.java,在拷贝后并没有移除。导致AppClassLoader优先在其Classpath下面找到并成功加载了目标类。
我们成功通过自定义类加载器加载了目标类。创建了
Children对象,并通过反射调用了它的say()方法。
至此,我们自己的一个简单的类加载器就完成了!
周志明,深入理解Java虚拟机:JVM高级特性与最佳实践,机械工业出版社
欢迎关注技术公众号: 零壹技术栈
本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。
标签:ali 虚拟机 逻辑 原理 加载失败 tde child 位置 exists
原文地址:https://www.cnblogs.com/ostenant/p/9695247.html