前言
近几个月学习了儒猿技术窝的专栏《从 0 开始带你成为JVM实战高手》后,基于课程讲解的知识,做了提炼、归纳、扩展,整理出当前文章。
感谢专栏传授的知识!专栏中从零开始,一步一图的方式,加上大量真实线上案例讲解,让我收获很多。
一、java代码执行过程
.java源代码编译成.class字节码文件
打包成jar或者war包
诸如"jar -jar"之类的命令运行字节码文件,此时启动一个JVM进程
jvm运行.class字节码文件
jvm通过类加载器把编译好的.class字节码文件加载到jvm内存中
JVM基于自己的字节码执行引擎,执行加载到JVM内存里的类
![](https://img.haomeiwen.com/i12794947/41a37fef34653f22.png)
二、JVM类加载机制
可参考:你真的理解JVM类加载的各阶段过程(加载、验证、准备、解析、初始化)吗? https://blog.csdn.net/qq_33589510/article/details/106877661
示例代码:
![](https://img.haomeiwen.com/i12794947/f7156369ee63659c.png)
1.类加载到使用的过程
加载-->验证-->准备-->解析-->初始化-->使用-->卸载
![](https://img.haomeiwen.com/i12794947/883406b52c712583.png)
1.1 类加载
什么情况下会去加载一个类?
代码中用到这个类的时候,会去加载一个类,也就是会从字节码文件中加载这个类到JVM内存中。
示例
![](https://img.haomeiwen.com/i12794947/8e1a9889f1b9f7c0.png)
![](https://img.haomeiwen.com/i12794947/377cf05861384b95.png)
流程:
1.代码中main()方法的主类会在JVM进程启动之后被加载到内存中
2.执行main()方法中代码
3.发现使用了其他类"ReplicaManager",此时会从对应的".class"字节码文件中加载对应的类到内存里
1.2 验证
校验字节码文件是否符合JVM规范,如果不符合规范,无法执行
1.3 准备
功能:
(1)给类分配一定的内存空间
(2)给类的类的静态成员变量(static修饰的变量)分配内存空间,并赋予默认的初始值。
上图示例代码中,会给类变量"flushInterval"分配内存空间,并赋予默认初始值"0"。
1.4 解析
符号引用替换为直接引用。过程比较复杂,实际工作中很少用到,可以不深入研究。
1.5 初始化
核心阶段。
功能:
执行类中静态成员变量的赋值和static{}静态代码块
什么时候初始化一个类?
(1) "new xxx()”实例化类的对象时,会触发类的加载到初始化的全过程
(2) 调用一个类的静态方法
(3) 调用一个类的静态字段(非编译期已知的常量) 不太理解
(4) 使用反射调用其对应方法的时候
(5) 包含"main()”方的主类,会立马初始化
(7) 如果初始化一个类的时候,没有他的父类还没有初始化,那必须先初始化他的父类。
不会引起类初始化的场景-被动使用:
这块比较难理解
参考:JVM Knowledges-类型的初始化阶段 https://blog.csdn.net/time_hunter/article/details/13007715
(1) 调用一个类的static final编译期已知的常量时
![](https://img.haomeiwen.com/i12794947/156c27757c4620a7.png)
执行结果中没有打印 “MyClass init!"
(2) 通过子类调用父类的static常量
![](https://img.haomeiwen.com/i12794947/e95b08ab2a7d2a91.png)
执行结果中没有打印 “SubClass init!"
(3) 使用类型数组定义
![](https://img.haomeiwen.com/i12794947/cdd19ce6dcb5fdb8.png)
三、类加载器
类加载到初始化的过程都需要用到类加载器
类加载器分类
(1) 启动类加载器
Bootstrap ClassLoader。负责加载JDK中”lib"目录中的核心类库
(2) 扩展类加载器
Extension ClassLoader。负责加载”lib\ext”目录中的类
(3) 应用程序类加载器
Application ClassLoader。负责加载”ClassPath”环境变量锁指定路径中的类。
(4) 自定义类加载器
自定义的类加载器,根据自己的需求加载类
双亲委派机制
JVM类加载器有亲自层级结构,如下图
![](https://img.haomeiwen.com/i12794947/86a7509cc8b14107.png)
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的父级类加载器去加载,最终传导到顶层的类加载器去加载,但是如果父类加载器在自己负责加载的范围内,没找到这个类,则会下推权利到自己的子类加载器。
通俗的示例:
假设JVM需要加载”ReplicaManager”类,会有如下加载步骤
(1)应用程序类加载器检查是否加载过,如果加载过,则无需再加载;如果没有加载过,则不先自己加载而是直接委派应用程序类加载器会委派给扩展类加载器加载。
(2)扩展类加载器检查是否加载过,如果加载过,则无需再加载;如果没有加载过,则不先自己加载而是直接委派他的父级类加载器即启动类加载器去加载。
(3)启动类加载器检查是否加载过,如果加载过,则无需再加载;如果没有加载过,启动类加载器尝试加载类,如果无法加载到,则下放权利给子级即扩展类加载器去加载。
(4)扩展类加载器可以加载到,则加载。如果也无法加载到,则继续下放权利给他的子级即应用程序类加载器加载,此时类加载器找到了该类,然后把类加载到内存中
(5)如果都无法加载,则会抛出ClassNotFoundException异常
![](https://img.haomeiwen.com/i12794947/174cad5ac1657670.png)
四、双亲委派机制
作用
1.防止重复加载同一个.class。通过委托去向上问一问,加载了就不用再加载一遍,保证数据安全。
2.保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象,保证了Class执行安全。
五、思考题:如何对.class文件处理保证不被人拿到后反编译获取公司源代码?
编译的时候,采用工具对字节码加密,或者做混淆等处理,在类加载的时候,对加密的类,采用自定义的类加载器进行解密,这样.class即使获取了,也无法执行,即保护了源码。
网友评论