目录
类加载子系统
内存结构
类的生命周期
类的加载过程
加载
加载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对象,作为方法区这些数据的访问入口
从本地系统中直接加载
通过网络获取
从压缩包中获取
运行时计算生成
其他文件生成(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种符号引用进行
初始化阶段是执行初始化方法
对于
类加载器是一个负责加载类的对象,用于实现类加载过程中加载这一步;每个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。
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()
方法。