聊聊JVM类加载机制
一、从一条最简单的命令开始
首选我们准备一个最简单的java类,编译运行
/**
0. cd 文件所在目录
1. 将java文件编译成字节码文件 javac Math.java -->Math.class
2. 运行 java Math
*/
public class Math {
public int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
Math math = new Math();
System.out.println(math.add(1, 2));
}
}
我们都知道java是运行在JVM虚拟机上的,试想一下,java Math 命令想要成功运行是不是要按照以下几步
- 运行java Math 命令
- 启动JVM虚拟机
- 将Math.class 字节码加载到JVM虚拟机中
- 执行main 方法
二、JVM 加载类流程
- 运行java Math 命令(注意此处Math 是类名)
- java 命令底层会启动一个JVM虚拟机
- JVM虚拟机创建引导类加载器(BootStrap ClassLoader)实例
- 引导类加载器实例调用java代码 sun.misc.Launcher.getLauncher() 得到launcher类实例
- launcher实例调用 getClassLoader 方法 得到Classloader 实例
- 调用classloader.loadClass(Math) 方法加载类
- 运行main方法 (JVM C++测发起调用)
通过上面流程我们知道 jvm类加载的时候调用了Launcher 类,我们来看看Launcher类的具体内容
通过图中代码我们可以得出以下信息
- Launcher 实例是个单例
- Launcher 构造方法会创建两种类型的类加载器(
ExtClassLoader、AppClassLoader
) - 创建
AppClassLoader
的时候用到了ExtClassLoader
(继续深入跟进我们会发现,java 中所有的ClassLoader 都继承自ClassLoader
类,类中存在一个属性private final ClassLoader parent)
- AppClassLoader.parent = ExtClassLoader
- ExtClassLoader.parent = null
综上所述,我们现在知道,JVM启动时候一共会创建三个类加载器(自定义类加载器创景除外)分别是
- BootStrap ClassLoader 引导类加载器(C++语言实现) — 用于加载 jdk lib 目录下jar包 (比如:rt.jar )
- ExtClassLoader 扩展类加载器 (JAVA 语言实现)— 用于加载 jdk lib/ext目录下jar包
- AppClassLoader 应用程序类加载器 (JAVA 语言实现)— 用于加载classPath 下jar包,就是我们日常自己写的代码
- 自定义类加载器 负责加载自定义路径下的jar包
三、JVM类加载具体流程
我们上面知道 Math 类是通过APPClassLoader.loadClass(Math) 方法加载,我们现在来跟进一下具体记在过程
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) {
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;
}
}
上述代码流程解读
- 检查是否类是否已经被加载(findLoadedClass) 如果已经被加载直接返回
- parent 不为空 ,parent 去加载 (此处会递归一直找到parent 为空技术)AppClassLoader->ExtClassLoader
- parent 为空 引导类加载器尝试加载
- parent 如果加载到直接返回,未加载到自己加载(注意这里所说的自己是针对以没一层往上递归时的自己)
四、一个验证类加载器的小案例
public class TestClassLoader {
public static void main(String[] args) {
System.out.println("---------验证不同的类有不同的类加载器加载---------");
System.out.println(String.class.getClassLoader()); //BootStrap ClassLoader
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName()); //ExtClassLoader
System.out.println(TestClassLoader.class.getClassLoader().getClass().getName()); //AppClassLoader
System.out.println("---------验证类加载器之间parent之间的关系---------");
ClassLoader appClassLoader = TestClassLoader.class.getClassLoader();
ClassLoader extClassLoader = appClassLoader.getParent();
ClassLoader boostStrapClassLoader = extClassLoader.getParent();
System.out.println(extClassLoader.getClass().getName());
System.out.println(boostStrapClassLoader);
System.out.println("---------验证boostStrapClassLoader加载路径---------");
URLClassPath bootstrapClassPath = Launcher.getBootstrapClassPath();
URL[] urLs = bootstrapClassPath.getURLs();
for (URL urL : urLs) {
System.out.println(urL.toString());
}
}
}
五、类加载的具体过程
- 加载:将class 文件通过文件流的形式加载进内存(类加载是用到的时候加载,例如main 方法中遇到new 关键字 ),在加载阶段会生成一个代表这个类的java.lang.Class对象, 作为方法区这个类的各种数据访问入口
- 验证:校验字节码文件的正确性
- 准备:给静态变量分配内容,并赋予默认值
- 解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过 程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用
- 初始化:对静态变量初始化为指定的值,调用静态代码
- 使用
- 卸载
六、总结
下面再带大家回顾一下我们上面讲的全部内容
- 一条简单的java命令,底部会创建一个JVM虚拟机,JVM会创建一个引导类加载器,引导类加载器调用
Launcher.getLauncher()
方法,然后launcher.getClassloader()
得到AppClassLoader
加载器,ClassLoader
调用loadClass
的过程采用的是双亲委派机制进行加载,如果想要打破双亲委派机制,自己定义自己的类加载器。 - 类加载的具体过程 分为加载、验证、准备、解析、初始化、使用、卸载
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/132182.html