JVM 之 类的装载机制

看到网上有一道面试题:能不能装载自定义的 java.lang.String?
答案是否定的,我们能自定义一个java.lang.String,但是加载不进来。

我相信很多人在网上看到这样的答案“可以,但在应用的时候,需要用自己的类加载器去加载”。这个回答是错误的,现在我们来分析一下jvm在装载一个类的时候,是如何进行的。

双亲委派模型

当虚拟机接受到一个类的加载请求时,它将这个加载请求委派给父类加载器进行加载,只有当父类加载器自己无法完成加载请求时,子类加载器才会尝试自己加载。

那么系统自带的加载器有哪些呢?

1.启动类加载器BootstrapClassLoader:

是嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负载加载JAVA_HOME/lib下的类库,启动类加载器无法被应用程序直接使用。

2.扩展类加载器Extension ClassLoader:

该加载器器是用JAVA编写,且它的父类加载器是Bootstrap,是由sun.misc.Launcher$ExtClassLoader实现的,主要加载JAVA_HOME/lib/ext目录中的类库。开发者可以这几使用扩展类加载器。

3.统类加载器App ClassLoader:

系统类加载器,也称为应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文件。它的父加载器为Ext ClassLoader。

各个类加载器之间是组合关系,并非继承关系。

双亲委托模式的优点

1.避免重复加载。当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
2.安全性。如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患。

重写String

回到上面的面试题,我重写了一个java.lang.String的类

package java.lang;  

public class String {  

    public static void main(String[] args) {  
        System.out.println("Hello String");  
    }  

} 

运行之后会抛一个异常:
错误: 在类 java.lang.String 中找不到主方法, 请将主方法定义为:
public static void main(String[] args)

因加载某个类时,优先使用父类加载器加载需要使用的类。如果我们自定义了java.lang.String这个类,加载该自定义的String类,该自定义String类使用的加载器是AppClassLoader。

根据优先使用父类加载器原理,AppClassLoader加载器的父类为ExtClassLoader,所以这时加载String使用的类加载器是ExtClassLoader,但是类加载器ExtClassLoader在jre/lib/ext目录下没有找到String.class类。

然后使用ExtClassLoader父类的加载器BootStrap,父类加载器BootStrap在JRE/lib目录的rt.jar找到了String.class,发现已经加载过了,于是不会再加载我们自定义的类。

打破双亲委托机制

有些人看到这,就会想,那我们自定义一个类加载器,但是不实现双亲委托机制就好了,强行加载,就算重复了也不管。但是你会发现也不会加载成功,具体就是因为针对java.*开头的类,jvm的实现中已经保证了必须由bootstrp来加载。并且这个方法被final修饰的,是改不了的。

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MakeContral/article/details/78374698
个人分类: JVM
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭