Java 关于类加载的一些想法

本文深入探讨Java类加载机制中的双亲委派模型,解释其如何避免类的重复加载及保护核心类库免受篡改,同时澄清常见误解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

声明

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

疑惑

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

解惑

  • 首先,要确定两个类(Class对象)相同的的一个必要条件,就是这两个类必须都是由同一个类加载器加载进虚拟机的,之所以如此规定,是为来保证类本身在虚拟机中的唯一性;否则即使两个类来自同一个字节码文件,也不算一个类

以下引自<深入理解Java虚拟机>

  • 类加载器

    • 启动类加载器(Bootstrap ClassLoader),C++实现,是虚拟机的一部分,这个类负责将存放在<JAVA_HOME>\lib目录中的,或者被参数-Xbootclasspath指定的路径的,并且被虚拟机识别的(按名字识别,名字不符合,放在lib下也没用),Java程序无法调用这个启动类加载器
    • 扩展类加载器 : Java语言实现,继承java.lang.ClassLoader,负责<JAVA_HOME>\lib\ext目录下的,或者被java.ext.dirs变量所指定的路径下的所有类库,程序可以直接调用这个
    • 应用程序类加载器,使用ClassLoader.getSystemClassLoader()方法获得,一般称为系统类加载器,负责加载用户类路径上的指定的类库,如果没有自定义的类加载器,默认是这个;
  • 双亲委派机制模型要求除了启动类加载器以外,所有类加载器都应当有自己的父类加载器(组合实现,不是继承),工作过程是这样的,如果一个类加载器收到类加载请求,自己不会去加载这个类,而是交给父类加载器加载,直到启动类加载器,如果父类加载器在自己的搜索范围没有找到所需要的类,子加载器自己尝试加载;

  • 这样的机制也就避免了重复加载

  • 一些实验

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

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

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

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

从代码上将,看一下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方法的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值