Java类加载深度剖析-大白话

文章详细探讨了Java类加载的入口,包括BootstrapClassLoader、AppClassLoader和ExtClassLoader之间的关系。它解释了为何ExtClassLoader的父加载器显示为null,以及如何打破双亲委派机制。同时,文章强调了确定类对象唯一性的条件,即类加载器类型和内存地址必须相同。
摘要由CSDN通过智能技术生成

1.类加载的入口

sum.misc.Launcher:
它是一个java虚拟机的入口应用。
这里我们可以发现BootstrapClassLoader的加载路径:System.getProperty("sun.boot.class.path")

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;

    public static Launcher getLauncher() {
        return launcher;
    }

    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
        //拓展类加载器
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
        //应用类加载器,将拓展类加载器作为应用类加载器的parent
            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);
        }

    }

2.AppClassLoader、ExtClassLoader、BootstrapClassLoader的血脉渊源

上面代码片段java虚拟机的入口应用实例化了ExtClassLoader和AppClassLoader,那么我们看看实例化这两个ClassLoader的时候有没有对它们有额外的设置,比如双亲委派中的parent:
AppClassLoader:
这里也可以看出AppClassLoader的加载路径System.getProperty("java.class.path");

public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            final String var1 = System.getProperty("java.class.path");
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
                public Launcher.AppClassLoader run() {
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    return new Launcher.AppClassLoader(var1x, var0);
                }
            });
        }

new Launcher.AppClassLoader(var1x, var0);var0是ExtClassLoader(1中提及过),一路追踪上去,追踪到了顶层父类ClassLoader的方法中:

// The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;
    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            assertionLock = this;
        }
    }

之前一直说ExtClassLoader 的父类加载器是null,那么我们也遵循同样的思路一探究竟:

static class ExtClassLoader extends URLClassLoader {
        private static volatile Launcher.ExtClassLoader instance;

        public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
            if (instance == null) {
                Class var0 = Launcher.ExtClassLoader.class;
                synchronized(Launcher.ExtClassLoader.class) {
                    if (instance == null) {
                        instance = createExtClassLoader();
                    }
                }
            }

            return instance;
        }

注意,跟AppClassLoader一样的逻辑,但这里的ClassLoader是null,这也就解释了日常输出的ExtClassLoader 的父类加载器是null。

public ExtClassLoader(File[] var1) throws IOException {
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }

3.ExtClassLoader究竟是不是孙大圣

既然ExtClassLoader父类加载器为null,那么双亲委派机制中的ExtClassLoader委托BootstrapClassLoader加载类又是怎么一回事呢?jdk从加载类的逻辑去实现这个功能:
在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 {
                //有父类加载器就使用父类加载器加载,例如AppClassLoader和我们不正确地编写自定义类加载器(误将AppClassLoader作为父类加载器)
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    //针对ExtClassLoader
                        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;
        }
    }

4.为什么自定义类加载器的父类加载器是AppClassLoader呢?

那就从构造方法的执行顺序中说起了,我们自定义的ClassLoader会继承ClassLoader,执行自定义的ClassLoader之前会执行ClassLoader的构造方法,一般是无参构造方法。

    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<>();
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            assertionLock = this;
        }
    }

对比可得我们重点关注getSystemClassLoader()方法
追踪:
initSystemClassLoader()->

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

在这里插入图片描述
我们之前提及过this.loader被设置为AppClassLoader,因此自定义类加载器的父类加载器是AppClassLoader。

5.我们应该如何打破双亲委派机制呢?

与其说打破双亲委派机制,不如说如何将我们要加载的class与我们自定义的类加载器建立联系,而不是委派AppClassLoader加载。
我们平常自定义类加载器的时候可能在不经意的时候在重写loadClass方法的时候调用了getParent().loadClass(name);这就导致了使用AppClassLoader去加载类。

public abstract class ClassLoader {
@CallerSensitive
    public final ClassLoader getParent() {
        if (parent == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Check access to the parent class loader
            // If the caller's class loader is same as this class loader,
            // permission check is performed.
            checkClassLoaderPermission(parent, Reflection.getCallerClass());
        }
        return parent;
    }
    }

那么自定义类加载器和类是如何建立联系的呢?这里先放个彩蛋:
注意this(每new一个classLoader都会调这个方法将自身设置进去)

 // The "default" domain. Set as the default ProtectionDomain on newly
    // created classes.
    private final ProtectionDomain defaultDomain =
        new ProtectionDomain(new CodeSource(null, (Certificate[]) null),
                             null, this, null);

重点关注protectionDomain = preDefineClass(name, protectionDomain);Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);前者封装classLoader,后者将classLoader和类对象建立关系

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

每个classLoader加载类的时候,最终都会调用这个方法Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
每个classLoader都对应一个defaultDomain
在这里插入图片描述
最后调用本地方法生成class对象并且与classLoader建立联系。

    private native Class<?> defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);

综上所述:要打破双亲委派机制,关键点是想办法让真正的ProtectionDomain 与自定义的类加载器关联,要做到这一点,就不能在重写loadClass方法中调用getParent().loadClass(name);方法。而且,往往会存在一种幻觉,new 自定义类加载器的时候ProtectionDomain 是封装了自定义类加载器,但是deBug到protectionDomain = preDefineClass(name, protectionzDomain);的时候发现是AppClassLoader或者其他上层加载器,那就是因为在重写loadClass方法中调用getParent().loadClass(name);方法

6.如何保证同class对象是唯一的

参考地址
jvm使用包路径的类名和类加载器唯一确定了一个类
怎么说呢,在每次调用loadClass方法将要类的时候,内部会首先调用Class<?> c = findLoadedClass(name);JDK官方源码注释:First, check if the class has already been loaded,中译:首先,检查这个类是否已经加载过了,如果返回的引用不为空,就将这个class对象返回去,同时,这个方法最终会调用本地方法,这个本地方法的作用就如上所述:jvm使用包路径的类名和类加载器唯一确定了一个类。

7.确定是否是同一个类对象取决于类加载器的类型还是说在类型相同的基础上还得是同一个具体对象(内存地址要相同)

答案:确定是否是同一个类对象取决在类加载器的类型相同的基础上还得是同一个具体对象(内存地址要相同),验证思路:自定义类加载器,加载过程不得调用父加载器,new两个类加载器和使用同一个类加载器去加载这个类,如果class对象相同说明判定一个类对象是否是同一个门槛并不高,仅仅取决于是不是同样的类加载器类型,反之说明门槛稍高:得是同样的类加载器类型并且是同一个类加载器对象才可以。

8.心得推论

重新认识一个类对象和改类对象类型的实例对象,java万物皆对象,平常所说的类模板衍生出实例对象,其实这个类模板就是类对象,而且在jvm中只有一个,朦胧之中就可以将jvm将class文件加载到内存落地生成类对象(方法区-公共-模板-唯一)作为入口联系起来。

卷积神经网络(CNN)是一种常用于图像处理和模式识别的深度学习模型。它的设计灵感来自于生物学视觉皮层的神经元结构。为了用通俗的语言解释CNN,我们可以用以下方式来理解它: 假设你要识别一张猫的图片。首先,你的大脑会将这张图片的像素点转化成一系列数字,并且记录下它们的位置和颜色。然后,大脑会将这些数字输入到“卷积层”。 在卷积层,会有很多个“过滤器”。这些过滤器可以视为一双眼睛,它们通过抓取图片的不同特征来帮助你识别物体。每个过滤器都在图片上滑动并计算一个“特征图”,这个特征图描述了所检测到的特定特征。例如,一个过滤器可以检测到猫的边缘,另一个可以检测到猫的颜色等等。当所有过滤器完成计算后,就会得到一些不同的特征图。 在“池化层”,每个特征图都会被压缩,去除一些不重要的信息。这样可以减少需要计算的数据量,并且使得特征更加鲁棒和不变形。 最后,在全连接层,所有的特征图都被连接起来,形成一个巨大的向量。接下来,这个向量会通过一些神经元节点,最终输出识别结果,也就是“这是一张猫的图片”。 CNN的一个重要特点是参数共享,这意味着每个过滤器会在整个图片上进行计算,而不仅仅是某个局部区域。这样可以减少需要计算的参数量,提高训练速度和模型的泛化能力。 总结一下,CNN通过卷积层来提取图像的特征,并通过池化层降低特征的维度。最后,通过全连接层将所有特征连接起来并输出结果。这种结构使得CNN非常适合于图像分类和识别任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fire king

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值