类加载器
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类加载器相关的知识,希望对你有一定的参考价值。
类加载器机制的层次结构每个 .java 文件都包含着程序的业务逻辑,这些 .java 文件经过 Java 编译器编译成 .class 文件,.class 文件中包含着 Java 代码转换后的虚拟机指令,当需要使用某个类时,虚拟机加载它的 .class 文件,并创建对应的 class 对象,将 .class 文件加载到虚拟机内存,这个过程就成为类加载
加载
将字节码文件加载到内存,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表该类的 class 对象,作为方法区数据的访问入口
链接
将 Java 类的二进制代码合并到JVM的运行状态之中的过程
验证:确保加载的类信息符合 JVM 规范,没有安全方面的问题
准备:正式为类变量配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配
解析:虚拟机常量池的符号引用替换为字节引用过程
初始化
执行类构造器 \<clinit>() 方法的过程,类构造器 \<clinit>() 方法是由编译器自动收藏类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生,代码从上往下执行,当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化,虚拟机会保证一个类的 \<clinit>() 方法在多线程环境中被正确加锁和同步
类加载器层次结构
启动(Bootstrap)类加载器
加载 JVM 自身需要的类,它负责将 %JAVA_HOME%/lib 路径下的核心类库或 -Xbootclasspath 参数指定的路径下的jar 包加载到内存中,虚拟机按照文件名识别加载 jar 包,如果文件名不被虚拟机识别,即使丢到 lib 也不被加载,处于安全考虑,启动类加载器只加载包名为 java、javax、sun 等开头的类
扩展(Extension)类加载器
扩展类加载器是指 Sun 公司的 sun.misc.Launcher$ExtClassLoader 类,由 Java 语言实现的,是 Launcher 的静态内部类,加载 %JAVA_HOME%/lib/ext 目录下或由系统变量 -Djava.ext.dir 指定位路径中的类库
系统(System)类加载器
也叫应用程序加载器是指 Sun 公司实现的 sun.misc.Launcher$AppClassLoader,加载系统类路径 CLASSPATH 或 -D java.class.path 指定路径下的类库,也就是我们经常用到的 CLASSPATH 路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器
除此以外,我们还可以自定义类加载器,但是虚拟机对 class 文件采用按需加载的方式,而且加载某个类时,采用双亲委派模式将请求交由父类处理
双亲委派模式
如果一个类加载器收到了加载请求,首先它不会去加载,而是将请求委托给自己的父亲去加载,如果他的父亲还存在父亲,就一直向上委托,知道顶层加载器,如果父类加载器可以完成加载请求,就成功返回,如果不行,子加载器才会自己加载
优势:
- 避免重复加载
- 防止核心API库被随意篡改
类加载器之间关系
启动类加载器,由 C++ 实现,没有父类加载器
扩展类加载器,由 Java 实现,父类加载器为 null
系统类加载器,由 Java 实现,父类加载器为扩展类加载器
自定义加载器,父类为系统类加载器
类加载器的常用方法
- loadClass(String) 加载名称为 name 的类,查看这个 class 是否被加载,如果没被加载,继续往下走,查看父类加载器,递归调用该方法,如果父类加载器为 null,说明是启动类加载器,查找对应 class,如果都没找到,调用 finclass, 如果找到直接返回
- findClass(String) 根据名称或位置加载字节码
- defineClass(byte[] b, int off, int len) 将 byte 字节流解析成 JVM 能够识别的 Class 对象
热部署
对于Java 应用程序来说,热部署就是在运行时更新Java 类文件
热部署原理:
1、销毁该自定义 ClassLoader
2、更新 class 类文件
3、创建新的ClassLoader去加载更新后的class类文件
热部署与热加载
热部署与热加载的联系
-
不重启服务器编译/部署项目
- 基于Java的类加载器实现
Java热部署与热加载的区别:
-
部署方式
- 热部署在服务器运行时重新部署项目
- 热加载在运行时重新加载 class
-
实现原理
- 热部署直接重新加载整个应用
- 热加载在运行时重新加载 class
- 使用场景
- 热部署更多的是在生产环境使用
- 热加载则更多的实在开发环境使用
demo
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 获取文件名称
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
// 读取文件流
InputStream is = this.getClass().getResourceAsStream(fileName);
// 读取字节
byte[] bytes = new byte[is.available()];
is.read(bytes);
// 将byte字节流解析成JVM能够识别的Class对象
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
}
/**
* 热部署入口
*/
public class HotSwap {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, InterruptedException {
loadClass();
System.gc();
// 要修改的字节码
File file1 = new File("C:\\Users\\kernel\\Desktop\\User.class");
// 原字节码
File file2 = new File("D:\\Codes\\Java\\Performance\\jvm-day04\\target\\classes\\com\\kernel\\User.class");
if (!file2.delete())
System.out.println("热部署失败。。。");
// 移动字节码
if (file1.renameTo(file2))
loadClass();
}
public static void loadClass() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> aClass = myClassLoader.findClass("com.kernel.User");
Object instance = aClass.newInstance();
Method info = instance.getClass().getMethod("info");
info.invoke(instance);
}
}
以上是关于类加载器的主要内容,如果未能解决你的问题,请参考以下文章
活动(加载器 - 下载)+ 3 个片段(使用加载器 - 计算)
用于在多个活动/片段中重用的全局加载器 (LoaderManager)
Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段
Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段