JVM类加载机制
JVM类加载机制
本文主要记录JAVA项目在启动之后,对于我们编写好的JAVA代码是如何加载,以及加载过程中还执行了哪些操作。
运行全程
现假设有一个JAVA程序,程序内编写如下代码并启动main()方法
package cn.yaien.jvm;
/**
* <h1> 模拟类加载过程 </h1>
*
* @author aieny
* @date 2023-02-04
**/
public class Math {
public static final int intValue = 666;
public static User user = new User();
public int open() { //一个方法对应一块栈帧内存区域
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
Math math = new Math();
math.open();
}
}
以上代码在启动后,执行的大体流程如下
C++实现流程需要下载hotspot源码进行查看,在此不进行赘述。
类加载器
加载器类型
类的加载主要是通过类加载器来加载,java里主要有以下这几种类加载器
- 引导类加载器(bootstrapLoader):负责加载位于JRE的lib目录下的核心类库
- 扩展类加载器(extClassLoader):负责加载位于JRE的lib目录下ext扩展目录中的类包
- 应用程序类加载器(appClassLoder):负责加载ClassPath路径下的类,这部分类就是程序员自己写的类
- 自定义类加载器:负责加载程序员指定路径下的类
package cn.yaien.jvm;
import sun.misc.Launcher;
import java.net.URL;
/**
* <h1> 查看JAVA类加载器 </h1>
*
* @author aieny
* @date 2023-02-04
**/
public class TestClassLoader {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(TestClassLoader.class.getClassLoader().getClass().getName());
System.out.println();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassloader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader : " + bootstrapLoader);
System.out.println("the extClassloader : " + extClassloader);
System.out.println("the appClassLoader : " + appClassLoader);
System.out.println();
System.out.println("bootstrapLoader加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println();
System.out.println("extClassloader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
运行结果如下:
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@2a84aee7
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
bootstrapLoader加载以下文件:
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/classes
extClassloader加载以下文件:
/Users/aieny/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
appClassLoader加载以下文件:
/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_361.jdk/Contents/Home/jre/lib/rt.jar:/Users/aieny/IdeaProjects/work/yanggl_code/out/production/yanggl_code:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar
注意:上述代码可以看到,加载String系统类的累加载为null,而Spring是在resources.jar包中的,加载此类包的bootstrapLoader,之所以为null是因为引导类加载器是在C++底层代码实现的,没有加载到JVM里
类加载器实例源码(sun.misc.Launcher)
在启动项目的时候,c++底层会调用Launcher类构造方法,构造方法创建了两个类加载器:ext和app,而Launcher.getCLassLoader()
方法默认返回的类加载器是appCLassLoader应用类加载器。
public Launcher() {
ExtClassLoader var1;
try {
// 构建Ext扩展类加载器,父加载器设置为null
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// 构建App应用类加载器,并传入扩展类加载器作为其父加载器
// 设置默认类加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
}
/**
* 获取类加载器
*/
public ClassLoader getClassLoader() {
return this.loader;
}
类加载过程(loadClass)
类的加载过程,这一步主要是将.java文件编译加载到JVM中,供程序运行时的调用,其加载过程主要有如下几步:
- 加载:查找硬盘上符合的字节码文件通过IO读入,加载是懒加载(使用到类时才会加载)
- 验证:校验字节码文件内容是否符合编写规范
- 准备:为类的静态变量分配内存并赋默认值
- 解析:将符号引用替换成直接引用,即将一些静态方法替换为指向数据所存在的指针或句柄(静态链接)
- 初始化:为类的静态变量赋值为指定的值并调用静态代码块
类加载器将类加载到JVM方法区中后,会创建一个对应的Classl类型对象实例放到堆(heap)中,开发人员访问方法区中类定义中的信息都是通过heap中的class作为入口和切入点的。
类在方法区中主要存有:
- 运行时常量池
- 类型信息
- 字段信息
- 类加载器的引用
- class实例的引用等
双亲委派机制
双亲委派机制即在加载某个类时,会先委托父加载器去找是否已经加载过了,找到了直接返回,如果没有继续向上委托。所有父加载器都没有加载过,则从父加载器回去自己的加载路径下查找并加载,父类加载失败则由子加载器继续加载,如果都找不到则由自己类的类加载器进行加载。
双亲委培机制简单讲就是:先叫父加载器看有没有加载过,没有就尝试加载,不行就子加载器自己加载
双亲委派源码
/**
* 类加载器中的loadClass方法,这里面就实现了双亲委派机制
*/
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 检查加载器时候加载过该类
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 如果父加载器不为空,调用父加载器的loadClass加载
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();
// 尝试在加载器路径下查找并加载类
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;
}
}
设计好处
沙箱安全:可以保障JAVA自己写的核心类不会被随意加载,防止核心类库被篡改
避免重复加载:当父加载器已经加载过,则子加载类就没必要再去加载了,保证了被加载类的唯一性
全盘负责机制
全盘负责即当一个classLoade装载一个类的时候,这个类所依赖以及引用的类也是由这个classLoader来加载的,除非显示的指定使用另一个类加载器。
自定义类加载器
自定义类加载器需要继承ClassLoader类,重写该类的两个核心方法:loadClass实现双亲委派机制;findClass进行类加载实现
package cn.yaien.jvm;
import java.io.FileInputStream;
import java.lang.reflect.Method;
/**
* <h1> 自定义类加载器 </h1>
*
* @author aieny
* @date 2023-02-04
**/
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
/**
* 加载类
*
* @param name 类全名
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
/**
* 双亲委派机制
*/
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 打破双亲委派机制就是修改此处实现
return super.loadClass(name, resolve);
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:/test");
Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}