Java 关于类加载的一些想法

声明

本人是个菜鸟,不会写整个加载机制的介绍,其实大部分相关的中文博客都是互相转载,我这里只是说一下自己的学习过程中的疑惑,和如何解决的方法,是为了有相同疑惑的人可以少走点路.

疑惑

  • 从类加载器开始说起,双亲委派机制大家都知道,但我一般很想知道为什么,为什么使用这个机制,各种博客大同小异,都是说为了避免重复加载,很多说到这里就没了,这完全是误人子弟,怎么避免重复加载了,这明显就像小学生写作文,光说这个东西怎么怎么好,但就是不说好在哪里.
  • 继续向下,看到了介绍加载器内部的代码,类加载器先从缓存中寻找,找不到,再将加载任务递交给父加载器等等.这里就有一个问题,这个缓存是整个JVM共享的吗,如果是的话,那如何实现的避免重复加载,既然整个缓存是共享的,那我用哪一个加载器不是都一样吗?

解惑

我的疑惑来源于先入为主.什么先入为主了呢?就是双亲这个概念,学习OOP这么久了,对于继承这个特性其实已经是潜意识的了.其实很多博客也说了,但是并没有被注意到,也就是此处的双亲并不是类与类这件的父子关系,三级的类加载器(启动类加载器,扩展类加载器,系统类加载器)他们的负责范围并不是包含的关系,而是各自负责一部分,至于为什么启动类加载器最高级,也许是因为它负责的区域比较重要.

这里的关系我觉得不能用父子来表示,而是用兄弟姐妹会更好.例如,这三个类加载器就像是三兄弟,只是三兄弟的能力有大有小,就像启动类加载器能力最大(只是比喻,并不是说它负责的区域大,而是更重要),扩展类加载器次之,系统类加载器最小.而有事来了,俗话说的好: 天塌下来有高个子顶着,能力越大,责任越大,肯定是启动类加载器先试试,就我的理解应该是这样.

关于避免加载重复的说法,网上还是有很多,但还是那句话,都是抄来抄去,可怕的是很多抄的根源就错了.错在哪里?

我先是自己定义了一个Integer,当我在同包或放在同一类文件下使用时,发现可以加载,是通过系统类加载器加载的,而java的java.lang.Integer没有被启动类加载器加载,这虽然困惑了我一会,但是很快就明白了,判定类加载是类的权限定名,不是单个类的名字.

那么问题来了,如果我创建一个权限定名和java.lang.Integer一样的类,会怎样呢?此时,我创建java.lang这个包在我的classpath下,然后调用,此时你会发现调用的并不是你的类,而是java的类.

这才是双亲委派的意义所在,并不是简单避免重复加载,更重要的是防止更改系统本身的类,防止像骇客一般的行为,而我们正常在自己的包中创建一个相同类名的类,是可以调用的.

还有一个问题,就是网上的一道类加载的面试题,能不能自己创建一个java.lang.xx的类,按照我们上面的说法,这是不行的,但有很多答案说,虽然明面上不行,但是可以通过实现一个自定义类加载器来创建这个类.这就是我说的错误的根源.

哪里错了,我想问,自定义类加载器是独立的吗?它是不是要继承ClassLoad类,我们先从道理上来说,说上面话的人,你认为sun/oracle会蠢到想不到这一点吗?毕竟是类加载,这么重要的部分,会在已经Java11了还留给你吗?
其次从代码上将,看一下ClassLoad中定义类的命名检测方法:源代码

private ProtectionDomain preDefineClass(String name,
                                            ProtectionDomain pd)
    {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);

        // Note:  Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
        // relies on the fact that spoofing is impossible if a class has a name
        // of the form "java.*"
        if ((name != null) && name.startsWith("java.")) {
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf('.')));
        }
        if (pd == null) {
            pd = defaultDomain;
        }

        if (name != null) checkCerts(name, pd.getCodeSource());

        return pd;
    }

看这一行
if ((name != null) && name.startsWith("java.")) {
你能加载以java开头的方法吗?
可能有人会说,我可以重写这个方法,但是对不起,这是私有方法,其次调用这个方法的方法defineClass是一个final方法,是不允许重写的.
也就是说,我们是不能写自己的java.lang.xx方法的

猜你喜欢

转载自blog.csdn.net/qq_36865108/article/details/85238913
今日推荐