目录
类加载子系统
内存结构
类的生命周期
类的加载过程
加载
加载class文件方式
连接
验证
验证阶段
准备
解析
初始化
类加载器
介绍
作用
分类
引导类加载器
自定义类加载器
ClassLoader
获取ClassLoader途径
双亲委派机制
介绍
执行流程
好处
打破双亲委派
类加载子系统
内存结构
Class文件
类加载子系统
运行时数据区
方法区
堆
程序计数器
虚拟机栈
本地方法栈
执行引擎
本地方法接口
本地方法库
类的生命周期
类从被加载到虚拟机内存中开始到卸载出内存为止,整个生命周期为7个阶段:加载、验证、准备、解析、初始化、使用、卸载;其中前三个阶段统称为连接
卸载:jvm结束生命周期
类的加载过程
class文件需要加载到虚拟机之后才能运行和使用;主要分为三步:加载、连接、初始化;其中连接又可以分为三步:验证、准备、解析
/**
*示例代码
*/
public class HelloLoader {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
加载
类加载过程的第一步;
主要为了完成3件事:
1.通过全类名获取定义此类的二进制字节流
2.将字节流所代表的静态存储结构转换为方法区的运行时数据结构
3.在内存中生成一个代表该类的class对象,作为方法区这些数据的访问入口
加载class文件方式
从本地系统中直接加载
通过网络获取
从压缩包中获取
运行时计算生成
其他文件生成(jsp、html)
从数据库中提取
从加密文件中获取
连接
验证
主要是为了确保class文件字节流中包含的信息符合规范,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
验证阶段
准备
为类变量分配内存且设置该类变量的默认初始值。
注意
1.进行内存分配的仅包括类变量(静态变量),而不包括实例变量
2.这里所设置的初始值"通常情况"下是数据类型默认的零值(如 0、0L、null、false 等),比如我们定义了public static int value=111
,那么 value 变量在准备阶段的初始值就是 0 而不是 111(初始化阶段才会赋值)。特殊情况:比如给 value 变量加上了 final 关键字public static final int value=111
,那么准备阶段 value 的值就被赋值为 111
解析
解析是虚拟机将常量池内的符号引用替换为直接引用的过程。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄、调用限制符等7种符号引用进行
初始化
初始化阶段是执行初始化方法<clinit>()方法的过程,是类加载的最后一步,之后jvm才开始执行类中定义的程序代码。
对于<clinit>()方法的调用,jvm会确保其在多线程环境的安全性,因为<clinit>()方法是带锁线程安全,所以在多线程环境下进行类初始化可能会引起线程堵塞,并且这种堵塞很难被发现
类加载器
介绍
类加载器是一个负责加载类的对象,用于实现类加载过程中加载这一步;每个Java类都有一个引用指向加载它的ClassLoader;数组类不是ClassLoader创建的,而是jvm直接生成
作用
系统加载class类型的文件主要分为3步:加载、连接、初始化;其中连接过程又可以分为3步:验证、准备、解析
类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识
分类
jvm支持两种类型的加载器。分别为:引导类加载器、自定义类加载器。
自定义加载器一般指程序开发中开发人员自定义的一类类加载器,但是jvm规范却没有这么定义,而是将所有派生于ClassLoader的类加载器都划分为自定义加载器。
引导类加载器
启动类加载器(引导类加载器,Bootstrap ClassLoader)
这个类加载使用C/C++语言实现的,嵌套在JVM内部。
它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
并不继承自ava.lang.ClassLoader,没有父加载器。
加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
扩展类加载器(Extension ClassLoader)
Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
派生于ClassLoader类
父类加载器为启动类加载器
从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/1ib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
应用程序类加载器(系统类加载器,AppClassLoader)
java语言编写,由sun.misc.LaunchersAppClassLoader实现
派生于ClassLoader类
父类加载器为扩展类加载器
它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
通过ClassLoader#getSystemclassLoader() 方法可以获取到该类加载器
自定义类加载器
为什么要自定义类加载器?
隔离加载类
修改类加载的方式
扩展加载源
防止源码泄漏
实现步骤:
1.需要继承 ClassLoader
抽象类
2.自定义的类加载逻辑写在findClass()方法中
3.在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类
ClassLoader
ClassLoader类是一个抽象类,其后所有的类加载器都继承自ClassLoader。
获取ClassLoader途径
1.获取当前ClassLoader
clazz.getClassLoader()
2.获取当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader()
3.获取系统的ClassLoader
ClassLoader.getSystemClassLoader()
4.获取调用者的ClassLoader
DriverManager.getCallerClassLoader()
双亲委派机制
介绍
当一个类收到了加载请求时,它是不会先自己去尝试加载的,而是委派给父类去完成,比如我现在要 new 一个 Person,这个 Person 是我们自定义的类,如果我们要加载它,就会先委派 App ClassLoader ,只有当父类加载器都反馈自己无法完成这个请求(也就是父类加载器都没有找到加载所需的 Class)时,子类加载器才会自行尝试加载。
双亲委派模型并不是一种强制性的约束,只是 JDK 官方推荐的一种方式
执行流程
1.在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载
2.类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成
3.只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载(调用自己的 findClass()
方法来加载类)
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {//首先,检查该类是否已经加载过Class c = findLoadedClass(name);if (c == null) {//如果 c 为 null,则说明该类没有被加载过long t0 = System.nanoTime();try {if (parent != null) {//当父类的加载器不为空,则通过父类的loadClass来加载该类c = parent.loadClass(name, false);} else {//当父类的加载器为空,则调用启动类加载器来加载该类c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {//非空父类的类加载器无法找到相应的类,则抛出异常}if (c == null) {//当父类加载器无法加载时,则调用findClass方法来加载该类//用户可通过覆写该方法,来自定义类加载器long t1 = System.nanoTime();c = findClass(name);//用于统计类加载器相关的信息sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {//对类进行link操作resolveClass(c);}return c;}
}
好处
避免类的重复加载
保护程序安全,防止核心api被篡改
打破双亲委派
自定义加载器的话,需要继承 ClassLoader
。如果我们不想打破双亲委派模型,就重写 ClassLoader
类中的 findClass()
方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass()
方法。