类加载过程
我是 javapub,一名 Markdown
程序员从👨💻,八股文种子选手。
: 你了解Java的类加载过程吗?跟我聊聊classes是如何加载到JVM中的。
Java的类加载过程由加载、验证、准备、解析和初始化5个阶段组成。当我们使用java命令执行一个类时,JVM会首先搜索类的加载路径,这包括Bootstrap Classpath、Extension Classpath和Application Classpath。
: 哈哈,这也太官方了吧,来,我们以更口语的方式探讨下类加载过程。想象你是一名新手Java程序员,刚入职一个公司,被分配一个任务需要执行一个Java类,你会有什么疑惑或者过程?
好的,那我来思考下当初我第一次运行Java程序的时候的内心活动: 天啊,我首先得搞清楚要运行的这个类到底在哪儿?难道要我一个个文件翻找吗?那还不如让我直接读JVM的源码来找呢! Wait,原来JVM已经把这事儿都帮我干了,它会去找 classpath 下的文件,包括环境变量里设置的那堆classpath。这肯定是个苦力活,幸好有JVM这个工具哥帮忙! 找到类文件了,接下来JVM该干嘛?嗯,它得确定这个类里写的是否都是正确的Java语法,不会误导小菜鸟我。它会进行类文件的验证,确保我的Java程序没有安全隐患! 验证通过了,JVM该准备干啥?它需要为类中的静态变量分配内存并设置默认初始值,这个过程就是准备阶段。 静态变量有内存了,JVM还需要干什么?嗯,它得解析类文件里的符号引用,像是类名、方法名、变量名等,把这些符号转成直接引用,方便后续调用。这就是解析阶段。 最后,JVM要真正帮我干活了,它要执行类构造器 <clinit>()
方法的字节码,给静态变量复杂的初始值。这就是初始化阶段。 Initialization of 类名 complete! 我的任务终于可以开始执行了!真不容易,还得感谢JVM这位大恩人。
: 哈哈,这个解释我喜欢!inclusion of源代码和动态的思考过程增加了解释的轻松和趣味性。你这种围绕一个场景作解释的方式很形象,让人容易理解,这在技术面试中是很重要的一点。
谢谢面试官的夸奖!我也觉得把一个复杂的技术问题变成一个故事或场景会让人更容易理解其中的逻辑和流程。这也是我在博客和公众号里常用的一种讲解方式,很高兴面试官能够欣赏!
: 那我们继续聊聊类加载过程中最重要的几个类吧,什么类负责 finding 和loading 操作?
在类加载过程中,ClassLoader 类及其子类负责finding和loading操作。
: 是的,ClassLoader是一个很重要的类。那么默认的ClassLoader又有哪几个?
默认有3个ClassLoader:
- Bootstrap ClassLoader 启动类加载器:负责加载JDK内置的类,如rt.jar等。
- Extension ClassLoader 扩展类加载器:负责加载JDK扩展目录中的jar包、以及VM指定的其他jar包。
- App ClassLoader 应用程序类加载器:负责加载用户自定义的类。
//JDK源码中ClassLoader的继承关系
public class ClassLoader {
public ClassLoader() {}
public Class<?> loadClass(String name) {...}
}
public class SecureClassLoader extends ClassLoader {...}
public class URLClassLoader extends SecureClassLoader {...}
//和类加载息息相关的其他类
public final class Class<T> {...}
public class ClassNotFoundException extends Exception {...}
: ClassLoader的加载顺序遵循什么规则?
ClassLoader遵循父类委派模式,当一个类加载器收到类加载请求时,它会把这个请求委派给它的父类加载器去完成,依此形成一个链条。只有父类加载器在它的搜索范围内无法找到所需的类时,子加载器才会尝试自己去加载这个类。 因此,类加载的顺序为:
- Bootstrap ClassLoader
- Extension ClassLoader
- App ClassLoader 如果父类可以完成类加载工作则子类不会再去加载,否则子类才会负责加载。这种委派机制可以避免重复加载,也有利于安全性。
: 很好,你对Java类加载机制有很深入的理解。最后,我们聊一聊类加载过程的双亲委派模型在哪些方面带来的好处?
类加载双亲委派模型带来的好处主要有两点:
- 避免重复加载:当父类已经加载了某个类时,子类不会再重复加载该类,从而避免资源消耗。
- 安全性:父类加载的类被所有的子类所信任。 strs如果子类可以随意加载,那么就可能加载一个非授权版本的类,破坏安全性。 综上,双亲委派模型体现了“安全第一,不重复加载”的设计思想,这两点好处使得Java类加载机制更加完备和安全。
: 很好,你的回答很全面和到位。
非常感谢面试官的指导。
: 你的谢意我心领了,我们的对话也达到了我的目的。真诚地希望这些知识能在你的工作中派上用场。加油!
非常感谢面试官的鼓励!我会努力运用所学的知识,在工作实践中不断进步。也祝面试官心想事成
: 好,让我们继续讨论类加载过程中另一个重要概念:类的生命周期。什么是类的生命周期?它包括哪几个阶段?
类的生命周期描述了一个类从被加载到被卸载的整个过程。它主要包括:
- 加载:找寻并加载类的二进制数据,将其读入内存,并为之创建一个Class对象。
- 链接:验证、准备和解析。验证是否有正确的内部结构,并和其他类保持一致性。准备分配内存并设置初始值。把相关的符号引用转换为直接引用。
- 初始化:执行类构造器
<clinit>()
方法的字节码,给类的静态变量赋予正确的初始值。 - 使用:程序使用这个类创建实例对象、访问类的静态变量和方法等。
- 卸载:GC回收这个类的所有实例和空间。卸载该类的字节码,并从运行时常量池中移除这个类的符号引用。
: 说明的很详细。类的生命周期中,有哪几个阶段会触发类初始化?
有三种情况会触发类的初始化:
- 新创建一个该类的实例。
- 访问该类的静态变量,或为静态变量赋值。
- 调用该类的静态方法。 除此之外,下面这些操作不会触发类的初始化:
- 使用一个类的名称,如在变量声明中使用该类的名称。
- 使用类加载器加载一个类。
- 访问某个类的静态常量。
//举例
public class Test {
public static int a = 1; //静态变量,会触发初始化
public static final int b = 2; //静态常量,不会触发初始化
public static void method(){} //静态方法,会触发初始化
}
Test t = new Test(); //创建实例,会触发初始化
Test.a = 2; //访问静态变量,会触发初始化
Test.b = 3; //访问静态常量,不会触发初始化
Test.method(); //调用静态方法,会触发初始化
: 说的很清楚,举例也很形象。那静态代码块是在哪个阶段执行的?
静态代码块是在类初始化阶段执行的,位于 <clinit>()
方法中。它优先于构造方法执行,并且只执行一次。例如:
public class Test {
static {
System.out.println("静态代码块执行");
}
public Test() {
System.out.println("构造方法执行");
}
}
Test t1 = new Test();
//打印:
静态代码块执行
构造方法执行
Test t2 = new Test();
//打印:
构造方法执行
因此,静态代码块主要用于一次性地对类进行初始化设置,这些设置只在类第一次被加载时执行。它和构造方法的不同之处在于,构造方法在每次创建实例时都会执行。
: 很好,你对类的生命周期及其与类初始化的关系理解很透彻。我想你应该可以轻松应付与类加载相关的面试题了!
非常感谢面试官的提问,这些关于类的生命周期和初始化阶段的知识点对我来说均很重要。我会不断复习和运用这些知识,以便进一步熟练掌握类加载机制的相关原理,从而应对可能遇到的面试题和实际开发中的相关问题。
最近我在更新《面试1v1》系列文章,主要以场景化的方式,讲解我们在面试中遇到的问题,致力于让每一位工程师拿到自己心仪的offer,感兴趣可以关注公众号JavaPub追更!
🎁目录合集:
Gitee:https://gitee.com/rodert/JavaPub
GitHub:https://github.com/Rodert/JavaPub