JVM类加载机制

Java程序运行时,有两个阶段,编译和运行。编译阶段将Java文件编译成class字节码文件,然后在运行阶段将class文件加载到内存,并对class文件解释执行。

Java跨平台原因

Java语言跨平台的主要原因就是JVM以及字节码文件。

C语言是将高级语言直接解释成机器码,通过CPU指令集执行机器码,但是,不同架构的CPU,他的指令集可能是不一样的,这样就造成C语言难以跨平台。可是Java就不一样了,Java在机器和程序之间加了一次抽象的虚拟机。直接将Java程序编译成字节码,由Java虚拟机来解释字节码文件,对于不同的平台,解释器是不同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。

这里写图片描述

编译阶段

将Java文件转换成字节码文件。字节码文件存储了所有信息。

class文件中有一个class常量池,里面存放了字符串。

所以,类似于String s = “a”+”b”;在编译阶段会直接被优化为”ab”。

类加载阶段

类加载就是将class文件加载到内存,并对数据进行校验,转换解析和初始化,最终转化成可被虚拟机使用的Java类型。类加载阶段是在运行期间完成的。

类加载阶段主要包括五个阶段:加载,验证,准备,解析,初始化。。

img

加载

  • 通过一个类的全限定名获取字节码文件。
  • 将字节码文件静态存储结构转化为运行时内存区域。
  • 生成这个类的java.lang.class对象。存在于方法区(元空间)。

验证

确保字节码文件信息是否合法。

准备

为类变量(static 修饰)分配内存并设置初始值的阶段。

注意,这个阶段只是类变量设置初始值,而不包括实例变量。

设置初始值通常情况下指的是数据类型的零值。

1
public static int a = 23;

这里只会将a设置为0,将a设置为23需要等到初始化阶段。

解析

将class常量池内的符号引用替换为直接引用的过程。

初始化

执行clinit()方法的过程,初始化类变量以及执行静态语句块。

clinit是类初始化的方法,init是对象初始化的方法,static静态语句块在clinit阶段执行,所以静态语句块肯定是先于构造函数执行的。

类初始化时机

这几个阶段类必须要初始化而不是类加载

  • 遇到new实例化对象,读取或者设置static字段。
  • 反射调用。
  • 初始化一个类,若父类还没有初始化,需要先初始化父类。
  • 初始化主类(执行main方法的)。

类加载器

对于任何一个类,都需要由加载他的类加载器以及这个类本身来确立他在JVM中的唯一性。比较两个类是否相等,首先应该是建立在同一个类加载器上的。两个类即使来自于同一个class文件,由不同的类加载器加载,这两个加载的类也是不相等的。

双亲委派模型

title

双亲委派机制,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务(搜索范围内没有这个类),子加载器才会尝试自己去加载,这就是双亲委派模式。

避免了类加载的混乱。比如说,在classpath下定义了一个java.lang.Object,要是没有双亲委派模型,那么应用程序类加载器会加载这个类,那么就会与启动类加载器加载的Object类冲突。但是要是有类加载机制,那么将会交给启动类加载器,这样加载的还是java自带的Object类,就不会产生冲突。

破坏双亲委派模型

如何破坏双亲委派模型?

继承ClassLoader,自己重写loadClass方法,然后加入自己逻辑,特定某些class可以按照你的方式处理。。

重写loadClass:

  • findLoadedClass

  • 委托parent加载器加载(这里注意bootstrap加载器的parent为null)

  • 自行加载

打破委派机制要做的就是打乱2和3的顺序,通过类名筛选自己要加载的类,其他的委托给parent加载器

JDBC破坏

因为类加载器受到加载范围的限制,在某些情况下父类加载器无法加载到需要的文件,这时候就需要委托子类加载器去加载class文件。

JDBC的Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,比如MySQL驱动包。DriverManager 类中要加载各个实现了Driver接口的类,然后进行管理,但是DriverManager位于 $JAVA_HOME中jre/lib/rt.jar 包,由BootStrap类加载器加载,而其Driver接口的实现类是位于服务商提供的 Jar 包,根据类加载机制,当被装载的类引用了另外一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。也就是说BootStrap类加载器还要去加载jar包中的Driver接口的实现类。我们知道,BootStrap类加载器默认只负责加载 $JAVA_HOME中jre/lib/rt.jar 里所有的class,所以需要由子类加载器去加载Driver实现,这就破坏了双亲委派模型。
通过线程上下文类加载器,默认是应用程序类加载器,可以通过Thread的方法进行设置。

Tomcat破坏

tomcat破坏双亲委派模型

OSGI

类加载器不再是双亲委派模型的树状结构,而是网状结构。