JVM类加载机制

本文详细介绍了Java中的类加载过程,包括加载、验证、准备、解析和初始化等步骤。讲解了Launcher类如何加载类,并阐述了类加载器的双亲委派机制,以及为何采用这种机制的原因,如沙箱安全和避免类的重复加载。此外,还讨论了自定义类加载器的实现方式,以及如何通过打破双亲委派来实现特定需求,例如Tomcat的类加载策略。
摘要由CSDN通过智能技术生成

Launcher: 启动器,发射器

Launcher类下的getLauncher() ,返回Launcher对象,这个对象是个单例
–》 launcher.getClassLoader() 获取对应的classLoader
–》 classLoader.loadClass("类全限定类名") 加载要运行的类
–》 加载完后jvm会执行类中的main方法
–》 程序运行结束,jvm销毁

类加载过程:

  1. 加载 :在硬盘上查找并通过IO读取字节码文件,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类各种数据的访问入口(字节码加载到jvm内存中)
  2. 验证:校验字节码文件格式是否正确,是否符合jvm规范
  3. 准备:将静态变量按照jvm规定赋予默认的初始值,整形:0,布尔:false
  4. 解析:将符号引用变成直接引用
    符号引用: 就是每一个符号
    直接引用:相当于在jvm中的内存地址
    静态链接:把一些静态方法(符号引用)替换成指向数据所存内存的指针或句柄(直接引用),在类加载期间完成(静态方法,main方法,为了提高效率,因为这些方法在加载之后就不会变了)
    动态链接:程序在运行期间将符号引用替换成直接引用(运行期间多态,相同的符号引用可能动态到不同的内存地址)
  5. 初始化: 对类的静态变量初始化为指定的值,并执行静态代码块

other

类被加载到方法区中后主要包含: 运行时常量池,,类型信息,,字段信息,,方法信息,类加载器实例的引用 ,,对应class实例的引用

Launcher

jar包中的类不是一次性全部加载的,是使用到时才加载
Launcher.getLauncher()返回的 Launcher对象 ,,在构造的时候会 创建 ExtClassLoaderAppClassLoader,,并且将ExtClassLoader实例传入AppClassLoader中,并设置parent属性为ExtClassLoader实例。。
在这里插入图片描述

Launcher默认返回的类加载器是AppClassLoader
在这里插入图片描述

双亲委派机制:

ClassLoader#loadClass() : 实现了双亲委派机制
在这里插入图片描述

  • 引导类加载器:加载jre的lib目录下核心类库
  • 扩展类加载器:加载jre的lib目录下ext扩展目录
  • 应用类加载器: 加载自己写的那些类
  • 自定义加载器: 加载自己定义的路径下的那些类

应用类加载器加载路径:包括了引导类加载器和扩展类加载器的路径

默认先使用应用类加载器加载,,加载类调用loadClass(),最终调用的是父类ClassLoaderloadClass()

双亲委派解释:

这个loadClass() 会先检查是否加载过这个类,如果加载过了,就不用加载,直接返回
如果没有加载过,委托给父加载器加载
如果父加载器和bootstrap加载器都没有找到指定的类,那么调用当前类的findClass()来完成类加载

在这里插入图片描述

问题:

  • 为什么不直接从bootstrapClassLoader 到 AppClassLoader ,,而要双亲委派:
    95%的代码都是 AppClassLoader,如果一个类会被使用很多次,如果是从BootstrapClassLoader --> AppClassLoader ,每次加载都会从上到下委托,,如果是双亲委派,他会先通过findLoadedClass()查找是否加载过当前类,如果加载过了,直接使用

  • 为什么要设计双亲委派

    • 沙箱安全机制: 确保核心api库被随意更改
    • 避免类的重复加载: 当父亲已经加载了该类,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
全盘负责委托机制

当一个ClassLoader装载一个类时,除非显示的使用另外一个ClassLoader,该类所依赖及引用的类也由这个ClassLoader载入

自定义类加载器

ClassLoader类 有两个核心方法:

  1. loadClass() : 实现了双亲委派
  2. findClass() : 空方法,,子类实现,加载自己的类
  • 继承ClassLoader 类,
  • 重写loadClass()打破双亲委派机制,
  • 重写 findClass(),如果父类加载器找不到最终会调用自己的 findClass()

在这里插入图片描述

public class MyClassLoaderTest {


    static class MyClassLoader extends ClassLoader{
        /**
         * 要加载的路径
         */
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        /**
         * 读取类文件字节
         * @param name  类的名字
         * @return
         * @throws Exception
         */
        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);
            return  data;
        }


        /**
         * 双亲委派会委托给父类加载器加载,如果父类没有,最终会调用自己的 findClass()
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name,data,0,data.length);
            } catch (Exception e) {
                e.printStackTrace();
            }

            return null;
        }

        /**
         * 重写 loadClass() 打破双亲委派机制
         * @param name
         * @param resolve
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            synchronized (getClassLoadingLock(name)){
                // 是否加载过这个类  ,,加载过直接使用
                Class<?> c = findLoadedClass(name);
                if(c == null){
                    if(name.startsWith("com.cj")){
                        // 自定义classLoader 加载
                        c = findClass(name);
                    }else{
                        // 调用appClassLoader去加载核心类库
                        c = this.getParent().loadClass(name);
                    }
                }

                if(resolve){
                    resolveClass(c);

                }
                return c;
            }

        }
    }

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        MyClassLoader classLoader = new MyClassLoader("I:/test");
        Class<?> claz = classLoader.loadClass("com.cj.User");
        Object o = claz.newInstance();
//        claz.getDeclaredMethod()

    }
}
import com.cj.User;
import jdk.nashorn.internal.ir.CallNode;

import java.io.FileInputStream;
import java.io.IOException;

public class MyClassLoader extends ClassLoader {
    // classLoader加载路径
    private String classPath;

    @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();
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                if (name.startsWith("com.cj")){
                    // com.cj包 调用自己的类加载器
                    c = findClass(name);
                }else{
                    // 其他使用双亲委派
                    c = super.loadClass(name,resolve);
                }

                // 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;
        }
    }

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        // 加载data
        byte[] data = new byte[0];
        try {
            String path = name.replaceAll("\\.", "/").concat(".class");
            FileInputStream fis = new FileInputStream(classPath + "/" + path);
            // 返回有多少字节
            int len = fis.available();

            data = new byte[len];

            fis.read(data);
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // defineClass() 将一个字节数组转换为Class对象
        return defineClass(name,data,0,data.length);

    }


    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        Class<?> clazz = classLoader.loadClass("com.cj.User");
        Object obj = clazz.newInstance();
        System.out.println(clazz.getClassLoader().getClass().getName());
        System.out.println(User.class.getClassLoader().getClass().getName());
    }
}

Tomcat 也就是通过打破双亲委派机制,实现不同war包的独立运行。。每个war包都有自己的ClassLoader实例,加载自己的war中的类。。。 共用的一个ClassLoader类,只是不同的实例。。实例中传入自己的要加载的路径

jsp热加载实现: 创建一个线程去定时监听这个文件是否修改(文件都有modify time,最后更新时间),如果修改了,创建一个新的lassLoader去加载修改后的文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值