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();
    }
}

以上代码在启动后,执行的大体流程如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QW4y0lzX-1675505503032)(/Users/aieny/Downloads/jvm_image/入口类加载流程.png)]

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实例的引用等

双亲委派机制

双亲委派机制即在加载某个类时,会先委托父加载器去找是否已经加载过了,找到了直接返回,如果没有继续向上委托。所有父加载器都没有加载过,则从父加载器回去自己的加载路径下查找并加载,父类加载失败则由子加载器继续加载,如果都找不到则由自己类的类加载器进行加载。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5RFTY3Uv-1675505503033)(/Users/aieny/Downloads/jvm_image/双亲委派.png)]

双亲委培机制简单讲就是:先叫父加载器看有没有加载过,没有就尝试加载,不行就子加载器自己加载

双亲委派源码

/**
 * 类加载器中的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());
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值