jvm之类加载机制解析

本文详细介绍了Java程序的运行方式以及JVM的工作原理,重点讲解了类加载器的类型和职责,包括引导类加载器、扩展类加载器和应用类加载器。文章阐述了双亲委派机制的运作过程及其优点,如安全性和避免重复加载。同时,讨论了如何通过自定义类加载器打破双亲委派机制,以满足特定场景的需求,如Tomcat容器的做法。

一、程序如何运行的

首先,我们都知道,java程序写好之后,打成jar包或者war,然后丢到服务器上启动运行,但是,到底是怎么运行的呢?java虚拟机在这里面怎么工作的?又是谁推动jvm的运转工作?

jvm底层是使用C++语言实现的,首先C++会帮我们创建jvm,然后把程序代码丢到jvm内存中,并且调用main方法,用以启动程序。

而jvm调用一些java类的方法时,是需要先进行类的加载的,只有被加载好的类才能被使用。

那么类加载,需要一个专门用来加载类的工具,我们称之为类加载器。

  1. 首先C++帮我们创建jvm
  2. C++创建一个引导类加载器,它是最顶层的类加载器
  3. 然后C++调用java的代码创建jvm的启动器Launcher的实例  Launcher有引导类加载器加载
  4. 创建Launcher实例过程中,会创建两个加载器:ExtClassLoad 和 AppClassLoad 加载器
  5. 然后使用AppClassLoad加载器按照双亲委派机制加载需要加载的类,之后调用main方法启动。
  6. java程序执行结束,jvm销毁。

二、类加载器

java类的加载是使用类加载器加载,那么类加载器有哪些?分别负责加载哪些类?如何工作的?

类加载器有哪些?分别负责加载哪些类?

  1. 引导类加载器(BootStrapClassLoader):负责加载支撑jvm运行的位于jre的lib目录下的rt.jar,charset.jar等核心类库中类
  2. 扩展类加载器(ExtClassLoader):负责加载支撑jvm运行的位于jre的lib目录下的ext扩展目下的核心类库中的类
  3. 应用类加载器(AppClassLoader):负责加载应用程序中自己创建类
  4. 自定义类加载器:负责加载自定义路径下的类

类加载过程分为:加载->验证->准备->解析->初始化

类加载器的创建

public class Launcher {
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
    // 静态变量 该类在被加载的初始化阶段 被实例化 调用构造方法  单例模式
    private static Launcher launcher = new Launcher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;

    //jvm调用此方法 获取实例
    public static Launcher getLauncher() {
        return launcher;
    }

    //构造方法
    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            //创建ExClassLoader扩展类加载器
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            /**
            ** 创建AppClassLoader应用类加载器 上一步创建的扩展类加载器被作为父加载器传入        
            ** AppClassLoader的构造器中
            ** 创建完成后  赋值给loader变量
            **/
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                    ;
                } catch (InstantiationException var6) {
                    ;
                } catch (ClassNotFoundException var7) {
                    ;
                } catch (ClassCastException var8) {
                    ;
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

    public ClassLoader getClassLoader() {
        //返回的是AppClassLoader加载器
        return this.loader;
    }
}

首先C++创建引导类加载器,

然后由引导类加载器加载Launcher,在加载Launcher的初始化阶段 给launcher静态变量赋值时,调用Launcher的构造方法(由此可见,类的静态代码块先于构造方法被调用)

在构造方法中创建两个构造器:ExtClassLoader,AppClassLoader。

调用launcher.getClassLoader()方法获取类加载器,获取到加载器后 进行类的加载工作。

分析源码可见:所有的加载器都继承ClassLoader加载器

类加载机制默认遵循双亲委派机制且是懒加载。

三、双亲委派机制

类加载器在加载类时默认遵循双亲委派机制

双亲委派机制

一般一个类从AppClassLoader开始,在AppClassLoader中先找是否已经加载过,如果已经加载过则直接返回,

若未加载过,则丢给父加载器ExtClassLoader加载,ExtClassLoader先找自己是否加载过,

若未加载过则丢给父加载器BootStrapClassLoader引导类加载器,BootStrapClassLoader会在自己的加载路径中找,找不到

则再回丢给ExtClassLoader加载,ExtClassLoader在自己的加载路径找  找不到回丢给AppClassLoader加载,AppClassLoader在自己加载路径中找到了 则加载成功。

双亲委派机制的优点:

  1. 沙箱安全:加入你自定义创建一个类String  全类名java.lang.String。在加载的时候,会最终丢给最顶层的引导类加载器加载,而加载器发现自己已经加载过了(加载的是jre下lib目录下的rt.jar核心库的类,并不是你创建的String类),只要全类名相同,加载器就认为加载过了,直接返回。这就防止有人对java的核心类库的类进行篡改。
  2. 避免重复加载:在整个加载过程中,一个类被任何一个加载器加载过了,就不会在此被加载,避免了重复加载。

四、实现原理

类加载是由各个加载器加载的,每个加载类都有两个核心方法 loadClass() 和findClass();

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先 查看自己是否已经加载过当前类 已加载过 直接返回
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                //没有加载过
                long t0 = System.nanoTime();
                try {
                 
                    if (parent != null) {
                        //委托父加载器进行加载
                        c = parent.loadClass(name, false);
                    } else {
                        //没有父加载器了 当前加载器为ExtClassLoader  找BootStrapClassLoader加载器加载
                        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;
        }
    }

加载器会一直向上委托父加载器进行加载,最顶层的加载器加载失败,才会向下传递由子加载器加载,为双亲委派机制就体现在loadClass(String name, boolean resolve)方法中

五、如何打破双亲委派机制

上文已经说过双亲委派机制就是在loadClass()方法实现的,那么我们如何打破双亲委派机制呢?

打破双亲委派,我们只需要在类加载时,不在委托父加载器就可以了,就那么简单,如何实现?

自定义加载器,重写loadClass()就可以了。

/**
 * @description: 自定义类加载器  父加载器默认AppClassLoader 应用类加载器
 * @author: zhangbing
 * @create: 2020-06-09 13:59
 **/
public class MyClassLoader extends ClassLoader {

    private String classPath;

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


    /**
     * 重写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) {
                //当前加载器没有加载过
                long t0 = System.nanoTime();
                if (!name.startsWith("main.jvm.classLoad")) {
                    //jdk的类 不允许打破双亲委派机制
                    c = getParent().loadClass(name);
                } else {
                    //自定义的类 使用自定义加载器加载
                    c = findClass(name);
                }
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();

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

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] bytes = loadByte(name);
        return defineClass(name, bytes, 0, bytes.length);

    }

    private byte[] loadByte(String fullPathname) {
        String path = fullPathname.replaceAll("\\.", "/");
        byte[] bytes = null;
        try {
            FileInputStream fileInputStream = new FileInputStream(classPath + "/" + path + ".class");
            int available = fileInputStream.available();
            bytes = new byte[available];
            fileInputStream.read(bytes);
            fileInputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();

        } catch (IOException e) {
            e.printStackTrace();
        }

        return bytes;
    }

    
    public static void main(String[] args) {
        //创建一个自定义加载器 传入自定义类路径
        MyClassLoader myClassLoader = new MyClassLoader("D:/workspace/test/");
        try {
            Class<?> aClass = myClassLoader.loadClass("main.jvm.classLoad.User", false);
            System.out.println("使用的类加载器:" + aClass.getClassLoader());
            Object o = aClass.newInstance();
            Method testMethod = aClass.getDeclaredMethod("testMethod", null);
            testMethod.invoke(o, args);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

代码运行结果:

打破双亲委派机制成功!!!

我们熟知的tomcat内部就没有使用默认的双亲委派机制,内部打破了双亲委派机制。

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值