jvm类加载器、委派双亲模式、自定义类加载器

jvm类加载器、委派双亲模式、自定义类加载器,第1张

jvm类加载器、委派双亲模式、自定义类加载器 一、类加载

当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、验证、准备、初始化4个步骤来对该类进行初始化。通过java命令执行代码流程如下:

其他loadClass的类加载过程有几步:

加载:加载class文件,使用到类的时候才会加载,列如调用main方法或者new对象等等

验证:校验字节码文件的正确性

准备:给类的静态变量分配内存,并赋予默认初始值

初始化:对静态变量进行初始化指定值,执行静态代码块

类加载到方法区中主要是包含了运行时常量、类型信息、字段信息、类加载器的引用、对应的class实例引用等相关信息

二、类加载器

java有如下几种类加载器:

引导类加载器:负责加载支撑jvm运行的位于JRE的lib目录下的核心类库,例如rt.jar、charsets.jar

扩展类加载器:负责加载支撑jvm运行的位于JRE的lib目录下的ext扩展目录中的jar包

应用程序类加载器:负责加载classPath路径下的类包,通常就是加载我们自己写的类

自定义类加载器:负责加载用户指定目录下的类或包

类加载器初始化过程:首先会创建jvm启动实例sun.misc.Launcher。Launcher初始化使用的是单例设计模式,从而保证jvm虚拟机内存中只会存在一个Launcher实例。在Launcher内部构造方法会创建两个类加载器,分别是ExtClassLoader(扩展类加载器)、AppClassLoader(应用类加载器)。Launcher构造方法代码如下

//构造方法
 public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
			//构造扩展类加载器,在构造的过程会把对应的父加载器设置为null
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
			//构造应用类加载器,在构造的过程会把对应的父加载器设置为ExtClassLoader
			//AppClassLoader加载器一般都是用加载我们自己写的应用程序
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }        
		。。。。。//省略
    }
三、双亲委派模式

jvm的类加载器是有对应的亲子模式如图下:

这里的类加载其实就是一个双亲委派机制。加载某个类是会先委派父加载器寻找目标类,如果找不到则会再委派上一层父加载器寻找目标类。当所有父加载器都没有找到对应的类时,则会在自己类加载路径中查找并加载类 。

列如加载一个Test类,最开始是找到应用程序类加载器,会在应用程序类加载器中先委托扩展类加载器,在扩展类加载器中会委托引导类加载器。如果Test类在引导类加载器没有找到对应的类,则会向下退回加载Test,当扩展类加载器接收到回退,则会在自己的类加载器路径中加载,在扩展类加载器没有加载到Test,则会回退到应用程序类加载器,于是会在应用程序类加载器的路径下找到对应的Test类,找到后则会进行加载

为什么会设计双亲委派机制?

  1. 沙箱安全机制:防止核心API库被随意篡改
  2. 避免类重复加载:当父类已经加载了该类时,没有必要在子类再进行一次ClassLoader再加载一次,保证被加载类的唯一性
四、自定义类加载器

自定义类加载器需要实现java.lang.ClassLoader类,该类由两个核心的方法,一个是loadClass(String name, boolean resolve)该方法实现了委派双亲模式,另外一个是findClass方法默认实现是空的方法。而实现自定义类加载器则需要重新loadClass方法。代码如下:

package com.cn.jvm;

import java.io.FileInputStream;
import java.lang.reflect.Method;


public class MyClassLoaderTest {

    
    static class MyClassLoader extends ClassLoader {

        private String classPath;

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

        
        private byte[] loadByte(String name) throws Exception {
            name = name.replace(".", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

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

        
        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) {
                    //非自定义类走委派双亲模式
                    if (!name.startsWith("com.cn.jvm")) {
                        c = this.getParent().loadClass(name);
                    } else {
                        c = findClass(name);
                    }
                    long t0 = System.nanoTime();
                    // 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;
            }
        }

    }

    public static void main(String[] args) throws Exception {
//        User user = new User();
        //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        //D盘创建 test/com/cn/jvm几级目录,将User类的复制类User1.class丢入该目录
        Class clazz = classLoader.loadClass("com.cn.jvm.User1");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }


}
package com.cn.jvm;


public class User {

    private Long id;

    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void sout() {
        System.out.println("=======自己的加载器加载类调用方法=======");

    }
}

以上是最近学习jvm类加载及类加载器的相关记录如有错误请多多指教。如有侵权请联系。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5636598.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存