学习记录392@深入理解JVM类加载机制

本文详细介绍了JVM的类加载过程,包括加载、验证、准备、解析和初始化阶段。讲解了类加载器的层次结构,如引导类加载器、扩展类加载器和应用程序类加载器,以及自定义类加载器的实现。重点讨论了双亲委派机制,解释了其设计原因和好处,包括沙箱安全机制、避免类重复加载以及为何向上委托。此外,还展示了如何通过自定义类加载器打破双亲委派机制。
摘要由CSDN通过智能技术生成

感谢某视频,这里就将自己学习后做的笔记截图上来了,因为用md写的好看一点,最后附上了文档内容。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接上
在这里插入图片描述
接上
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接上
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接上
在这里插入图片描述
接上
在这里插入图片描述
在这里插入图片描述
接上
在这里插入图片描述
接上
在这里插入图片描述
在这里插入图片描述

从JDK源码深入理解JVM的类加载机制

类加载过程

  • 加载
    • 将class文件读取到JVM内存中,采用的懒加载模式,只有使用到类时才加载,比如调用main方法,new对象时,加载阶段会在内存中生成Class对象,作为方法区这个类的各种数据访问入口。
  • 验证
    • 验证字节码文件的正确性。
  • 准备
    • 给类的静态变量分配内存,并赋予初始值。
  • 解析
    • 符号引用替换为直接引用,这就是静态链接过程,如果时运行期间将符号引用转化为直接引用,则是动态链接。
  • 初始化
  • 对类的静态变量初始化为指定的值,并执行静态代码块,这就是为什么静态代码块比构造方法先执行的原因。

类加载器

  • 引导类加载器
    • 在JVM启动时自动加载的,是用C++写的程序,加载JRE-LIB文件夹下面的一些支撑Java程序运行的jar包,比如rt.jar;于此同时引导类加载器会自动创建launcher类,用于加载扩展类加载器和应用程序类加载器,且分清楚两者的上下级关系,上级是扩展类加载器,下级是应用程序类加载器。
  • 扩展类加载器
    • 加载JRE-LIB-EXT文件夹下面的一些扩展jar包,如sunjce_provider.jar。
  • 应用程序类加载器
    • 加载在类路径下的自己写的类
  • 自定义类加载器

双亲委派机制

什么是双亲委派机制

当加载某个类时,先交给应用程序类加载器,看是否已经加载过,如果没有加载过,交给扩展类加载器,看是否已经加载过,如果没有加载过,交给引导类加载器,看是否已经加载过,如果没有加载过,就尝试加载,如果加载失败,交给扩展类加载器,尝试加载,如果加载失败,交给应用程序类加载器,尝试加载,如果失败就失败了,如果成功就成功了。

核心源码
//ClassLoader
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 {
                    //没有加载过,交给上级类加载器去加载
                    //parent表明某个加载器有个属性是parent,不要理解为这几个类加载器是继承关系
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        //没有上级类加载器,扩展类加载器的parent就为null了,就直接交给引导类加载器处理,本地方法,无法查看
                        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();
                    //自己尝试加载,调用UrlClassLoader类中的findClass方法
                    //这是个空实现,扩展类加载器时,因为继承的是UrlClassloader的这个方法
                    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;
        }
    }
//应该被实现类重写这个方法,然后执行,这里也就是要执行UrlClassLoader的findClass,自定义类加载器的化就是重写这个方法即可!
//为什么是执行rlClassLoade的findClass呢?因为扩展类加载器没有findClass方法,而扩展类加载器是继承UrlClassLoader的,因此就是执行UrlClassLoader的findClass方法了
/**
     * Finds the class with the specified <a href="#name">binary name</a>.
     * This method should be overridden by class loader implementations that
     * follow the delegation model for loading classes, and will be invoked by
     * the {@link #loadClass <tt>loadClass</tt>} method after checking the
     * parent class loader for the requested class.  The default implementation
     * throws a <tt>ClassNotFoundException</tt>.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class could not be found
     *
     * @since  1.2
     */
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
//URLClassLoader类中
//就是找那个类,然后加载进JVM内存中
//就是在执行加载、验证、准备、解析、初始化的过程
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 {
                                //加载进JVM内存中
                                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;
    }
为什么设计双亲委派机制
  • 沙箱安全机制
    • 假如自定义一个类和JDK的类的包名和类名完全一致,但是内部的方法不一样,这个时候运行程序就会报错,因为上级的类加载器会加载到这个类,但是没有自定义的方法。比如自定义一个java.lang.String,但是内部有一个main方法,并且运行,就会报错,因为这个类会被引导类加载器加载,但本身String类时没有main方法的。
    • 好处:防止恶意串改JDK最基本的类,要不然你就可以自己写一个类,覆盖原始的类,别人运行就会报错的。
  • 避免类的重复加载
    • 很好理解,就是上级加载了,下级就不会加载了。
  • 为什么要向上委托而不是向下委托
    • 比较好的解释是,因为大部分程序的类都是自定义的,就可以做到大部分类由应用程序类加载器加载,要不然每次都先从上级开始,委派给下级,这样会造成很大的浪费。
  • 为什么要一层层委托,然后从上层开始如果加载失败才交给下层加载,而不是下层开始能加载就加载,不能加载就再给上层加载
    • 上面已经确认向上委托的好处,如果不向上委托,就是当需要加载某个类是,下级能加载,就加载,那不就破坏杀向安全机制了么,比如自定义一个java.lang.String,应用程序类加载器能加载,就加载了,就可以篡改JDK以下原始的类了,造成很大的风险。
自定义类加载器
自定义类加载器代码实现
package cn.cdqf.mydmsjportal.test;


import java.io.FileInputStream;
import java.io.IOException;
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 IOException {
            name=name.replaceAll("\\.","/");
            FileInputStream fileInputStream = new FileInputStream(classPath + "/" + name+".class");
            int len= fileInputStream.available();//获取文件大小
            byte[] bytes = new byte[len];
            fileInputStream.read(bytes);
            fileInputStream.close();
            return bytes;
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] bytes = loadByte(name);
                return defineClass(name,bytes,0,bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }
    }


    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader("D:\\qianfeng\\JavaEE2007\\self-study\\");
        try {
            //不要写成findClass!!!
            Class<?> aClass = myClassLoader.loadClass("cn.cdqf.mydmsjportal.test.Test");
            Object newInstance = aClass.newInstance();
            Method method = aClass.getMethod("test");
            method.invoke(newInstance,null);
            //打印使用的类加载器
            System.out.println(aClass.getClassLoader().getClass().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
测试

建一个文件夹,放入Test.class,然后运行程序,得到的结果如下:

在这里插入图片描述

测试自定义类加载器
sun.misc.Launcher$AppClassLoader

为什么是AppClassLoader

这就是因为双亲委派机制,因为程序中的类路径下Test.class存在,就由父级加载器加载了,为什么自定义类加载器的父级类加载器是AppClassLoader?一方面是为了遵循双亲委派机制,另一方面从源码的角度来看:

    //传入的就是系统类加载器,而系统类加载器默认的就是应用程序类加载器

    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }
    
    
   private ClassLoader(Void unused, ClassLoader parent) {
       //指定父级类加载器
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }


//获取系统类加载器,注意initSystemClassLoader();,初始化系统类加载器
@CallerSensitive
    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }


//初始化系统类加载器,注意scl = l.getClassLoader();
private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
                try {
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            sclSet = true;
        }
    }

//获得类加载器
public ClassLoader getClassLoader() {
        return this.loader;
    }

//初始化会调用默认无参构造方法,loader赋值为AppClassLoader,搞定!
public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            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);
        }
    }
打破双亲委派机制

以上不让父级类加载器加载Test的方法有两个,一个是在类路径下删除,另外一个方法就是打破双亲委派机制,双亲委派机制的核心在loadClass方法中,因此重写即可!

package cn.cdqf.mydmsjportal.test;


import java.io.FileInputStream;
import java.io.IOException;
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 IOException {
            name=name.replaceAll("\\.","/");
            FileInputStream fileInputStream = new FileInputStream(classPath + "/" + name+".class");
            int len= fileInputStream.available();//获取文件大小
            byte[] bytes = new byte[len];
            fileInputStream.read(bytes);
            fileInputStream.close();
            return bytes;
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] bytes = loadByte(name);
                return defineClass(name,bytes,0,bytes.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();
                    //注意

                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
//                   如果不是是要自己加载的,就让父级去加载
//                    注意不能全部让自定义类加载器去加载,因为有些父级比如object是需要提前加载的
//                    这些必须用父级加载器去加载
//                    否则会报错:java.io.FileNotFoundException: D:\qianfeng\JavaEE2007\self-study\java\lang\Object.class (系统找不到指定的路径。)
                    if (!name.startsWith("cn.cdqf.mydmsjportal.test")){
                        c=this.getParent().loadClass(name);
                    }else {
                        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;
            }
        }
    }


    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader("D:\\qianfeng\\JavaEE2007\\self-study\\");
        try {
            //不要写成findClass!!!
            Class<?> aClass = myClassLoader.loadClass("cn.cdqf.mydmsjportal.test.Test");
            Object newInstance = aClass.newInstance();
            Method method = aClass.getMethod("test");
            method.invoke(newInstance,null);
            //打印使用的类加载器
            System.out.println(aClass.getClassLoader().getClass().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
测试

这样就是直接使用自定义类加载器啦

测试自定义类加载器
cn.cdqf.mydmsjportal.test.MyClassLoaderTest$MyClassLoader

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值