JVM会在什么情况下加载一个类
一个类从加载到使用,一般会经历如下过程:
加载——>验证——>准备——>解析——>初始化——>使用——>卸载
什么时候会将.class字节码文件加载这个类到JVM内存里来:当代码中使用到这个类的时候就会加载
例如:
public class Dog {public static void main() {}}
有一个Dog类,其中main方法作为程序主入口,那么一旦你的JVM进程启动以后,它一定会先把你的这个类(Dog)加载到内存里,然后从main()方法的入口代码开始执行。
流程如下:
接着上边代码,做如下改动:
public class Dog {public static void main() {Food food = new Food();}}
代码中使用到了Food类,这时JVM通过类加载器,从Food.class字节码文件中加载对应的类到内存里来使用,这样代码才能跑起来。
流程如下:
简单总结:首先Dog代码是包含main()方法的主类,一定会在JVM进程启动之后被加载到内存中,开始执行main()方法中的代码,然后遇到了别的类,如Food,此时就会从对应的.class字节码文件加载对应的类到内存里来。
JVM不同阶段解析
- 验证阶段:根据Java虚拟机规范,来校验加载进来的
.class文件中的内容是否符合指定的规范 - 准备阶段:我们写好的类中一般会定义静态成员变量、静态方法,在验证阶段验证通过以后,就需要给类分配一定的内存空间来进行初始化静态变量
- 解析阶段:实际上就是把符号引用替换为直接引用的过程
以上三个阶段的流程:
- 核心阶段:初始化
类初始化代码举例:
public class Food {public static int fish = Configuration.getInt("fish.num");}
在准备阶段只是给fish变量分配了一个内存空间,然后赋个初始值为0。
在初始化阶段时才会执行Configuration.getInt("fish.num")这段代码,然后赋值给fish。
静态代码块也会在这个阶段执行。
什么时候会初始化一个类?
一般来说在以下一些情况会进行初始化:
new Food()来实例化类的对象,此时就会触发类的加载到初始化的全过程,把这个类准备好,然后再实例化一个对象出来- 包含
main()方法的主类,必须是立马初始化的 此外还有一个非常重要的规则,如果初始化一个类的时候,发现他的父类还没初始化,那么必须先初始化它的父类
类加载器和双亲委派机制
启动类加载器:Bootstrap ClassLoader,主要是负责加载我们在机器上安装的Java目录下的核心类,这个核心目录就是
lib目录,所以一旦jvm启动,首先就会依托启动类,去加载你的Java安装目录下的lib目录中的核心类库- 扩展类加载器:Extension ClassLoader,主要是负责加载Java目录下的
lib\ext目录 - 应用程序类加载器:Application ClassLoader,这个类加载器主要负责加载
ClassPath环境变量所指定的路径中的类 - 自定义类加载器:根据需要加载类
- 双亲委派机制:JVM的类加载器是有亲子层级结构的,就是说启动类加载器是最上层的,扩展类加载器在第二层,第三层是应用程序类加载器,最后一层是自定义类加载器。
基于这个亲子层级结构,就有一个双亲委派的机制。
双亲委派机制简单来说就是:父加载器先进行加载,加载不了再由儿子来加载,以此类推。
每日一问
如何保证.class文件不被人拿到以后反编译获取源代码?
编译时可以用一些工具对字节码加密,或者做混淆处理。然后在类加载的时候,对加密类,考虑采用自定义的类加载器来解密文件即可。
