一.了解类的生命周期
一个类是在运行期间第一次使用时,会被类加载器动态的加载至JVM
。JVM不会一次加载所有类,这样会占用大量的内存。
一个类的加载可以分为如下七个步骤:
前面五个是主要的:
- 加载:
加载过程完成以下3
件事:
- 通过类的完全限定名称获取定义该类的二进制字节流。
- 将该字节流表示的静态存储结构转换为
Metaspace
元空间区的运行时存储结构。 - 在内存中生成一个代表该类的
Class
对象,作为元空间区中该类各种数据的访问入口。
获取二进制字节流的方式:
- 从
ZIP
包读取,成为JAR
、EAR
、WAR
格式的基础。- 从网络中获取,最典型的应用是
Applet
。运行时计算生成,例如动态代理技术,在java.lang.reflect.Proxy
使用ProxyGenerator.generateProxyClass
的代理类的二进制字节流。- 由其他文件或容器生成,例如由
JSP
文件生成对应的Class
类。
2.验证
确保 Class
文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
3.准备
类变量是被 static
修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是元空间区的内存。
实例变量不会在这阶段分配内存,它会在对象实例化时,随着对象一起被分配在堆中。
注:应该注意到,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。
4.解析
将常量池的符号引用替换为直接引用的过程。其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java
的动态绑定。
5.初始化
初始化阶段才真正开始执行类中定义的 Java
程序代码。初始化阶段是虚拟机执行类构造器 <clinit>()
方法的过程。在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。
<clinit>()
是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。所以,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。
6.使用
7.卸载
二.类加载的时机
主动引用
1.当遇到 new
、 getstatic
、putstatic
或 invokestatic
这 4 条字节码指令时,比如 new
一个对象,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
- 当
jvm
执行new
指令时会加载类。即:当程序创建一个类的实例对象。- 当
jvm
执行getstatic
指令时会加载类。即:程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。- 当
jvm
执行putstatic
指令时会加载类。即:程序给类的静态变量赋值。- 当
jvm
执行invokestatic
指令时会加载类。即:程序调用类的静态方法。
2.使用 java.lang.reflect
包的方法对类进行反射调用时如 Class.forname("...")
, 或newInstance()
等等。如果类没初始化,需要触发类的加载。
3.加载一个类,如果其父类还未加载,则先触发该父类的加载。
被动引用
除主动引用之外,所有引用类的方式都不会触发加载,称为被动引用。
三. 类加载器分类
从 Java
虚拟机的角度来讲,只存在以下两种不同的类加载器:
第一种:启动类加载器,使用C++实现,是虚拟机的一部分。
第二种:其他类加载器,它独立于虚拟机,都是java.lang.ClassLoader。启动类加载器(Bootstrap ClassLoader
),扩展类加载器(Extension ClassLoader),应用程序类加载器(Application ClassLoader
)。
四.双亲委派模型
一.概念:
应用程序是由三种类加载器互相配合,从而实现类加载,除此之外还可以加入自己定义的类加载器。类之间的层级关系形成了双亲委派模型。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器(这里的父子关系一般通过组合关系)。如图所示:
二.双亲委派的工作机制
一个类加载器首先先将请求发送到父类的加载器,只有当父类的加载器无法完成才尝试自己完成。
三.双亲委派的作用
使Java
类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一,避免冲突
举个🌰:java.lang.Object
存放在 rt.jar
中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar
中的 Object 比在 ClassPath 中的 Object 优先级更高,因为 rt.jar
中的 Object
使用的是启动类加载器,而 ClassPath
中的 Object
使用的是应用程序类加载器。rt.jar
中的 Object
优先级更高,那么程序中使用的所有的 Object
都是由启动类加载器所加载的 Object
。