JVM重要知识点
一、类加载机制
我们在使用一个Java类的时候,比如使用类的静态属性或者new一个对象,JVM虚拟机会将我们目标类的.class文件加载到JVM特定的内存区域,然后经过一系列的加载流程,最后我们的程序才能够使用,这整个流程称之为类加载机制,也就是说我们要使用一个类,必须先加载才能使用。当然,一个类的声明周期不单单只是加载,还有许多阶段,例如初始化、卸载等操作。
JVM类加载是通过一系列的类加载器进行实现,类加载器具有加载一个.class的文件能力,不同的类加载器有着不同的效果。
类的生命周期
加载 loading
- 根据要加载的类的全限定名获取到该类的二进制文件,通过磁盘I/O转化为字节流。
- 将字节流信息结构转化为方法区的运行时数据结构。
- 在JVM堆内存中创建一个该类的java.lang.Class对象,作为方法区类元数据的访问入口。
校验 / 验证 verification
校验阶段是类加载连接阶段的第一步,目的是检查class文件是否符合JVM规范要求。其中,校验阶段一共分为4个校验点:文件格式验证、元数据验证、字节码验证、符号引用验证(在解析阶段完成)
准备 preparation
准备阶段会为类中定义的静态变量分配内存并设置初始值,基本类型就赋值默认值,引用类型就是null。
但是,如果是final修饰的静态变量,且是基本类型和String且赋值时字面量形式,在准备阶段会赋值真实值,如果不是字面量形式,那么会在初始化赋值。
解析 resolution
解析阶段是将JVM常量池类信息的符号引用替换为直接引用过程,该阶段会将一些静态方法替换为指向数据所真实存在的内存地址,称之为静态链接 or 静态绑定,动态绑定则是在程序运行时动态完成。
初始化 initialzation
初始化阶段主要是对class中定义的静态块和静态变量赋予真实值。JVM规范中定义了6种情况必须对类进行初始化操作,包括:
a. 使用new关键字实例化对象时,读取 or 设置一个类的静态变量时,调用静态方法时。
b. 使用java.lang.reflect包进行反射时。
c. 发现父类没有初始化时,会初始化父类。
d. 程序启动类main方法所在类,严格触发初始化。
e. JDK7后,使用java.lang.invoke.MethodHandle句柄操作类时。
f. 当接口中存在default修饰符时,且实现类发生了初始化时。
类加载器
类加载过程执行者即被称为类加载器,jdk提供了三层类加载器
引导类加载器 Bootstrap ClassLoader
任何类加载的行为都要经过它,他负责加载java核心类库(JAVA_HOME/jre/lib包下):rt.jar、resources.jar、charsets.jar等,也可以通过JVM指令进行指定加载路径:-Xbootclasspath,我们在Java程序中打印rt.jar包下面的类的加载器是显示null,是因为这个引导类加载器是C++语言构造。
扩展类加载器 Extention ClassLoader
加载JAVA_HOME/jre/lib/ext包下的jar包和.class文件,它继承自URLClassLoader。
应用类加载器 App ClassLoader
用于加载工程classpath下的jar包和.class文件。
除此之外我们也可以自定义加载器
继承java.lang.ClassLoader类,重写findClass()方法,加载自定义路径下的class文件
双亲委派机制
双亲委派机制的简要流程是一个递归的过程,如下:
1.AppClassLoader检查该类是否已经被加载findLoadClass(),如果又返回,则不用加载,直接返回。
2.如果没有被加载过,则判断是否有父加载器,如果有则委托父加载器加载loadClass()。
3.如果父加载器加载不了,则调用当前类加载器进行加载。
简单来说,先找父亲加载,不行再由儿子自己加载
双亲委派机制有什么作用?
1.保证安全,防止JDK核心类库不会被篡改。
2.避免重复加载,保证类的唯一性。
双亲委派机制如何打破?
可以通过覆盖loadClass()进行双亲委派的自定义。但是打破双亲委派是在ExtClassLoader之下的类加载器,JDK核心包仍然是不能打破的,会抛出package java权限错误。
例如Tomcat就是打破双亲委派机制典型的代表:
Tomcat并没有使用默认的类加载机制,而是自定义了三个类加载器
commonClassLoader:Tomcat最基本的类加载器,加载各个webapp应用公用库的加载,可以被tomcat容器本身和各个webapp访问;
catalinaClassLoader:tomcat容器中私有的类加载器,对webapp应用不可见。
sharedClassLoader:各个webapps共享的类加载器,对所有的webapp都可见,但是对tomcat容器和catalina不可见
WebappClassLoader:各个应用的类私有的加载器,各个应用的类相互隔离,每个WebappClassLoader加载自己目录的class文件和jar,并不会委托给父加载器,这样就打破了双亲委派模;比如不同的war包应用引入了不同的spring版本,这样实现就能加载各自的spring版本。
Tomcat 如果使用默认的双亲委派类加载机制行不行?
首先我们思考一下:Tomcat是一个web容器,那么它要解决什么问题?
1)、一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
2)、部署在同一个web容器中相同的类库相同的版本可以共享。否则如果服务器有10个应用程序,那么要有10份相同的类库加载器进虚拟机。
3)、web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
4)、web容器要支持jsp的修改,我们知道,JSP文件最终也是要编译成class文件才能在虚拟机中运行,但是程序运行后修改jsp已经是已经司空见惯的事情,web容器需要支持jsp修改后不用重启。
现在再看看我们的问题:Tomcat 如果使用默认的双亲委派类加载机制行不行?
答案是不行的。为什么呢?
第一个问题:如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加载器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
第二个问题:默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
第三个问题:和第一个问题一样。
第四个问题:怎么实现jsp文件的热加载,jsp文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改好了,就直接卸载这个jsp类加载器,重新创建类加载器,重新加载jsp文件。
文章继续编辑中
参考文章:https://blog.csdn.net/weixin_40181736/article/details/125560640