public class ClinitTest1 {
static class Father{
public static int A = 1;
static{
A = 2;
}
}
static class Son extends Father{
public static int B = A;
}
public static void main(String[] args) {
//加载Father类,其次加载Son类。
System.out.println(Son.B);//2
}
}
运行结果是2,因为Son类是Father类的子类,在执行Son类的加载前会先执行Father类的加载。
虚拟机必须保证一个类的<clinit>()
方法在多线程下被同步加锁:
public class DeadThreadTest {
public static void main(String[] args) {
Runnable r = () -> {
System.out.println(Thread.currentThread().getName() + "开始");
DeadThread dead = new DeadThread();
System.out.println(Thread.currentThread().getName() + "结束");
};
Thread t1 = new Thread(r, "线程1");
Thread t2 = new Thread(r, "线程2");
t1.start();
t2.start();
}
}
class DeadThread {
static {
if (true) {
System.out.println(Thread.currentThread().getName() + "初始化当前类");
while (true) {
}
}
}
}
结果分析:
- 两个线程同时去初始化 DeadThread 类,而 DeadThread 类中静态代码块中有一处死循环,切该类只能被初始化一次
- 所以先初始化 DeadThread 类的线程抢到了同步锁,然后在类的静态代码块中执行死循环,而另一个线程在等待同步锁的释放
- 所以运行结果是:某一个线程先执行 DeadThread 类的初始化,而另外一个类不会继续执行
类加载器的分类
JVM支持两种类型的类加载器 ,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。
- 从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器,所以ExtClassLoader 和 AppClassLoader 都属于自定义加载器。
四者之间是包含关系,不是上层和下层,也不是子父类的继承关系:
测试类加载器:
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
}
}
- 在获取引导类加载器时,获取到的值为 null ,因为引导类加载器由 C/C++ 语言编写,我们无法获取
- 两次获取系统类加载器的值都相同 ,说明系统类加载器全局唯一
虚拟机自带的加载器:
启动类加载器(引导类加载器,Bootstrap ClassLoader)
- 这个类加载使用C/C++语言实现的,嵌套在JVM内部
- 它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
- 并不继承自java.lang.ClassLoader,没有父加载器
- 加载扩展类和应用程序类加载器,并作为他们的父类加载器(当他俩的爹)
- 出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
扩展类加载器(Extension ClassLoader)
- Java语言编写,由sun.misc.Launcher$ExtClassLoader实现
- 派生于ClassLoader类
- 父类加载器为启动类加载器
- 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载
应用程序类加载器(系统类加载器,AppClassLoader)
- Java语言编写,由sun.misc.LaunchersAppClassLoader实现
- 派生于ClassLoader类
- 父类加载器为扩展类加载器
- 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
- 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
- 通过classLoader.getSystemclassLoader()方法可以获取到该类加载器
代码举例说明:
public class ClassLoaderTest1 {
public static void main(String[] args) {
System.out.println("**********启动类加载器**************");
//获取BootstrapClassLoader能够加载的api的路径
URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL element : urLs) {
System.out.println(element.toExternalForm());
}
//从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println(classLoader);//null,证明我们无法获取到启动类加载器
System.out.println("***********扩展类加载器*************");
String extDirs = System.getProperty("java.ext.dirs");
for (String path : extDirs.split(";")) {
System.out.println(path);
}
//从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器
ClassLoader classLoader1 = CurveDB.class.getClassLoader();
System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d
}
}
运行结果:
用户自定义类加载器:
在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。那为什么还需要自定义类加载器?
- 隔离加载类
- 修改类加载的方式
- 扩展加载源
- 防止源码泄漏
如何自定义类加载器?
- 开发人员可以通过继承抽象类java.lang.ClassLoader类的方式,实现自己的类加载器,以满足一些特殊的需求
- 在JDK1.2之前,在自定义类加载器时,总会去继承ClassLoader类并重写loadClass()方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findclass()方法中
- 在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findclass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。
代码示例:
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();
}
}
}
ClassLoader 类:
ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)
获取 ClassLoader 途径:
代码示例:
public class ClassLoaderTest2 {
public static void main(String[] args) {
try {
//1.Class.forName().getClassLoader()
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
# 总结
阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了
![image](https://img-blog.csdnimg.cn/img_convert/797db2dbbe759ad83a6f3a808e71e480.webp?x-oss-process=image/format,png)
1、JAVA面试核心知识整理(PDF):包含**JVM**,**JAVA集合**,**JAVA多线程并发**,JAVA基础,**Spring原理**,**微服务**,Netty与RPC,网络,日志,**Zookeeper**,**Kafka**,**RabbitMQ**,Hbase,**MongoDB**,Cassandra,**设计模式**,**负载均衡**,**数据库**,**一致性哈希**,**JAVA算法**,**数据结构**,加密算法,**分布式缓存**,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。
![image](https://img-blog.csdnimg.cn/img_convert/7f8b33cad8618c305fc075ed4ed961fb.webp?x-oss-process=image/format,png)
2、Redis学习笔记及学习思维脑图
![image](https://img-blog.csdnimg.cn/img_convert/d1b6a335d24d3db877ad921c950b774a.webp?x-oss-process=image/format,png)
3、数据面试必备20题+数据库性能优化的21个最佳实践
![image](https://img-blog.csdnimg.cn/img_convert/73b9db70afbce845fd3d95b806166e09.webp?x-oss-process=image/format,png)
AVA面试核心知识整理(PDF):包含**JVM**,**JAVA集合**,**JAVA多线程并发**,JAVA基础,**Spring原理**,**微服务**,Netty与RPC,网络,日志,**Zookeeper**,**Kafka**,**RabbitMQ**,Hbase,**MongoDB**,Cassandra,**设计模式**,**负载均衡**,**数据库**,**一致性哈希**,**JAVA算法**,**数据结构**,加密算法,**分布式缓存**,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。
[外链图片转存中...(img-DPhfcVnJ-1725893109814)]
2、Redis学习笔记及学习思维脑图
[外链图片转存中...(img-V3uR4JAY-1725893109815)]
3、数据面试必备20题+数据库性能优化的21个最佳实践
[外链图片转存中...(img-UoEPg1rM-1725893109815)]