类重复引用_JVM类加载机制

JVM类加载机制

1、整体流程:

041b165ac7b22b1ba9b4d9e04b77139a.png

2、加载扩展类加载器以及应用类加载器

JVM自动调用getLauncher()方法获取launcher实例,而Launcher实例是在引导类加载器加载Launcher类的“初始化”阶段进行实例化的(launcher属性被static修饰)。

7405bb10ea8260b1724cfdca34c4c4f3.png

下面看一下Launcher类的构造方法,可以看到Launcher类是如何加载扩展类加载器以及应用类加载器的:

首先创建扩展类加载器,扩展类加载器是Launcher类的静态内部类,调用扩展类加载器的getExtClassLoader()方法,这里使用单例模式中的双重检查创建ExtClassLoader的实例。扩展类加载器的父加载器是引导类加载器,但是由于引导类加载器是C++实现,所以扩展类加载器的parent属性(parent属性位于顶级父类ClassLoader类中)为null。

d1841b0a8c32625bcf3ac70c742f1c9e.png

d421ab8ac4410df543f26cc4f8039cdb.png

然后加载应用类加载器,应用类加载器也是Launcher类的静态内部类,调用应用类加载器的getAppClassLoader()方法,并以扩展类加载器的实例作为入参,最终会将其赋值给AppClassLoader的parent属性(parent属性位于顶级父类ClassLoader类中),该属性会在JVM的双亲委派机制实现中使用。

8e05ddc29cdeb4a8d6ceed13dd32a2a3.png

a825b614a4f55100b9f9f9f808f398b3.png

加载完成后,会将应用类加载器实例赋值给Launcher类的loader属性,所以当调用Launcher类的getClassLoader()方法时,将会返回应用类加载器实例。

46785e134b5bad1f4b2039346b136512.png

3、类加载过程

JVM会调用类加载器的loadClass()方法进行类加载,类加载的过程如下:

961c0dfac25326fdb9b984b6b707dda3.png

  • 加载:将字节码文件加载到内存中,将其存放到运行时数据区的方法区内,并在堆区中创建一个该类的Class对象,Class对象中封装了方法区内的数据结构,并提供了访问方法区数据结构的接口。字节码可以存在于本地磁盘、网络上、zip或jar等归档包、数据库等地方。

  • 验证:校验字节码文件的格式,确保加载的类的正确性。

  • 准备:给类的静态变量分配内存,并初始化默认值,比如int类型就初始化为0,boolean类型就初始化为false等,而非初始化为代码中显式赋予的值;

  • 解析:将类中的符号引用转换为直接引用,主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄,可以理解为在内存中的地址。

  • 初始化:将类的静态变量初始化为代码中显式赋予的值,并执行类中的静态代码块

类被加载到方法区中后主要包含 运行时常量池、类型信息、字段信息、方法信息、类加载器的 引用、对应class实例的引用等信息。可以使用javap -v Test.class命令,查看这些内容的定义:

e7c59b70828f2ff9577717a04362e469.png

使用javap -v Test.class命令查看Test字节码文件反编译详情

类的加载时机:jar包、war包或者其他相关类并不是在应用启动时全部加载的,只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

  • 通过new的方式创建类的实例

  • 访问某个类或接口的静态变量,或者对该静态变量赋值

  • 调用类的静态方法

  • 反射(如 Class.forName(“com.ngu.oa.Test”))

  • 初始化某个类的子类,则其父类也会被初始化

  • Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类

4、双亲委派机制

首先介绍一下引导类加载器、扩展类加载器、应用类加载器的作用:

  • 引导类加载器:负责加载位于jre/lib目录下的核心类库,如rt.jar。

  • 扩展类加载器:负责加载位于jre/lib/ext目录下的jar包。

  • 应用类加载器:负责加载classpath路径下的类,也就是自己写的类。

类加载器父子层级关系:

71a3ee43b293863d89f738b216e76b0c.png

双亲委派机制:在加载某个类时,先查询要加载的类是否已经加载过,如果没有加载过,先委托给父类加载器(这里的父加载器并不是指的继承关系(extends),而是组合关系,该父类加载器指向ClassLoader的parent属性)加载,依次向上委托,直到引导类加载器,如果引导类加载器无法加载,则依次向下的类加载器尝试加载,如果都不能加载,则由自己进行加载,如果自己也加载不了,则会抛出异常ClassNotFoundException。

双亲委派机制的源码如下:

f1a042c8cc07754b804f6295073f38bb.png

首先尝试从缓存中查询该类是否已经加载过,如果已经加载过则直接返回,否则当前类加载器是否存在父类加载器,如果存在则继续调用父类加载器的loadClass方法,依次向上委托,否则调用启动类加载器进行加载,如果仍未能加载该类,则调用URLClassLoader中的findClass()方法依次向下,由子类加载器加载,如果都未能加载,则抛出ClassNotFoundException。

fdb5b3686b4cd9f9f15cf2ecae9c9d23.png

实现双亲委派机制的原因:

  • 沙箱安全机制:防止核心类库被篡改,比如自己在类路径下定义一个java.lang.String类,是无法被加载的。

  • 避免类重复加载:当父类已经加载该类时,子类加载器不需要再次加载,保证了类的唯一性。

5、全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。

6、缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效。

7、自定义类加载器:自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 loadClass(String, boolean),该方法实现了双亲委派机制,如果需要打破双亲委派机制,需要重新该方法;另一个方法是findClass(String),默认实现是一个空方法,所以我们自定义类加载器主要是重写findClass()方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值