「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」。
本期是【大厂面试】系列文章的第8期,模拟Java虚拟机基础知识高频面试题目。
面试开始
面试官:今天聊聊Java虚拟机吧
面试官:知道什么是堆吗?
大彬:堆用于存放对象实例,是垃圾收集器管理的主要区域,因此也被称作GC
堆。堆可以细分为:新生代(Eden
空间、From Survivor
、To Survivor
空间)和老年代。
面试官:嗯,说一下堆栈的区别?
独白:哈哈,这个简单啦~
大彬:1、堆的物理地址分配是不连续的,性能较慢;栈的物理地址分配是连续的,性能相对较快。
大彬:2、堆存放的是对象的实例和数组;栈存放的是局部变量,操作数栈,返回结果等。
大彬:3、堆是线程共享的;栈是线程私有的。
面试官:了解双亲委派模型吗?
独白:老八股文了哈哈
大彬:当类载器收到一个类的加载请求时,它首先不会自己尝试去加载它,而是把这个请求委派给父类加载器去完成,这样层层委派,因此所有的加载请求最终都会传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
大彬:双亲委派模型的具体实现代码在 java.lang.ClassLoader
中,此类的 loadClass()
方法运行过程如下:先检查类是否已经加载过,如果没有则让父类加载器去加载。当父类加载器加载失败时抛出 ClassNotFoundException
,此时尝试自己去加载。源码如下:
public abstract class ClassLoader {
// The parent class loader for delegation
private final ClassLoader parent;
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
}
面试:那你觉得为什么需要双亲委派模型呢?
大彬:假如没有双亲委派模型而是由各个类加载器自行加载的话,内存里面可能会出现多个相同的类。
大彬:比如用户编写了一个java.lang.Object
的同名类并放在ClassPath
中,多个类加载器都去加载这个类到内存中,系统中将会出现多个不同的Object
类,那么类之间的比较结果及类的唯一性将无法保证。
面试官:刚提到类加载器,什么是类加载器呢?
大彬:类加载器就是加载所有的类的工具,它加载的类在内存中只会存在一份,也就是堆中的Class对象。不可以重复加载
大彬:Java 文件在经过 Java 编译器编译之后就被转换成 Java 字节码(class 文件)。类加载器负责读取 Java 字节码,并转换成 java.lang.Class
类的一个实例。
面试官:嗯,那你知道有哪些类加载器吗?
独白:没完没了了...
大彬:主要有以下四种类加载器:
- 启动类加载器:用来加载 Java 核心类库,无法被 Java 程序直接引用。
- 扩展类加载器:它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器:它根据应用的类路径来加载 Java 类。可通过
ClassLoader.getSystemClassLoader()
获取它。 - 自定义类加载器:通过继承
java.lang.ClassLoader
类的方式实现。
面试官:好,怎么判断一个对象是否存活?
大彬:嗯,JVM对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不再被任何途径引用的对象)。
大彬:判断对象是否存活有两种方法:引用计数法和可达性分析。
大彬:先讲讲引用计数法。给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。
大彬:然后是可达性分析。通过GC Root
对象为起点,从这些节点向下搜索,搜索所走过的路径叫引用链,当一个对象到GC Root
没有任何的引用链相连时,说明这个对象是不可用的。
面试官:那可以作为GC Roots的对象有哪些呢?
大彬:主要有四种对象。
- 虚拟机栈中引用的对象
- 本地方法栈中Native方法引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
面试官:不错,今天面试就到这吧
独白:本期JVM专题内容相对基础,后续会更新JVM调优、垃圾收集器、内存分配策略等内容,敬请期待~
\