附录:精选面试题
Q:为什么虚拟机必须保证一个类的Clinit( )方法在多线程的情况下被同步加锁 ?
A: 因为虚拟机在加载完一个类之后直接把这个类放到本地内存的方法区(也叫原空间)中了,当其他程序再来调这个类的时候直接从内存中拿的,因此Clinit( )方法只会执行一次来保证类只会被加载一次,当同时有两个线程进来操作同一个类的Clinit( )方法,可能会出现数据的不一致性,当两个线程中的某个线程抢到了类锁,而且一直在Clinit()方法中执行,另一个线程会一直保持阻塞状态
Q: 类加载子系统中的加载阶段分别有哪些类加载器 ?
A: 一般会有BootStarpClassLoader引导类加载器,ExtClassLoader扩展类加载器,ApplicationClassLoder应用类加载器。
Q: 类加载器又分为几种 ?
A: 从官方给的规范来讲,分为两种类加载器,分别是引导类加载器,和自定义类加载器。引导类加载器BootStarpClassLoader是由C语言和C++进行编写的,主要加载Java的核心类库。而直接或间接继承ClassLoader的加载器都可以认为是自定义类加载器,JVM默认提供给用户使用的是继承图最底层的ApplicationClassLoader系统类加载器,ApplicationClassLoader调用getParent( )方法一直往上面调,是获取不到BootStarpClassLoader引导类加载器的。
一、内存结构
当class文件以二进制流的方式加载到JVM内存的类加载子系统时,在类加载子系统(Class Loader SubSystem)中又分三步对数据进行解析,分别是加载阶段(Lading),链接阶段(Linking),初始化阶段(Initiatlizetion)。
![](https://img-blog.csdnimg.cn/img_convert/e9e917d9013263504e9fab9141838ebc.png)
加载阶段有:引导类加载器(BootStrapClassLoader),扩展类加载器(ExtensionClassLoder),系统类加载器(ApplicationClassLoder),和自定义加载器。
链接阶段:验证(Verify),准备(Prepare),解析(Resolve)。
最后把数据一起放入初始化阶段。
![](https://img-blog.csdnimg.cn/img_convert/ea1b492c0c81eec1ca73076ba341afb2.png)
![](https://img-blog.csdnimg.cn/img_convert/9f0124bfe0a7dc8692f50d1a67139dab.png)
![](https://img-blog.csdnimg.cn/img_convert/933b462ad920546651cf74158970e931.png)
二、类加载器和类加载过程
ClassLoader 只负责class文件的加载,至于是否可以运行,则有ExecutionEngin(执行引擎) 决定。
![](https://img-blog.csdnimg.cn/img_convert/75a44eee3ef66a014956afe1d68e2d17.png)
![](https://img-blog.csdnimg.cn/img_convert/e289d45f589ed7c706681de627d67974.png)
![](https://img-blog.csdnimg.cn/img_convert/7c18bddaeca7202677450ef8e5be726b.png)
![](https://img-blog.csdnimg.cn/img_convert/c56ec6d29ee9a1ace0a1033bead12902.png)
Loading加载阶段
![](https://img-blog.csdnimg.cn/img_convert/ae0f71ea0282e5dd3690f32ef2225601.png)
![](https://img-blog.csdnimg.cn/img_convert/a45800de8575f5ffff8f8fdecae93081.png)
链接阶段(Linking)
![](https://img-blog.csdnimg.cn/img_convert/7f1c54763f59eaa198f77ebbcd476272.png)
初始化阶段(Initiatlizetion)
![](https://img-blog.csdnimg.cn/img_convert/cea6bf5e1248867766b2538e48c0eb1e.png)
程序加载到初始化阶段<clinit>( )方法会收集静态变量赋值的动作,在连接阶段(Linking)先为静态变量(类变量)赋0值,当程序走到初始化阶段从上到下收集赋值动作,赋值给静态变量。
当类中没有声明过静态变量或者静态代码块时,类加载子系统在进行到初始化阶段时就不会创建<clinit>( )方法。
![](https://img-blog.csdnimg.cn/img_convert/41d3ca5ec2c2e4b63ff42917f8c5ebfe.png)
![](https://img-blog.csdnimg.cn/img_convert/0b3f433b1dd59a7931a7b7618f711010.png)
变量声明在静态代码块的后面,可以在静态代码块进行赋值,但不能进行调用,报错信息为 “非法的前向引用”
![](https://img-blog.csdnimg.cn/img_convert/b2f8c39adf2799909c2bbc0611840894.png)
![](https://img-blog.csdnimg.cn/img_convert/1d0e4434e0f129abf4b15bd7194423e4.png)
任何一个类都会有 init( )方法(用来初始化类对象),但不都有Clinit( )方法(用于收集静态变量,供该类全局使用)
![](https://img-blog.csdnimg.cn/img_convert/a69021ad6aaecaf57d4ccc0cb32b8ed0.png)
Q:为什么虚拟机必须保证一个类的Clinit( )方法在多线程的情况下被同步加锁。
A: 因为虚拟机在加载完一个类之后直接把这个类放到本地内存的方法区(也叫原空间)中了,当其他程序再来调这个类的时候直接从内存中拿的,因此Clinit( )方法只会执行一次来保证类只会被加载一次,当同时有两个线程进来操作同一个类的Clinit( )方法,可能会出现数据的不一致性,当两个线程中的某个线程抢到了类锁,而且一直在Clinit()方法中执行,另一个线程会一直保持阻塞状态。
三、类加载器的分类
![](https://img-blog.csdnimg.cn/img_convert/c942be2a8ccb4ee5df8dc92aa7fb87eb.png)
下面这一张图的几种类加载器是等级关系,例如文件目录上下级。但绝不能理解为是 继承关系。BootClassLoader是用C和C++编写的,而下面的几个类加载器是Java语言编写的,都间接继承了ClassLoader。
![](https://img-blog.csdnimg.cn/img_convert/904121587781c5328fc133e84d670091.png)
![](https://img-blog.csdnimg.cn/img_convert/b3691c17efa53c9dcc88bb1689dcdcbb.png)
![](https://img-blog.csdnimg.cn/img_convert/4df073813d71e2f66d329849cb88da3a.png)
![](https://img-blog.csdnimg.cn/img_convert/403746bcaa9e5fab239c9fe4a4e0661f.png)
![](https://img-blog.csdnimg.cn/img_convert/0210668fcb48ea10c5eaa0046421466e.png)
是一种像文件夹一样的包含关系。A文件夹包含B文件夹,B文件夹又包含着C文件夹,ClassLoader.getSystemClassLoader( ) 方法获取到ApplicationClassLoader。
![](https://img-blog.csdnimg.cn/img_convert/184860301fcf8e24dbfefe672385bf18.png)
也可以认为BootStarpClassLoader只负责加载Java程序的核心类库,自定义的加载器太low了,不配“高端”加载器出手帮忙,另外一方面,BootStarpClassLoader是使用C和C++进行编写的,自热而然的就获取不到它的对象了。
![](https://img-blog.csdnimg.cn/img_convert/3a8a8212c4bf70f9cdf1abdd17237ec1.png)
public class ClassLoaderTest {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取其上层:扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d
//获取其上层:获取不到引导类加载器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);//null
//对于用户自定义类来说:默认使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);//null
}
}
引导类、扩展类、系统类加载器的使用及演示
引导类加载器
在上面我们看到,从最底层的systemClassLoader系统类加载器,调用 getParent( )方法会调用到它上层的一个扩展类加载器和应用类加载器对象,这些两个类加载器对象也都需要加载。都是通过BootStarpClassLoader引导类加载器进行加载的,所以也就把BootStarpClassLoader、ExtClassLoader、ApplicationClassLoader都认为是系统核心类库。
![](https://img-blog.csdnimg.cn/img_convert/86b73fc79f4f79d0ec856922111a25da.png)
![](https://img-blog.csdnimg.cn/img_convert/790d4fc9445cc6c76fa58db6cb5d7be0.png)
![](https://img-blog.csdnimg.cn/img_convert/1af0d054c5d92ee535ec1b6a9d7e00cc.png)
扩展类加载器扩展类加载器扩展类加载器扩展类加载器扩展类加载器扩展类加载器扩展类加载器扩展类加载器扩展类加载器扩展类加载器
![](https://img-blog.csdnimg.cn/img_convert/17d75f236923e465537b81ebb2cb0b3f.png)
![](https://img-blog.csdnimg.cn/img_convert/4d4f71ab64c6974683ad1657a69c241e.png)
![](https://img-blog.csdnimg.cn/img_convert/e8eb2878b443cac9a4a4cf19ce81ed87.png)
应用程序类加载器应用程序类加载器应用程序类加载器应用程序类加载器应用程序类加载器应用程序类加载器应用程序类加载器应用程序类加载器应用程序类加载器应用程序类加载器
![](https://img-blog.csdnimg.cn/img_convert/dfe7591091835fdec3f13f2ad295a812.png)
自定义类加载器自定义类加载器自定义类加载器自定义类加载器自定义类加载器
![](https://img-blog.csdnimg.cn/img_convert/1766ffecd48c0a3c4e10b7fa5d4cc2ae.png)
![](https://img-blog.csdnimg.cn/img_convert/18f1c99c089b5592eef0487094daa2ca.png)
自定义加载器
package com.atguigu.java1;
import java.io.FileNotFoundException;
/**
* 自定义用户类加载器
* @author shkstart
* @create 2019 下午 12:21
*/
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] result = getClassFromCustomPath(name);
if(result == null){
throw new FileNotFoundException();
}else{
return defineClass(name,result,0,result.length);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
throw new ClassNotFoundException(name);
}
private byte[] getClassFromCustomPath(String name){
//从自定义路径中加载指定类:细节略
//如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。
return null;
}
public static void main(String[] args) {
CustomClassLoader customClassLoader = new CustomClassLoader();
try {
Class<?> clazz = Class.forName("One",true,customClassLoader);
Object obj = clazz.newInstance();
System.out.println(obj.getClass().getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
}
![](https://img-blog.csdnimg.cn/img_convert/4f967257718e5360590395cd00c73215.png)
package com.atguigu.java1;
/**
* @author shkstart
* @create 2020 上午 10:59
*/
public class ClassLoaderTest2 {
public static void main(String[] args) {
try {
//1.获取引导类加载器 获取不到 null
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
System.out.println(classLoader);
//2.获取系统类加载器 sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
System.out.println(classLoader1);
//3.获取扩展类加载器 sun.misc.Launcher$ExtClassLoader@1b6d3586
ClassLoader classLoader2 = ClassLoader.getSystemClassLoader().getParent();
System.out.println(classLoader2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}