IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> JVM类加载过程及源码分析 -> 正文阅读

[Java知识库]JVM类加载过程及源码分析

一、JVM类加载过程及源码分析

我们通过ide写的java代码,毫无疑问是最终需要加载到JVM来运行的。试想JVM作为跨语言的平台,能同时支持多种编程语言(js、groory、scala…等)的字节码文件运行,那么在字节码文件和JVM之间,必须有一套完备的流程,来将字节码文件转化为JVM内存中的变量信息。
下图,是坊间盛传的JVM类加载过程,今天我们来探一探水深。
在这里插入图片描述

1.1 前言引语

sun.misc.Launcher的方法很简单,静态代码实例了launcher,供getLauncher()方法获取launcher对象。
在这里插入图片描述
但是,当给我们他通过debug打点到launcher()构造方法获取ExtClassLoader、AppClassLoader时,却发现断点进不到46、52行。是思路错了还是断点无效?

于是我又翻了翻 IBM 关于 Java 中 Debug 实现原理的介绍,文章地址如下:https://www.ibm.com/developerworks/cn/java/j-lo-jpda1/
JDI(Java Debug Interface)是三个模块中最高层的接口,在多数的 JDK 中,它是由 Java 语言实现的。
参考 Oracle 的官方文档:https://docs.oracle.com/javase/9/docs/api/jdk.jdi-summary.html
可以知道 jdi 是一个位于 tools.jar 包下的子包,而 tools.jar 也是由 BootStrap 类加载器负责加载的

可以明确,Debug时调用的是lib/tools.jar的包,而此时启动类加载器势必已经完成启动,所以debug无效。

1.2类加载器

类加载器定义:

  • 启动类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等
  • 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR 类包
  • 应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的类
  • 自定义加载器:负责加载用户自定义路径下的类包
    JVM中默认读取的路径参数:
      class_path = SecuritySupport.getSystemProperty("java.class.path");
      boot_path  = SecuritySupport.getSystemProperty("sun.boot.class.path");
      ext_path   = SecuritySupport.getSystemProperty("java.ext.dirs");

上节了解到,启动类加载器比较神秘,多是由C++来实现的,那我们就先从Luncher着手看看扩展类加载器和应用程序类加载器的结构。

1、首先查看Launcher的结构,内部类有AppClassLoader、ExtClassLoader ,并且这2个内部类都继承了URLClassLoader。下图看URLClassLoader的组织结构,最终继承了ClassLoader类。所以我们需要明确,类加载器之间并没有继承的父子关系,而是ClassLoader中维护了private final ClassLoader parent;字段,用这种方式来标记当前加载器的父加载器。后面在看到双亲委委派机制时,不能搞混了。
在这里插入图片描述在这里插入图片描述
2、Luncher使用的ClassLoader ,是由getAppClassLoader()生成的,所以JVM的加载的类,首先都需要从App加载器开始加载。
Luncher的构造方法,生成 ExtClassLoader 、AppClassLoader。

    private ClassLoader loader;
	public ClassLoader getClassLoader() {
        return this.loader;
    }

    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
        	//创建ExtClassLoader扩展类加载器对象
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
        try {
        	//将扩展类加载器作为参数,创建AppClassLoader对象
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
    }

2、AppClassLoader的getAppClassLoader方法,将ExtClassLoader对象 var0传递给超类ClassLoader,并标记ExtClassLoader 扩展类加载器是AppClassLoader应用程序加载器的父加载器。同时ExtClassLoader 在构造时,传入的父加载器参数是null,但实际其父类加载器是启动类加载器,C++实现的所以在java代码中只出现了BootClassPathHolder。

		//var0是前面生成的ExtClassLoader对象
        public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            final String var1 = System.getProperty("java.class.path");
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
                public Launcher.AppClassLoader run() {
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    //创建AppClassLoader应用类加载器对象
                    return new Launcher.AppClassLoader(var1x, var0);
                }
            });
        }
        
	 private ClassLoader(Void unused, ClassLoader parent) {
	 	//可见在AppClassLoader中,设置了parent为ExtClassLoader 
        this.parent = parent;
    }

1.3 类加载过程及双亲委派机制

类加载的过程中,依次向上委托父类加载器进行加载。这就形成了双亲委派机制。结合loadClass(name)方法和下图看类的加载顺序,就不难理解了。
双亲委派机制简单的说,就是app加载器先向上交由父类加载器进行加载,父类中找不到,再由子类加载器自行加载。
为什么要设计成双亲委派机制?

  • 安全机制,防止核心API库被随意篡改。
  • 避免重复加载:当父类已经加载过该类时,子类加载器不再加载。保证被加载类的唯一性。
    核心图一 类加载过程及双亲委派机制
    核心图一  类加载过程及双亲委派机制
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            //第一步,在已加载的类中查找是否存在
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                	//第二步,通过父加载器去加载,如果当前是app加载器,则去ext扩展类加载器中查找,如果是扩展类加载器,则去查启动类加载器
                    if (parent != null) {
                    	//扩展类加载器没有重写loadClass方法,会再次进入本方法
                        c = parent.loadClass(name, false);
                    } else {
                    	//启动类加载器加载某个类
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }

                if (c == null) {
                    //第三步,如果还是没有找到类,那就要去磁盘查找文件,将类加载进来
                    long t1 = System.nanoTime();
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

	//具体实施查找的方法
    protected Class<?> findClass(final String name) throws ClassNotFoundException {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                    	//第四步,将类的全限定名,改写成目录的形式 (将.替换成/)
                        String path = name.replace('.', '/').concat(".class");
                        //第五步,去磁盘目录中查找文件
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                            	///第六步,将文件转化为内存中的类
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

1.4 类初始化过程

从磁盘中读取字节码文件 —> 转为二进制文件 —> 验证 —> 准备 —> 解析 —> 初始化
这其中多数核心代码,仍然是C++实现,Java代码中看不到太多有效代码,无奈略过。
在这里插入图片描述

    private Class<?> defineClass(String name, Resource res) throws IOException {
        long t0 = System.nanoTime();
        int i = name.lastIndexOf('.');
        URL url = res.getCodeSourceURL();
        if (i != -1) {
            String pkgname = name.substring(0, i);
            // Check if package already loaded.
            Manifest man = res.getManifest();
            definePackageInternal(pkgname, man, url);
        }
        // Now read the class bytes and define the class
        java.nio.ByteBuffer bb = res.getByteBuffer();
        if (bb != null) {
            // Use (direct) ByteBuffer:         
        } else {
            byte[] b = res.getBytes();
            // must read certificates AFTER reading bytes.
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, b, 0, b.length, cs);
        }
    }

    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
    	//前期检查
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

1.4 自定义类加载器

  • 预期:
    在加载类的时候,从自定义的路径中去查找文件。所以在实现自定义加载器时,只需要把原加载器中定义加载文件的路径替换为我们自己的路径即可。
  • 思路:
    从上面核心图一中,我们看到最终在查到磁盘中类文件的方法实际是: findClass(name),而扩展类加载器和APP类加载器,实际都没有去实现,而是在父类URLClassLoader中实现的 findClass(name)方法,所以在自定义加载器中,只要重写下方path的路径即可。
    protected Class<?> findClass(final String name) throws ClassNotFoundException {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                    	//查找磁盘文件路径的核心代码
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                            }
                        }
                    }
                }, acc);
        }
        return result;
    }

自定义加载器的功能在1.5中实现。

1.5 打破双亲委派机制

  • 预期:打破双亲委派的目的,就是在加载某个类时,不去父类中完成类的加载,而是自行实现加载的逻辑。
  • 思路:JVM中的加载器在定义加载类时,实际是继承或重写loadClass()来实现的。正如核心图一中的代码逻辑,我们只要不再使用原有的loadClass()逻辑,自行实现自由的加载逻辑即可。
public class MyClassLoader extends ClassLoader {
    //需要加载的自定义加载路径
    private String classPath;

    //指定路径的构造方法
    public MyClassLoader(String path){
        this.classPath = path;
    }

	//修改路径的自定义加载器
    private byte[] loadByte(String name) throws Exception {
    	//保持和原代码大体一致
        String path = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + path + ".class");
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }

    @Override
    protected Class<?> findClass(final String name) throws ClassNotFoundException{
        try {
            byte[] data = loadByte(name);
            //找到文件后,继续沿用原有的加载类的方法
            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
    {
        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(name.startsWith("com.asky")){
                        c = findClass(name);
                    }else {
                        c = this.getParent().loadClass(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader("D:/study");
        Class clazz = classLoader.loadClass("com.asky.test");
        Object obj = clazz.newInstance();
        System.out.println(clazz.getClassLoader());
    }
}

在这里插入图片描述

参考文章:https://cloud.tencent.com/developer/article/1590312

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-17 01:24:15  更:2021-08-17 01:24:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 23:36:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码