Class类加载过程、类加载器简单介绍

 

类加载机制

ClassLoader

ClassLoader 就是类加载器,它的唯一职责就是将Class文件加载到JVM中,通常开发者并不需要自己创建ClassLoader,但在框架、中间件中自定义ClassLoader 非常常见,Tomcat便极具代表性,通过自定义的Tomcat Classloader体系 实现应用的相互隔离。

在 Java 中默认提供了三个类加载器,分别是BootstarapClassLoader、ExtClassLoader、AppClassLoader,它们各自只负载加载规定目录内的Class文件,结构关系。


注:AppClassLoader 和 ExtClassLoader 由 Java 编写并且都是 java.lang.ClassLoader 的子类,而 BootstarapClassLoader 并非由 Java 实现而是由C++ 实现,所以打印结果为null。

查看源码:三个类加载器的范围

 

 

 

 

双亲委派制

简单的来说,双亲委派制就是当加载一个Class文件时会先交由上层ClassLoader来加载,如果发现已加载则直接返回,如果没有加载则去当前ClassLoader 的classes目录寻找该Class文件,找到则加载,找不到则交由下层ClassLoader来继续加载,如果直到最下层加载器都无法加载(找不到该Class文件)则抛出ClassNotFoundException异常。下面通过解读java.lang.ClassLoader 的 loadClass(String name, boolean resolve)源码来进一步了解该机制是如何运转的,代码如下所示。

1 首先,检查该Class是否已经被加载,如果已加载直接返回

2 如果没有加载,是否存在上层加载器,如果存在交由上层加载器加载

3 所有上层加载器都无法加载,由当前加载器加载

双亲委派机制主要出于安全来考虑,那是不是可以打破这个规则呢?请看下面:

打破双亲委派机制

  1. JDK1.2之前,自定义ClassLoader都必须重写loadClass()

  2.ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定

  3. 热启动,热部署

1. osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)

自定义加载器

1  继承ClassLoader,重写模板方法findClass,调用definClass

如下:

 

类的加载过程

 

加载
在加载阶段,JVM需要完成以下3件事情:

·  通过一个类的全限定名来获取定义此类的二进制字节流。

·  将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

·  在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

相对于类加载过程的其他阶段,一个非数组类的加载阶段(准确地说,是加载阶段中获取类的二进制字节流的动作)是开发人员可控性最强的,因为加载阶段既可以使用系统提供的引导类加载器来完成,也可以由用户自定义的类加载器区完成,开发人员可以通过定义自己的类加载器去控制字节流的获取方式(即重写一个类加载器的loadClass()方法)。

对于数组类而言,数组类本身不通过类加载器创建,它是由JVM直接创建的。但数组类与类加载器任然有很密切的关系,因为数组类的元素类型最终是要靠类加载器去创建。

加载阶段完成后,JVM外部的二进制字节流就按照JVM所需的格式存储在方法区之中,方法区中的数据存储格式由JVM实现自行定义,JVM规范未规定此区域的具体数据结构。然后在内存中实例化一个java.lang.Class类的对象(并没有明确规定是在Java堆中,对于HotSpot VM而言,Class对象比较特殊,它虽然是对象,但是存放在方法区里面),这个对象将作为程序访问方法区中的这些类型数据的外部接口。

延迟加载LazyLoading

new getstatic putstatic invokestatic指令,访问final变量除外

  –java.lang.reflect对类进行反射调用时

 –初始化子类的时候,父类首先初始化

 –虚拟机启动时,被执行的主类必须初始化

  –动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化

 

验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前JVM的要求,并且不会危害JVM自身的安全。比如验证“魔数”是否为0xCAFEBABE、Class文件编译版本号是否符合当前JVM等。

Java语言本身是相对安全的语言,使用纯粹的Java代码无法做到注入访问数组边界意外的数据、将一个对象转型为它并未实现的类型、跳转到不存在的代码行之类的事情,如果这样做了,编译器将拒绝编译。

但前面已经说过,Class文件并不一定要求用Java源码编译而来,可以使用任何途径产生,甚至包括用十六进制编辑器直接编写来产生Class文件。在字节码语言层面上,上述Java代码无法做到的事情都是可以实现的,至少语义上是可以表达出来的。

准备
准备阶段将为静态变量申请内存,并赋予初始值(基本类型为其默认值,引用类型为null),假设有如下代码:

public static int value = 8;

在该阶段value的值将根据其类型int初始化为 0。而将 value 赋值为8的动作在初始化阶段才会执行(调用<clinit()>方法,执行putstatic指令)。

解析

将类、方法、属性等符号引用解析为直接引用

常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用

初始化
在初始化阶段会调用类的初始化方法<clinit()>为静态变量赋予实际的值(例如将value赋值为8)、执行静态代码块。

猜你喜欢

转载自blog.csdn.net/huzhiliayanghao/article/details/106858527