2、JVM~类加载
2.1、class文件生命周期
一个class文件从创建到执行,大致会经历:
- 创建:编写HelloWord.java文件;
- 编译:通过javac指令吧HelloWord.java文件编程成HelloWord.class文件;
- 加载:读取硬盘上的HelloWord.class文件,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的ava.lang.Class对象,作为方法区这个类的各种数据的访问入口;
- 验证:包含class文件格式校验、元数据验证、字节码验证、符号引用验证;
- class文件格式校验:
①是否是以0xCAFEBABE开头;
②主次版本号是否在当前jvm虚拟机的接受范围;
③常量池中的常量是否有不被支持的常量类型;
④class文件各部分及文件本身是否有被删除的或附加的其他信息;
⑤······; - 元数据验证:
①这个类是否有父类(除java.lang.Object之外,所有的类都应当有父类);
②这个类的父类是否继承了不允许被继承的类(被final修饰的类);
③如果这个类不是抽象类,是否实现了父类或接口要求实现的所有方法;
④ ······; - 字节码验证:
①保证操作数栈的数据类型与指令代码序列都能配合工作;
②保证任何跳转指令都不会跳转到方法体以外的字节码指令上;
③保证方法体中的类型转换总是有效的;
④ ······; - 符号引用验证:
①符号引用中通过字符串描述的全限定名是否能找到对应的类;
②符号中引用的类、方法、字段的权限(public、protected、private、default)是否能被当前类访问到;
③ ······;
- class文件格式校验:
- 准备:给类的静态变量分配内存,并赋予默认值;
- 解析:将符号引用替换为直接引用,这个阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用。
- 初始化:对类的静态变量初始化为指定的值,执行静态代码块;(类加载过程的最后一步)
- 使用:运行HelloWord.java文件编写的代码;
- 卸载:JVM清除执行HelloWord.java文件是使用到的内存;
2.2、类加载器
Java中类加载器:
引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等;
扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包;
应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载自己写的那些类;
自定义加载器:负责加载用户自定义路径下的类包;
2.3、双亲委派机制
类加载过程中使用到一个双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。
类加载器这么设计的目的:
- 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改。
- 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性。
2.4、怎么打破双亲委派机制
2.4.1、为什么要打破双亲委派
以Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行?我们思考一下:Tomcat是个web容器, 那么它要解决什么问题:
- 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
- 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。
- web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
- web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。
2.4.2、Tomcat 如果使用默认的双亲委派类加载机制行不行?
答案是不行的。为什么呢?
- 如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
- 默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
- 第三个问题和第一个问题一样。
- 第四个问题,我们要怎么实现jsp文件的热加载,jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。
2.4.3、Tomcat 类加载原理
tomcat的几个主要类加载器:
commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见,比如加载war包里相关的类,每个war包应用都有自己的WebappClassLoader,实现相互隔离,比如不同war包应用引入了不同的spring版本,这样实现就能加载各自的spring版本;