当前位置:网站首页>jvm 一之 类加载器
jvm 一之 类加载器
2022-07-31 13:50:00 【兮家小二】
一、类加载器的相关说明
类加载器作用
将我们的class文件读取到内存中
类加载器加载的过程
类加载器加载我们的class文件,并且经历过验证、准备、解析,在初始化我们该类。
Class文件读取来源
1.本地磁盘文件 java源代码编译的class文件
2.通过网络下载的class文件
3.War、Jar解压的class文件
4.从专门的数据库中读取的class文件
5.使用java cglib、动态代理生成的代理类class文件
Jvm虚拟机中 通过 类加载器(用户可以自定义类加载器)
类加载器的分类
- 1.
启动(Bootstrap)类加载器
:加载JVM自身工作需要的类,它由JVM自己实现。它会加载$JAVA_HOME/jre/lib下的文件底层是C语言实现
- 2.
扩展(Extension)类加载器
:它是JVM的一部分,由sun.misc.LauncherExtClassLoader实现,他会加载ExtClassLoader实现,他会加载ExtClassLoader实现,他会加载JAVA_HOME/jre/lib/ext目录中的文件(或由System.getProperty(“java.ext.dirs”)所指定的文件)。底层是Java实现
- 3.
(应用)AppClassLoader 类加载器
:应用类加载器,我们工作中接触最多的也是这个类加载器,它由sun.misc.Launcher$AppClassLoader实现。他加载我们工程目录classpath下的class及jar包底层是java实现
- 4.自定义类加载器: 也就是用户自己定义的类加载器
类加载器读取class 目录总结:
- 启动(Bootstrap)类加载器 : $JAVA_HOME/jre/lib
- 扩展(Extension)类加载器 : JAVA_HOME/jre/lib/ext
- 应用 (AppClassLoader) 类加载器 : 工程目录编译后的 classpath 下的 class 及jar包
·
二、类加载API 及 加载顺序
各加载器加载的目录
1、启动类加载器
public static void bootstrapClassLoader() {
String property = System.getProperty("sun.boot.class.path");
List<String> list = Arrays.asList(property.split(";"));
list.forEach((t) -> {
System.out.println("启动类加载器目录:" + t);
});
}
加载 jre 源码包class文件
2、扩展类加载器
public static void extClassLoader() {
String property = System.getProperty("java.ext.dirs");
List<String> list = Arrays.asList(property.split(";"));
list.forEach((t) -> {
System.out.println("扩展类加载器" + t);
});
}
加载 扩展 jar class文件, 比如maven 引入的jar
3、应用类加载器
public static void appClassLoader() {
String property = System.getProperty("java.class.path");
List<String> list = Arrays.asList(property.split(";"));
list.forEach((t) -> {
System.out.println("应用类加载器" + t);
});
}
加载当前项目编译后的class 文件,不过会向上委托,把 启动类加载器,扩展类加载器 把需要的 class 一起给加载到jvm中
类加载器加载顺序
1、启动
(Bootstrap)类加载器 ->2、扩展
(Extension)类加载器 ->3、应用
(AppClassLoader) 类加载器不过程序一般都是 应用 (AppClassLoader) 类加载器 开始的,他会先向上委托 (双亲委派机制)
另外如果 包名+类名 重复, 将 根据顺序优先级加载类,如我们定义了一个和 jdk 相同的类,他会在启动类加载器读取到jdk自动的类, 从而不会在 应用类加载器在来读取我们自己定义的类
详见源码: ClassLoader
// 查询缓存中是否有缓存 该class
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 获取当前类加载器的父加载器 ---扩展类加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 如果当前没有父加载器,就是为启动类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 如果父加载器(扩展和启动类加载器都没有加载class,则使用当前(应用类加载器加载))
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
加载一个类
效果等同于 new User(); 加载后将编译成的calss文件, calss 存放目录查看
Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass("com.wsx.days01.User");
Object o = aClass.newInstance();
获取当前使用的类加载器的方法
String s = new String();
System.out.println("String类加载器" + s.getClass().getClassLoader());
User user= new User ();
System.out.println("user类加载器" + user.getClass().getClassLoader());
如果为 null 表示使用的启动类加载器,否则打印出对应的类加载器
三、自定义一个类加载器
自定义一个类加载器 加载指定磁盘的 class 文件
自定义类加载器 MeiteClassLoader
package com.mayikt.days01;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
public class MeiteClassLoader extends ClassLoader {
private File fileObject;
public MeiteClassLoader(File fileObject) {
this.fileObject = fileObject;
}
public void setFileObject(File fileObject) {
this.fileObject = fileObject;
}
public File getFileObject() {
return fileObject;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 从磁盘中读取到一个class文件 网络 向服务器端发送rest请求拿到class文件
byte[] data = getClassFileBytes(this.fileObject);
// 如何读取class文件 class文件如何组成
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 从文件中读取去class文件
*
* @throws Exception
*/
private byte[] getClassFileBytes(File file) throws Exception {
//采用NIO读取
FileInputStream fis = new FileInputStream(file);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
return baos.toByteArray();
}
}
使用自定义类加载器
public class JVMDemo11 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
// 使用该自定义类加载器加载 class
MeiteClassLoader mayiktClassLoader =
new MeiteClassLoader(
new File("D:\\mayikt\\mayikt-jvm\\MeiteEntity.class"));
// 读取 class加载到内存中
Class<?> aClass = mayiktClassLoader.loadClass("com.mayikt.days01.MeiteEntity");
// 类加载器 将class文件读取到内存中 new 该对象
Object o = aClass.newInstance();
System.out.println(o);
}
}
四、手写一个热部署插件
实现流程:
- 定义一个线程监听编译后的目录下的所有 class 文件
- 判断目录下的 class文件修改日期是否有发生变化,如果有发生变化 (使用获取文件时间的api),则从新使用类加载器读取最新的class文件到内存中。
创建一个类 保存类class 文件信息
public class ClassFileEntity {
/**
* 类的名称
*/
private String name;
/**
* 最后被更改的时间 文件最后更改的时间
*/
private long lastModified;
}
定义一个监听类,重新加载发生变化的 class 类
import com.mayikt.classLoader.MayiktClassLoader;
import com.mayikt.entity.ClassFileEntity;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class HotDeploymentPlug {
// 监听目录
private String path = "D:\\mayikt\\mayikt-jvm";
// 存放被监听的 calss 文件信息
private Map<String, ClassFileEntity> classFiles = new HashMap<>();
// 包名,最好修改为动态获取,通过目录层级
private String packageName = "com.mayikt.days01.";
/**
* 热部署插件原理 (手写)
* 1. class 如何判断一个class文件是否发生变化? 操作系统提供api 文件修改时间
* 2. 判断该class文件修改日期是否有发生变化,如果有发生变化,则从新使用类加载器读取最新的class文件到内存中。
* 3. 如何监听-----class文件是否有发生变化呢? 单独线程
*/
public void start() {
new Thread(() -> {
// 1.读取本地磁盘中 class文件
// 1.读取该文件下 第一次 读取每个class文件 记录每个class文件修改时间
while (true) {
File files = new File(path);
File[] tempList = files.listFiles();
for (File file : tempList) {
// 文件最后修改时间
long lastModified = file.lastModified();
// 文件名称 packageName+MeiteEntity.class
String className = packageName + file.getName().replace(".class", "");
ClassFileEntity mapClassFileEntity = classFiles.get(className);
if (mapClassFileEntity != null) {
if (mapClassFileEntity.getLastModified() != lastModified) {
try {
// 说明了 class文件有发生变化
MayiktClassLoader mayiktClassLoader = new MayiktClassLoader(file);
Class<?> aClass = mayiktClassLoader.loadClass(className);
Object o = aClass.newInstance();
log.info(">className:{} 文件发生了变化<", className);
// 反射调用方法,查看方法发生改变后的数据
// Method mayikt = aClass.getMethod("mayikt");
// Object result = mayikt.invoke(o);
// 同步最新的 class文件修改时间
mapClassFileEntity.setLastModified(lastModified);
} catch (Exception e) {
log.error(e.getMessage());
}
}
} else {
// 在我们的集合中不存在该 ClassFileEntity
ClassFileEntity classFileEntity = new ClassFileEntity(className, lastModified);
classFiles.put(className, classFileEntity);
}
}
// 避免cpu飙高的问题
try {
Thread.sleep(300);
} catch (Exception e) {
}
}
// 2.读取class文件 存入到 mapClassFiles
// 2.监听该class文件是否有发生变化
// 3.如果该class文件最后的修改时间 发生了变化 则从新使用类加载器读取
}).start();
}
public static void main(String[] args) {
HotDeploymentPlug hotDeploymentPlug = new HotDeploymentPlug();
hotDeploymentPlug.start();
}
边栏推荐
- LeetCode·每日一题·1161.最大层内元素和·层次遍历
- Comparison of Optical Motion Capture and UWB Positioning Technology in Multi-agent Cooperative Control Research
- 232层3D闪存芯片来了:单片容量2TB,传输速度提高50%
- 爱可可AI前沿推介(7.31)
- 六石编程学:不论是哪个功能,你觉得再没用,会用的人都离不了,所以至少要做到99%
- 技能大赛dhcp服务训练题
- Edge Cloud Explained in Simple Depth | 4. Lifecycle Management
- Node version switching management using NVM
- MySQL玩到这种程度,难怪大厂抢着要!
- AI cocoa AI frontier introduction (7.31)
猜你喜欢
Introduction to the PartImageNet Semantic Part Segmentation dataset
The Selenium IDE of the Selenium test automation
Grab the tail of gold, silver and silver, unlock the programmer interview "Artifact of Brushing Questions"
AI cocoa AI frontier introduction (7.31)
C# control ListView usage
A detailed explanation of the usage of Async and Await in C#
PHP Serialization: eval
ICML2022 | Fully Granular Self-Semantic Propagation for Self-Supervised Graph Representation Learning
Error: npm ERR code EPERM
图像大面积缺失,也能逼真修复,新模型CM-GAN兼顾全局结构和纹理细节
随机推荐
操作符详解
Error IDEA Terminated with exit code 1
纸质说明书秒变3D动画,斯坦福大学吴佳俊最新研究,入选ECCV 2022
Controller层代码这么写,简洁又优雅!
Shang Silicon Valley-JVM-Memory and Garbage Collection (P1~P203)
Text similarity calculation (Chinese and English) detailed explanation of actual combat
ML、DL、CV常见的问题整理
For enterprises in the digital age, data governance is difficult, but it should be done
Six Stones Programming: No matter which function you think is useless, people who can use it will not be able to leave, so at least 99%
技能大赛训练题:MS15_034漏洞验证与安全加固
清除浮动的四种方式及其原理理解
八大排序汇总及其稳定性
Error: npm ERR code EPERM
golang-gin-优雅重启
Tortoise speed by "template"
技能大赛dhcp服务训练题
六石编程学:不论是哪个功能,你觉得再没用,会用的人都离不了,所以至少要做到99%
LeetCode只出现一次的数字
IDEA connects to MySQL database and uses data
MySQL玩到这种程度,难怪大厂抢着要!