【JVM】JVM(一)–聊聊JVM类加载机制

没有人挡得住,你疯狂的努力进取。你可以不够强大,但你不能没有梦想。如果你没有梦想,你只能为别人的梦想打工筑路。

导读:本篇文章讲解 【JVM】JVM(一)–聊聊JVM类加载机制,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

聊聊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 命令想要成功运行是不是要按照以下几步

  1. 运行java Math 命令
  2. 启动JVM虚拟机
  3. 将Math.class 字节码加载到JVM虚拟机中
  4. 执行main 方法

二、JVM 加载类流程

在这里插入图片描述

  1. 运行java Math 命令(注意此处Math 是类名)
  2. java 命令底层会启动一个JVM虚拟机
  3. JVM虚拟机创建引导类加载器(BootStrap ClassLoader)实例
  4. 引导类加载器实例调用java代码 sun.misc.Launcher.getLauncher() 得到launcher类实例
  5. launcher实例调用 getClassLoader 方法 得到Classloader 实例
  6. 调用classloader.loadClass(Math) 方法加载类
  7. 运行main方法 (JVM C++测发起调用)

通过上面流程我们知道 jvm类加载的时候调用了Launcher 类,我们来看看Launcher类的具体内容
在这里插入图片描述

通过图中代码我们可以得出以下信息

  1. Launcher 实例是个单例
  2. Launcher 构造方法会创建两种类型的类加载器(ExtClassLoader、AppClassLoader
  3. 创建AppClassLoader 的时候用到了ExtClassLoader (继续深入跟进我们会发现,java 中所有的ClassLoader 都继承自ClassLoader 类,类中存在一个属性 private final ClassLoader parent)
  4. AppClassLoader.parent = ExtClassLoader
  5. 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;
        }
    }

上述代码流程解读

  1. 检查是否类是否已经被加载(findLoadedClass) 如果已经被加载直接返回
  2. parent 不为空 ,parent 去加载 (此处会递归一直找到parent 为空技术)AppClassLoader->ExtClassLoader
  3. parent 为空 引导类加载器尝试加载
  4. 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());
        }

    }
}

五、类加载的具体过程

在这里插入图片描述

  1. 加载:将class 文件通过文件流的形式加载进内存(类加载是用到的时候加载,例如main 方法中遇到new 关键字 ),在加载阶段会生成一个代表这个类的java.lang.Class对象, 作为方法区这个类的各种数据访问入口
  2. 验证:校验字节码文件的正确性
  3. 准备:给静态变量分配内容,并赋予默认值
  4. 解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过 程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用
  5. 初始化:对静态变量初始化为指定的值,调用静态代码
  6. 使用
  7. 卸载

六、总结

下面再带大家回顾一下我们上面讲的全部内容

  1. 一条简单的java命令,底部会创建一个JVM虚拟机,JVM会创建一个引导类加载器,引导类加载器调用Launcher.getLauncher()方法,然后launcher.getClassloader() 得到AppClassLoader加载器,ClassLoader 调用loadClass 的过程采用的是双亲委派机制进行加载,如果想要打破双亲委派机制,自己定义自己的类加载器。
  2. 类加载的具体过程 分为加载、验证、准备、解析、初始化、使用、卸载

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/132182.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!