Java 高频面试题汇总与解析
本文整理 Java 高频面试题,涵盖基础语法、面向对象、数据类型、标识符、instanceof、自动装箱拆箱、重载重写。JVM 部分详解内存模型、类加载机制、垃圾回收算法(G1、ZGC)。旨在帮助开发者系统复习核心知识点,应对技术面试。

本文整理 Java 高频面试题,涵盖基础语法、面向对象、数据类型、标识符、instanceof、自动装箱拆箱、重载重写。JVM 部分详解内存模型、类加载机制、垃圾回收算法(G1、ZGC)。旨在帮助开发者系统复习核心知识点,应对技术面试。

面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候一一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发。
面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。但是性能上来说,比面向过程要低。
注:

int 是基本数据类型,Integer 是 int 的封装类,是引用类型。int 默认值是 0,而 Integer 默认值是 null,所以 Integer 能区分出 0 和 null 的情况。一旦 java 看到 null,就知道这个引用还没有指向某个对象,再任何引用使用前,必须为其指定一个对象,否则会报错。
虽然定义了 boolean 这种数据类型,但是只对它提供了非常有限的支持。在 Java 虚拟机中没有任何供 boolean 值专用的字节码指令,Java 语言表达式所操作的 boolean 值,在编译之后都使用 Java 虚拟机中的 int 数据类型来代替,而 boolean 数组将会被编码成 Java 虚拟机的 byte 数组,每个元素 boolean 元素占 8 位。这样我们可以得出 boolean 类型占了单独使用是 4 个字节,在数组中又是 1 个字节。使用 int 的原因是,对于当下 32 位的处理器(CPU)来说,一次处理数据是 32 位(这里不是指的是 32/64 位系统,而是指 CPU 硬件层面),具有高效存取的特点。
标识符的含义:是指在程序中,我们自己定义的内容,譬如,类的名字,方法名称以及变量名称等等,都是标识符。
命名规则:(硬性要求) 标识符可以包含英文字母,0-9 的数字,$以及_ 标识符不能以数字开头 标识符不是关键字
命名规范:(非硬性要求) 类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。方法名规范:同变量名。
instanceof 严格来说是 Java 中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:
boolean result = obj instanceof Class
其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果 result 都返回 true,否则返回 false。
注意:编译器会检查 obj 是否能转换成右边的 class 类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
int i = 0;System.out.println(i instanceof Integer); // 编译不通过 i 必须是引用类型,不能是基本类型System.out.println(i instanceof Object); // 编译不通过Integer integer = new Integer(1);System.out.println(integer instanceof Integer); // true// false ,在 JavaSE 规范 中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回 false。System.out.println(null instanceof Object);装箱就是自动将基本数据类型转换为包装器类型(int-->Integer);调用方法:Integer 的 valueOf(int) 方法
拆箱就是自动将包装器类型转换为基本数据类型(Integer-->int)。调用方法:Integer 的 intValue 方法
在 Java SE5 之前,如果要生成一个数值为 10 的 Integer 对象,必须这样进行:
Integer i = new Integer(10);
而在从 Java SE5 开始就提供了自动装箱的特性,如果要生成一个数值为 10 的 Integer 对象,只需要这样就可以了:
Integer i = 10;
面试题 1:以下代码会输出什么?
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
运行结果:
truefalse为什么会出现这样的结果?输出结果表明 i1 和 i2 指向的是同一个对象,而 i3 和 i4 指向的是不同的对象。此时只需一看源码便知究竟,下面这段代码是 Integer 的 valueOf 方法的具体实现:
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
其中 IntegerCache 类的实现为:
private static class IntegerCache {
static final int high;
static final Integer cache[];
static {
final int low = -128;
// high value may be configured by property
int h = 127;
if (integerCacheHighPropValue != null) {
// Use Long.decode here to avoid invoking methods that
// require Integer's autoboxing cache to be initialized
int i = Long.decode(integerCacheHighPropValue).intValue();
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - -low);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
从这 2 段代码可以看出,在通过 valueOf 方法创建 Integer 对象的时候,如果数值在 [-128,127] 之间,便返回指向 IntegerCache.cache 中已经存在的对象的引用;否则创建一个新的 Integer 对象。
上面的代码中 i1 和 i2 的数值为 100,因此会直接从 cache 中取已经存在的对象,所以 i1 和 i2 指向的是同一个对象,而 i3 和 i4 则是分别指向不同的对象。
面试题 2:以下代码输出什么
public class Main {
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
运行结果:
falsefalse原因:在某个范围内的整型数值的个数是有限的,而浮点数却不是。
重写 (Override)
从字面上看,重写就是重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名,参数列表,返回类型 (除过子类中方法的返回值是父类中方法返回值的子类时) 都相同的情况下,对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。
public class Father {
public static void main(String[] args) {
Son s = new Son();
s.sayHello();
}
public void sayHello() {
System.out.println("Hello");
}
}
class Son extends Father{
@Override
public void sayHello() {
System.out.println("hello by ");
}
}
重写总结: 1.发生在父类与子类之间 2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同 3.访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private) 4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
重载(Overload)
在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
public class Father {
public static void main(String[] args) {
Father s = new Father();
s.sayHello();
s.sayHello("wintershii");
}
public void sayHello() {
System.out.println("Hello");
}
public void sayHello(String name) {
System.out.println("Hello" + " " + name);
}
}
重载总结: 1.重载 Overload 是一个类中多态性的一种表现 2.重载要求同名方法的参数列表不同 (参数类型,参数个数甚至是参数顺序) 3.重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准
JVM 是 Java 运行基础,面试时一定会遇到 JVM 的有关问题,内容相对集中,但对知识深度要求较高。

其中内存模型,类加载机制,GC 是重点方面。性能调优部分更偏向应用,重点突出实践能力。编译器优化和执行模式部分偏向于理论基础,重点掌握知识点。
需了解内存模型各部分作用,保存哪些数据。
类加载双亲委派加载机制,常用加载器分别加载哪种类型的类。
GC 分代回收的思想和依据以及不同垃圾回收算法的回收思路和适合场景。
性能调优常有 JVM 优化参数作用,参数调优的依据,常用的 JVM 分析工具能分析哪些问题以及使用方法。
执行模式解释/编译/混合模式的优缺点,Java7 提供的分层编译技术,JIT 即时编译技术,OSR 栈上替换,C1/C2 编译器针对的场景,C2 针对的是 server 模式,优化更激进。新技术方面 Java10 的 graal 编译器
编译器优化 javac 的编译过程,ast 抽象语法树,编译器优化和运行器优化。
线程独占:栈,本地方法栈,程序计数器 线程共享:堆,方法区
又称方法栈,线程私有的,线程执行方法是都会创建一个栈帧,用来存储局部变量表,操作栈,动态链接,方法出口等信息。调用方法时执行入栈,方法返回时执行出栈。
与栈类似,也是用来保存执行方法的信息。执行 Java 方法是使用栈,执行 Native 方法时使用本地方法栈。
保存着当前线程执行的字节码位置,每个线程工作时都有独立的计数器,只为执行 Java 方法服务,执行 Native 方法时,程序计数器为空。
JVM 内存管理最大的一块,对被线程共享,目的是存放对象的实例,几乎所有对象实例都会放在这里,当堆没有可用空间时,会抛出 OOM 异常。根据对象的存活周期不同,JVM 把对象进行分代管理,由垃圾回收器进行垃圾的回收管理
又称非堆区,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器优化后的代码等数据。1.7 的永久代和 1.8 的元空间都是方法区的一种实现

JMM 是定义程序中变量的访问规则,线程对于变量的操作只能在自己的工作内存中进行,而不能直接对主内存操作。由于指令重排序,读写的顺序会被打乱,因此 JMM 需要提供原子性,可见性,有序性保证。

加载过程

其中验证,准备,解析合称链接
加载通过类的完全限定名,查找此类字节码文件,利用字节码文件创建 Class 对象。
验证确保 Class 文件符合当前虚拟机的要求,不会危害到虚拟机自身安全。
准备进行内存分配,为 static 修饰的类变量分配内存,并设置初始值 (0 或 null)。不包含 final 修饰的静态变量,因为 final 变量在编译时分配。
解析将常量池中的符号引用替换为直接引用的过程。直接引用为直接指向目标的指针或者相对偏移量等。
初始化主要完成静态块执行以及静态变量的赋值。先初始化父类,再初始化当前类。只有对类主动使用时才会初始化。
触发条件包括,创建类的实例时,访问类的静态方法或静态变量的时候,使用 Class.forName 反射类的时候,或者某个子类初始化的时候。
Java 自带的加载器加载的类,在虚拟机的生命周期中是不会被卸载的,只有用户自定义的加载器加载的类才可以被卸。

双亲委派模式,即加载器加载类时先把请求委托给自己的父类加载器执行,直到顶层的启动类加载器。父类加载器能够完成加载则成功返回,不能则子类加载器才自己尝试加载。
优点:
分代回收基于两个事实:大部分对象很快就不使用了,还有一部分不会立即无用,但也不会持续很长时间。

年轻代->标记 - 复制 老年代->标记 - 清除
a、G1 算法
1.9 后默认的垃圾回收算法,特点保持高回收率的同时减少停顿。采用每次只清理一部分,而不是清理全部的增量式清理,以保证停顿时间不会过长。
其取消了年轻代与老年代的物理划分,但仍属于分代收集器,算法将堆分为若干个逻辑区域 (region),一部分用作年轻代,一部分用作老年代,还有用来存储巨型对象的分区。
同 CMS 相同,会遍历所有对象,标记引用情况,清除对象后会对区域进行复制移动,以整合碎片空间。
年轻代回收:并行复制采用复制算法,并行收集,会 StopTheWorld.
老年代回收:会对年轻代一并回收
初始标记完成堆 root 对象的标记,会 StopTheWorld. 并发标记 GC 线程和应用线程并发执行。最终标记完成三色标记周期,会 StopTheWorld. 复制/清楚会优先对可回收空间加大的区域进行回收
b、ZGC 算法
前面提供的高效垃圾回收算法,针对大堆内存设计,可以处理 TB 级别的堆,可以做到 10ms 以下的回收停顿时间。

roots 标记:标记 root 对象,会 StopTheWorld. 并发标记:利用读屏障与应用线程一起运行标记,可能发生 StopTheWorld. 清除会清理标记为不可用的对象。roots 重定位:是对存活的对象进行移动,以腾出大块内存空间,减少碎片产生。重定位最开始会 StopTheWorld,却决于重定位集与对象总活动集的比例。并发重定位与并发标记类似。
JVM 定义了不同运行时数据区,他们是用来执行应用程序的。某些区域随着 JVM 启动及销毁,另外一些区域的数据是线程性独立的,随着线程创建和销毁。jvm 内存模型总体架构图如下:(摘自 oracle 官方网站)

JVM 在执行 Java 程序时,会把它管理的内存划分为若干个的区域,每个区域都有自己的用途和创建销毁时间。如下图所示,可以分为两大部分,线程私有区和共享区。下图是根据自己理解画的一个 JVM 内存模型架构图:

JVM 内存分为线程私有区和线程共享区
线程私有区
1)、程序计数器
当同时进行的线程数超过 CPU 数或其内核数时,就要通过时间片轮询分派 CPU 的时间资源,不免发生线程切换。这时,每个线程就需要一个属于自己的计数器来记录下一条要运行的指令。如果执行的是 JAVA 方法,计数器记录正在执行的 java 字节码地址,如果执行的是 native 方法,则计数器为空。
2)、虚拟机栈
线程私有的,与线程在同一时间创建。管理 JAVA 方法执行的内存模型。每个方法执行时都会创建一个栈帧来存储方法的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法,-Xss 参数可以设置虚拟机栈大小)。栈的大小可以是固定的,或者是动态扩展的。如果请求的栈深度大于最大可用深度,则抛出 stackOverflowError;如果栈是可动态扩展的,但没有内存空间支持扩展,则抛出 OutofMemoryError。使用 jclasslib 工具可以查看 class 类文件的结构。下图为栈帧结构图:

3)、本地方法栈
与虚拟机栈作用相似。但它不是为 Java 方法服务的,而是本地方法(C 语言)。由于规范对这块没有强制要求,不同虚拟机实现方法不同。
线程共享区
1)、方法区
线程共享的,用于存放被虚拟机加载的类的元数据信息,如常量、静态变量和即时编译器编译后的代码。若要分代,算是永久代(老年代),以前类大多'static'的,很少被卸载或收集,现回收废弃常量和无用的类。其中运行时常量池存放编译生成的各种常量。(如果 hot spot 虚拟机确定一个类的定义信息不会被使用,也会将其回收。回收的基本条件至少有:所有该类的实例被回收,而且装载该类的 Classloader 被回收)
2)、堆
存放对象实例和数组,是垃圾回收的主要区域,分为新生代和老年代。刚创建的对象在新生代的 Eden 区中,经过 GC 后进入新生代的 S0 区中,再经过 GC 进入新生代的 S1 区中,15 次 GC 后仍存在就进入老年代。这是按照一种回收机制进行划分的,不是固定的。若堆的空间不够实例分配,则 OutOfMemoryError。



微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online