【JVM重要知识点(一)】

一、类加载机制

我们在使用一个Java类的时候,比如使用类的静态属性或者new一个对象,JVM虚拟机会将我们目标类的.class文件加载到JVM特定的内存区域,然后经过一系列的加载流程,最后我们的程序才能够使用,这整个流程称之为类加载机制,也就是说我们要使用一个类,必须先加载才能使用。当然,一个类的声明周期不单单只是加载,还有许多阶段,例如初始化、卸载等操作。
JVM类加载是通过一系列的类加载器进行实现,类加载器具有加载一个.class的文件能力,不同的类加载器有着不同的效果。
如图所示,我们自己写的类在使用时,在JVM中经历了这几个重要阶段

类的生命周期

在这里插入图片描述

加载 loading

  1. 根据要加载的类的全限定名获取到该类的二进制文件,通过磁盘I/O转化为字节流。
  2. 将字节流信息结构转化为方法区的运行时数据结构。
  3. 在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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值