1. 类加载过程图示

 

从上图的“类加载” 过程,去看看 JVM的类加载机制是什么原理?

2. JVM 在什么情况下会加载一个类

一个类从加载到使用,会经历如下的过程:

加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载

JVM在执行写好的代码时,在代码中用到这个类的时候,就会从 “.class” 字节码文件中加载这个类到 JVM 内存里来。

示例:在一个类(Kafka.class)里有一个“main()” 方法作为主入口。一旦JVM进程启动,就会先把 Kafka.class 类加载到内存里,然后从 “main()” 方法的入口代码开始执行。

如果在main() 方法中使用了 “ReplicaManager” 这个类去实例化对象,那么也需要将 “ReplicaManager.class” 字节码文件中的这个类加载到内存里去。

 

其加载过程如下图所示:

 

3. 验证、准备和初始化

1、 验证阶段

验证,就是根据 Java 虚拟机规范,来校验你加载进来的 “.class” 文件中的内容,是否符合指定的规范。只有完全符合 JVM 规范,后续才能交给 JVM 来运行

 

2、 准备阶段

一个类中,有一些类变量;比如有 “ReplicaManager” 类,它的 “ReplicaManager.class” 文件内容刚刚被加载到内存之后,会进行验证,确认这个字节码文件的内容是规范的。

接着就会进行准备工作。也就是给这个 “ReplicaManager” 类分配一定的内存空间。然后给它里面的类变量(也就是 static 修饰的变量)分配内存空间,来一个默认的初始值。

 

比如上面的示例里,就会给 “flushInterval” 这个类变量分配内容空间,给一个 “0” 这个初始值。

整个过程,如下图所示:

 

3、 解析阶段

这个阶段,是把 符号引用替换为直接引用 的过程。

 

4、 三个阶段小结

这里最核心的是,“准备阶段” 。准备阶段 是给加载进来的类分配好内存空间类变量也分配好内存空间,并且给了默认的初始值

4. 核心阶段: 初始化

1、 类的初始化是什么

在准备阶段之后,就是进行初始化的阶段了。这一阶段会正式执行我们的类初始化的代码

 

在上述代码中,对于 “flushInterval” 类变量,这里是通过 Configuration.getInt("replica.flush.interval")这段代码来获取一个值,并且赋值给它。

而在准备阶段是不会执行这个赋值逻辑的,它仅仅是对类变量分配内存,并给初始值 “0” 而已。

该赋值逻辑的代码,是在 “初始化” 阶段执行的。这一阶段,会执行类的初始化代码,而这时上述的赋值逻辑代码就会执行,完成一个配置项的读取,然后赋值给该类变量 “flushInterval” 。

另外,static 静态代码块,也会在这个阶段来执行。

 

此外,static 静态方法可以给 static 静态变量赋值,也就是说,类初始化的时候,调用static静态方法从磁盘中加载数据副本,并放在静态变量中,如下示例:

 

2、 什么时候会初始化一个类?(类的初始化的规则)

一般来说,比如使用 "new 类名()" 来实例化类的对象时,就会触发类的加载到初始化的全过程,把这个类准备好,然后再实例化一个对象处来;

或者是包含 “main() ” 方法的主类,必须是立马初始化的。

此外,如果初始化一个类的时候,发现他的父类未初始化,就必须先初始化它的父类。

示例代码如下:

 

如果要“new ReplicaManager()” 初始化这个类的实例,就会加载这个类,然后初始化这个类;

而在初始化这个类之前,发现 AbstractDataManager作为父类还没加载和初始化,就必须先加载这个父类,并且初始化该父类。

 

5. 类加载器和双亲委派机制

类加载从触发时机到初始化的过程依靠类加载器来实现

Java里有以下几种类加载器:

1)启动类加载器

Bootstrap ClassLoader,在JVM启动时,首先就会依托启动类加载器,负责加载机器上的 Java安装目录下的 “lib” 目录中的核心类库。

2)扩展类加载器

Extension ClassLoader,主要负责从Java安装目录下,加载 “lib\ext” 目录中的类。

3)应用类加载器

Application ClassLoader,负责去加载 “ClassPath” 环境变量所指定的路径中的类。其实就是加载写好的Java代码的类到内存里。

4)自定义类加载器

自定义的类加载器,可以根据自己的需求加载你的类。

5)双亲委派机制

JVM的类加载器有层级结构,启动类加载器在最上层,扩展类加载器在第二层,第三层是应用类加载器,最后一层是自定义类加载器。

 

基于上面的层级结构,就有一个双亲委派的机制

**所谓双亲委派机制:**就是在应用程序类加载器需要加载一个类,它首先会委派给父类加载器去加载,最终传导到顶层的类加载器去加载;如果父类加载器在自己负责加载的范围内,没找到这个类,就会下推加载权利给自己的子类加载器。

简而言之,就是先找父类加载器加载,不行的话逐层往下由子类加载器。从而避免多层级的加载器结构重复加载某些类。  

6. 思考题

如何对“.class” 文件处理保证不被人拿到以及反编译获取公司源代码?

**答:**首先在编译时,可以采用一些小工具对字节码加密,或者做混淆等处理;也可以购买第三方公司的商业级的字节码文件加密产品;然后在类加载的时候,对加密的类,考虑采用自定义的类加载器来解密文件即可,从而保证你的源代码不被人窃取。