面试官:不懂双亲委派机制还不进来看看?

741 阅读3分钟

1. 前言

面试官:了不了解类加载器?

小王:支支吾吾后说了一句:"了解一点"......

面试官:那就来说说类加载器有几种分类?

小王:2种

面试官:具体都有哪些类加载器呢?

小王:引导类加载器、扩展类加载器、系统类加载器

面试官:这几种类加载器对应的职责是干什么的?

小王:......

面试官:来说说什么是双亲委派机制?

小王:......

面试结束后,小王心里特别难过:"为什么平时不用的东西,在面试中被频繁问到?这次的面试估计是没戏了"为了下次能够顺利通过面试,到家后的小王立刻打开了电脑,开始查阅相关资料。

2.类加载器

2.1 类加载器分类

类加载器一般分为:引导类加载器和自定义类加载器

2.1.1 引导类加载器

引导类加载器由C和C++实现,嵌入在JVM中

2.1.2 自定义加载器

自定义类加载器由JAVA代码实现,继承ClassLoader,常见的自定义类加载器有扩展类加载器:ExtClassLoader和系统类加载器:AppClassLoader

2.2 类加载器职责

2.1.1 引导类加载器

引导类加载器用于加载JAVA中的核心类库,如java.lang.String

// 获取加载String的类加载器:null
// 引导类加载器用于加载JAVA的核心类库
ClassLoader classLoader3 = String.class.getClassLoader();
System.out.println("加载String的类加载器:" + classLoader3);

引导类加载器由C和C++实现,因此获取到的结果为null

2.1.2 自定义加载器

2.1.2.1 扩展类加载器

扩展类加载器用于加载jre/lib/ext下面的类

// 获取扩展路径
String extDir = System.getProperty("java.ext.dirs");
String[] extDirs = StringUtils.delimitedListToStringArray(extDir, ":");
for (String dir : extDirs) {
    System.out.println("扩展路径:" + dir);
}

// 获取加载CalendarData_en_SG的类加载器:sun.misc.Launcher$ExtClassLoader@2eafffde
// 用于加载扩展路径下面的类
ClassLoader classLoader2 = CalendarData_en_SG.class.getClassLoader();
System.out.println("加载CalendarData_en_SG的类加载器:" + classLoader2);
2.1.2.2 系统类加载器

系统类加载器用于加载自定义的类

// 获取加载ClassLoaderTest的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
// 系统类加载器用于加载用户自定义的类
ClassLoader classLoader1 = ClassLoaderTest.class.getClassLoader();
System.out.println("加载ClassLoaderTest的类加载器:" + classLoader1);

2.3 双亲委派机制

2.3.1 示例

在介绍双亲委派机制前先来看一个示例,在项目中创建java.lang包,新建String.java

public class String {

    static {
        System.out.println("自定义java.lang.String 被执行");
    }
}

在其它包中创建StringTest.java

public class StringTest {

    static {
        System.out.println("自定义java.lang.StringTest 被执行");
    }

    public static void main(String[] args) {
        ClassLoader classLoader = java.lang.String.class.getClassLoader();
        System.out.println("load String classLoader:" + classLoader);

        StringTest.class.getClassLoader();
    }
}

运行StringTest类中的main()方法,输出结果:

自定义java.lang.StringTest 被执行

load String classLoader:null

可以看到String类中的static代码块并没有执行

下面再来看一个示例,在String.java类中新增一个main()方法

public class String {

    static {
        System.out.println("自定义java.lang.String 被执行");
    }

    public static void main(String[] args) {
        System.out.println("执行String的main()方法");
    }
}

运行String.java类中mian()方法,输出结果:

Error: Main method not found in class java.lang.String, please define the main method as: public static void main(String[] args) or a JavaFX application class must extend javafx.application.Application

2.3.2 疑问

  • 第一个示例为什么没有执行String类中定义的static代码块?
  • 第二个示例执行String类中的main()方法为什么会报错?

2.3.3 解惑

在第二章节中介绍了三种类加载器:引导类加载器、扩展类加载器、系统类加载器,它们存在着层级关系:

类加载器在加载一个类的时候并不会立刻去加载,而是向上委托,直到没有可以委托的类加载器后,自己才进行加载。

在理解这句话后来看看第一个问题:第一个示例为什么没有执行String类中定义的static代码块?

当我们要加载String这个类的时候,会一直向上委托,直到委托给启动类加载器,启动类加载器发现自己可以java.lang.String这个类,那么就进行加载,加载是的jdk自带的String类,并非我们定义的String类,因此static代码块没有被执行

了解第一个问题后,第二个问题也就迎刃而解了,jdk自带的String类中没有main()方法,报错也是理所当然的