一、基础篇
1. 谈谈你对面向对象编程三大特性的理解?
- 封装:将数据和操作封装在类中,通过访问修饰符控制外部对类成员的访问,提高了代码的安全性和可维护性。例如,将一些敏感数据设为私有,并通过公共方法提供访问接口,可以防止外部直接修改数据,同时可以在方法中加入数据校验等逻辑。
- 继承:子类继承父类的属性和方法,实现了代码的复用。继承可以减少代码重复,提高开发效率。例如,多个类都有一些共同的属性和方法,可以将这些共同部分提取到父类中,子类继承父类即可拥有这些属性和方法。同时,继承也支持多态,使得程序更加灵活。
- 多态:同一操作作用于不同的对象可以有不同的表现形式。多态分为编译时多态(方法重载)和运行时多态(方法重写)。多态提高了代码的可扩展性和可维护性。例如,定义一个父类类型的变量,可以指向不同的子类对象,在运行时根据实际对象类型调用相应的子类方法。
2. 解释一下 Java 的垃圾回收机制?
Java 的垃圾回收机制是自动管理内存的一种方式。Java 虚拟机(JVM)负责跟踪和回收不再被使用的对象所占用的内存空间。
-
垃圾回收的过程:
- 标记阶段:JVM 遍历所有的对象,标记出那些仍然被引用的对象。
- 清除阶段:回收未被标记的对象所占用的内存空间。
- 整理阶段(可选):对内存进行整理,消除内存碎片。
-
垃圾回收算法:
- 标记 - 清除算法:先标记出所有需要回收的对象,然后统一回收这些对象所占用的内存空间。该算法简单,但容易产生内存碎片。
- 标记 - 整理算法:在标记阶段完成后,将所有存活的对象移动到一端,然后清理掉端边界以外的内存空间。该算法解决了内存碎片问题,但效率相对较低。
- 复制算法:将内存分为两块,每次只使用其中一块。当这一块内存满了时,将存活的对象复制到另一块内存中,然后清理掉原来的那块内存。该算法效率高,但内存利用率只有一半。
-
触发垃圾回收的条件:
- 当堆内存不足时,JVM 会自动触发垃圾回收。
- 可以通过调用 System.gc() 方法来建议 JVM 进行垃圾回收,但不能保证一定会立即执行。
二、JVM 篇
1. 说说 JVM 的内存结构?
JVM 的内存结构主要包括以下几个部分:
- 程序计数器:是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。每个线程都有一个独立的程序计数器,用于记录线程正在执行的字节码指令地址。如果线程正在执行的是一个本地方法,那么程序计数器的值为 undefined。
- 虚拟机栈:每个线程都有一个私有的虚拟机栈,用于存储栈帧。栈帧是方法执行的内存模型,它包含局部变量表、操作数栈、动态链接、方法出口等信息。当线程调用一个方法时,JVM 会为该方法创建一个栈帧,并将其压入虚拟机栈。当方法执行完毕时,对应的栈帧会从虚拟机栈中弹出。
- 本地方法栈:与虚拟机栈类似,本地方法栈用于支持本地方法(Native Method)的执行。本地方法是用其他语言(如 C、C++)实现的方法,它们不是由 Java 字节码指令直接驱动执行的。
- 堆:堆是 JVM 中最大的一块内存空间,用于存储对象实例和数组。几乎所有的对象实例都在堆上分配内存。堆可以分为新生代和老年代,新生代又可以进一步分为 Eden 区、Survivor0 区和 Survivor1 区。对象在堆上的分配和回收是由垃圾回收器自动管理的。
- 方法区:方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在 JDK 8 之前,方法区也被称为永久代。从 JDK 8 开始,使用元空间(Metaspace)来替代永久代,元空间使用本地内存,而不是 JVM 内存。
2. 如何判断一个对象是否可以被回收?
在 Java 中,判断一个对象是否可以被回收主要有两种方法:
- 引用计数法:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加 1;当引用失效时,计数器值减 1。当计数器值为 0 时,表示该对象没有被任何地方引用,可以被回收。但是,引用计数法存在一个问题,就是无法解决循环引用的问题。例如,两个对象相互引用,但没有其他地方引用它们,此时它们的引用计数器值都不为 0,但实际上它们已经不再被使用了。


