欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > Java基础概念汇总

Java基础概念汇总

2025/4/2 11:30:05 来源:https://blog.csdn.net/weixin_52242569/article/details/146551132  浏览:    关键词:Java基础概念汇总

JavaEE

  • Java基础概念面试题详解
    • 1. Java的特点是什么?
    • 2. Java和C++的区别有哪些?
    • 3. 什么是JDK、JRE和JVM?它们之间有什么关系?
    • 4. Java是编译型语言还是解释型语言?
    • 5. Java如何实现跨平台?
  • 数据类型与变量面试题详解
    • 1. Java有哪些基本数据类型?
    • 2. `int`和`Integer`有什么区别?
    • 3. 什么是自动装箱和拆箱?
    • 4. `String`、`StringBuilder`和`StringBuffer`的区别?
    • 5. `==`和`equals()`的区别?
  • 面向对象编程(OOP)面试题详解
    • 1. 面向对象的四大特性是什么?
    • 2. 什么是类和对象?
    • 3. 重载(Overload)和重写(Override)的区别?
    • 4. 抽象类和接口的区别?
    • 5. 什么是多态?如何实现多态?
    • 6. 构造方法(Constructor)的特点和作用?
    • 7. this和super关键字的区别?
    • 8. 什么是静态变量和静态方法?
    • 9. final、finally和finalize的区别?
  • 异常处理面试题详解
    • 1. Java异常体系结构是怎样的?
    • 2. Error和Exception的区别?
    • 3. 受检异常和非受检异常的区别?
    • 4. try-catch-finally的执行顺序?
    • 5. throw和throws的区别?
  • 集合框架面试题详解
    • 1. Java集合框架的主要接口和类有哪些?
    • 2. List、Set和Map的区别?
    • 3. ArrayList和LinkedList的区别?
    • 4. HashMap的工作原理?
    • 5. HashMap和HashTable的区别?
    • 6. ConcurrentHashMap如何保证线程安全?
      • Java 7 及之前的 ConcurrentHashMap 实现
      • Java 8 及之后的 ConcurrentHashMap 实现
      • 重要内部类变化
      • 并发控制机制对比
      • 为什么Java 8要重新设计?
      • 典型使用场景建议
  • 多线程面试题详解
    • 1. 线程和进程的区别?
    • 2. 创建线程的几种方式?
    • 3. Runnable和Callable的区别?
    • 4. 线程的生命周期(状态)有哪些?
    • 5. sleep()和wait()的区别?
    • 6. synchronized关键字的用法?
    • 7. volatile关键字的作用?
    • 8. 什么是线程池?为什么要用线程池?
  • IO与NIO面试题详解
    • 1. Java IO流分为哪几种?
    • 2. 字节流和字符流的区别?
    • 3. BIO、NIO和AIO的区别?
    • 4. NIO的核心组件有哪些?
  • 反射与泛型面试题详解
    • 1. 什么是反射?反射的应用场景?
    • 2. 如何获取Class对象?
    • 3. 什么是泛型?泛型擦除是什么意思?
  • Java 8新特性面试题详解
    • 1. Lambda表达式的作用?
    • 2. Stream API的常用方法有哪些?
    • 3. 什么是函数式接口?常见的函数式接口有哪些?
    • 4. Optional类的作用?
  • JVM基础面试题详解
    • 1. JVM内存结构是怎样的?
    • 2. 什么是垃圾回收(GC)?常见的垃圾回收算法有哪些?
    • 3. 强引用、软引用、弱引用和虚引用的区别?
    • 4. 类加载过程是怎样的?
  • Java Stream 补充面试题详解
    • 1. Stream 的中间操作和终止操作有什么区别?
    • 2. 并行流(Parallel Stream)使用时要注意什么?
    • 3. Stream 和 Collection 的区别?
    • 4. 常用的 Stream 收集器(Collectors)有哪些?
    • 5. Stream 中的 findFirst() 和 findAny() 有什么区别?
    • 6. Stream 的 flatMap() 和 map() 有什么区别?
    • 7. 如何用 Stream 处理大文件?
    • 8. Stream 的 peek() 和 forEach() 有什么区别?
    • 9. 如何自定义 Collector?
    • 10. Stream 的 reduce() 方法如何使用?
  • Java 其他高频面试题补充
    • 1. Java 模块化系统(Java 9+)
    • 2. 记录类型(Record, Java 14+)
    • 3. 密封类(Sealed Class, Java 15+)
    • 4. 模式匹配(Java 16+)
    • 5. 虚拟线程(Virtual Thread, Java 19+)
    • 6. 响应式编程(Reactive Streams)
    • 7. Java 原生内存访问(Java 16+)
    • 8. 协程库比较
    • 9. 微基准测试(JMH)
    • 10. 代码安全实践
    • 11. JVM 性能调优
    • 12. 现代Java开发实践

Java基础概念面试题详解

1. Java的特点是什么?

Java的主要特点包括:

  • 面向对象:完全遵循OOP原则(封装、继承、多态)
  • 平台无关性:通过"Write Once, Run Anywhere"机制实现
  • 健壮性
    • 强类型检查
    • 自动内存管理(垃圾回收机制)
    • 异常处理机制
  • 安全性
    • 字节码验证
    • 无指针运算
    • 安全管理器
  • 多线程支持:内置Thread类
  • 分布式支持:完善的网络库(java.net包)
  • 动态性:反射机制支持运行时类型检查

2. Java和C++的区别有哪些?

特性JavaC++
内存管理自动垃圾回收手动new/delete
指针无显式指针支持指针操作
多重继承通过接口实现直接支持
平台依赖性跨平台(JVM)平台相关
运行速度相对较慢(JIT优化)直接编译为机器码
异常处理强制处理受检异常无异常类型限制
标准库丰富的集合框架STL
运算符重载不支持支持
预处理器支持#define等

3. 什么是JDK、JRE和JVM?它们之间有什么关系?

JVM (Java Virtual Machine)

  • 字节码执行引擎
  • 包含类加载器、执行引擎、运行时数据区等
  • 不同平台有不同实现(Windows/Linux/Mac)

JRE (Java Runtime Environment)

  • = JVM + 核心类库(如java.lang, java.util等)
  • 只能运行Java程序

JDK (Java Development Kit)

  • = JRE + 开发工具(javac, javadoc, jdb等)
  • 包含完整的Java开发环境

关系图示:
JDK ⊃ JRE ⊃ JVM

4. Java是编译型语言还是解释型语言?

Java采用混合模式

  1. 编译阶段

    • 源码(.java) → 字节码(.class)
    • 使用javac编译器
    • 生成平台无关的中间代码
  2. 执行阶段

    • 解释执行:JVM逐行解释字节码
    • JIT编译(Just-In-Time):
      • 热点代码会被编译为本地机器码
      • 显著提升性能(如HotSpot VM)

典型执行流程:
.java → (javac) → .class → (JVM) → 机器码

5. Java如何实现跨平台?

跨平台实现原理

  1. 统一中间格式

    • 编译生成标准字节码(.class文件)
    • 字节码与具体硬件/OS无关
  2. JVM适配层

    • 各平台提供对应的JVM实现
    • JVM负责将字节码转换为本地指令
  3. 分层抽象

    • 硬件层:x86/ARM等
    • 系统层:Windows/Linux/macOS
    • JVM层:统一字节码接口
    • 应用层:Java程序

关键组件:

  • 类加载器(加载字节码)
  • 字节码验证器(安全检查)
  • 解释器/JIT编译器(执行优化)

示例说明:
Windows平台:.class → windows-jvm → x86指令
Linux平台:同样的.class → linux-jvm → ARM指令

数据类型与变量面试题详解

1. Java有哪些基本数据类型?

Java的8种基本数据类型:

类型大小默认值取值范围包装类
byte1字节0-128 ~ 127Byte
short2字节0-32768 ~ 32767Short
int4字节0-2³¹ ~ 2³¹-1Integer
long8字节0L-2⁶³ ~ 2⁶³-1Long
float4字节0.0f±1.4E-45 ~ ±3.4E38(6-7位有效数字)Float
double8字节0.0d±4.9E-324 ~ ±1.7E308(15位有效数字)Double
char2字节‘\u0000’0 ~ 65535(Unicode字符)Character
boolean未定义falsetrue/falseBoolean

注意:boolean在JVM层面用int(32位)或byte(8位)实现

2. intInteger有什么区别?

对比维度intInteger
类型基本数据类型包装类对象
内存分配栈内存堆内存
默认值0null
泛型支持不能用于泛型可用于泛型(如List<Integer>
方法支持无对象方法提供parseInt()等工具方法
缓存机制-128~127缓存(IntegerCache
比较方式直接比较值需要用equals()比较值

代码示例:

int a = 100;
Integer b = 100;          // 自动装箱(实际调用Integer.valueOf(100))
Integer c = new Integer(100);  // 显式创建新对象System.out.println(a == b);       // true(自动拆箱比较值)
System.out.println(b == c);       // false(比较对象地址)
System.out.println(b.equals(c));  // true(比较实际值)

3. 什么是自动装箱和拆箱?

自动装箱(Autoboxing)
基本类型 → 包装类型(编译器的语法糖)

int num = 42;
Integer boxedNum = num;  // 等价于 Integer.valueOf(42)

自动拆箱(Unboxing)
包装类型 → 基本类型(编译器的语法糖)

Integer boxedNum = Integer.valueOf(100);
int unboxed = boxedNum;  // 等价于 boxedNum.intValue()

重要特性

  • 缓存机制:-128~127的Integer对象会被缓存
  • 性能影响:循环中频繁装箱拆箱会导致性能问题(创建多余对象)

4. StringStringBuilderStringBuffer的区别?

特性StringStringBuilderStringBuffer
可变性不可变(final char[])可变可变
线程安全天生线程安全非线程安全线程安全(synchronized方法)
性能最低(每次修改创建新对象)最高中等
使用场景常量字符串操作单线程字符串拼接多线程字符串操作

代码示例:

String str = "Hello";
str += " World";  // 创建3个对象("Hello", " World", "Hello World")StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" World");  // 只操作同一个对象StringBuffer sbf = new StringBuffer();
sbf.append("Thread").append("Safe");  // 方法添加synchronized锁

5. ==equals()的区别?

操作符/方法比较内容适用场景
==内存地址比较基本类型比较值,对象比较地址
equals()对象内容比较(可重写)对象的值比较

关键规则

  • 对于基本类型:==比较值
int x = 5, y = 5;System.out.println(x == y);  // true
  • 对于对象类型:==比较内存地址
  String s1 = new String("abc");String s2 = new String("abc");System.out.println(s1 == s2);  // false
  • equals()默认实现与==相同,但可被重写:
  System.out.println(s1.equals(s2));  // true(String类重写了equals)

重要类别的equals实现

  • String:比较字符序列内容
  • Integer:比较int值
  • 自定义类:需重写equals()和hashCode()

面向对象编程(OOP)面试题详解

1. 面向对象的四大特性是什么?

  • 封装(Encapsulation)
    将数据和操作数据的方法绑定在一起,对外隐藏实现细节
    示例:
 public class Person {private String name;  // 私有字段public String getName() {  // 公开方法return this.name;}}
  • 继承(Inheritance)
    子类继承父类的特征和行为,实现代码复用
    示例:
class Animal {}class Dog extends Animal {}  // Dog继承Animal
  • 多态(Polymorphism)
    同一操作作用于不同对象产生不同行为
    实现方式:

    • 方法重写(Override)
    • 接口实现
    • 抽象类和抽象方法
  • 抽象(Abstraction)
    提取关键特征,忽略非本质细节
    实现方式:

    • 抽象类(abstract class)
    • 接口(interface)

2. 什么是类和对象?

类(Class)

  • 对象的蓝图/模板
  • 包含:
    • 属性(字段/成员变量)
    • 行为(方法)
  • 示例:
public class Student {String name;  // 字段void study() {  // 方法System.out.println("Studying...");}
}

对象(Object)

  • 类的具体实例
  • 特点:
    • 占用独立内存空间
    • 包含类定义的所有字段副本
  • 示例:
Student s1 = new Student();  // s1是Student类的对象
s1.name = "张三";
s1.study();

3. 重载(Overload)和重写(Override)的区别?

对比维度重载(Overload)重写(Override)
定义同一类中同名不同参的方法子类重新实现父类方法
参数要求必须修改参数列表必须保持相同参数列表
返回类型可以不同必须相同或为子类
访问修饰符可以不同不能比父类更严格
抛出异常可以不同不能抛出更宽泛的异常
调用时机编译时确定运行时确定

重载示例:

class Calculator {int add(int a, int b) { return a+b; }double add(double a, double b) { return a+b; }  // 重载
}

重写示例:

class Animal {void makeSound() { System.out.println("Animal sound"); }
}
class Cat extends Animal {@Overridevoid makeSound() { System.out.println("Meow"); }  // 重写
}

4. 抽象类和接口的区别?

对比维度抽象类(abstract class)接口(interface)
定义关键字abstractinterface
方法实现可以有具体方法Java 8前只能有抽象方法
字段可以有普通字段默认public static final
构造方法可以有不能有
多继承单继承多实现(implements多个接口)
设计目的代码复用定义规范/契约
默认方法Java 8前无Java 8支持default方法

抽象类示例:

abstract class Shape {abstract void draw();  // 抽象方法void move() {  // 具体方法System.out.println("Moving shape");}
}

接口示例(Java 8+):

interface Drawable {void draw();  // 抽象方法default void print() {  // 默认方法System.out.println("Printing...");}static void help() {  // 静态方法System.out.println("Help info");}
}

5. 什么是多态?如何实现多态?

多态类型

  1. 编译时多态:方法重载
  2. 运行时多态:方法重写

实现方式

  • 继承 + 方法重写
  • 接口实现
  • 抽象类和抽象方法

典型示例:

class Animal {void sound() { System.out.println("Animal sound"); }
}
class Dog extends Animal {@Overridevoid sound() { System.out.println("Bark"); }
}
class Cat extends Animal {@Overridevoid sound() { System.out.println("Meow"); }
}// 测试多态
Animal myAnimal = new Dog();
myAnimal.sound();  // 输出"Bark"(运行时多态)myAnimal = new Cat();
myAnimal.sound();  // 输出"Meow"

6. 构造方法(Constructor)的特点和作用?

特点

  • 方法名与类名相同
  • 无返回类型(连void都没有)
  • 可以重载(多个不同参数的构造方法)
  • 默认提供无参构造(如果没定义任何构造方法)
  • 不能被static/final/abstract修饰

作用

  • 初始化对象状态
  • 为对象字段赋初值
  • 执行必要的启动操作

示例:

public class Person {String name;int age;// 无参构造public Person() {this.name = "Unknown";this.age = 0;}// 带参构造public Person(String name, int age) {this.name = name;this.age = age;}
}// 使用构造方法
Person p1 = new Person();  // 调用无参构造
Person p2 = new Person("Alice", 25);  // 调用带参构造

7. this和super关键字的区别?

对比维度thissuper
指向当前对象实例父类对象引用
用途1. 解决局部变量与字段同名问题
2. 调用本类其他构造方法(this())
1. 访问父类成员
2. 调用父类构造方法(super())
调用限制必须在构造方法第一行必须在子类构造方法第一行

this示例:

class Student {String name;Student(String name) {this.name = name;  // 区分字段和参数}Student() {this("Unknown");  // 调用其他构造方法}
}

super示例:

class Parent {String familyName = "Smith";
}
class Child extends Parent {String fullName;Child(String firstName) {super();  // 调用父类构造方法(可省略)this.fullName = firstName + " " + super.familyName;}
}

8. 什么是静态变量和静态方法?

静态变量(类变量)

  • static修饰
  • 属于类而非对象
  • 所有实例共享同一份拷贝
  • 通过类名.变量名访问

静态方法(类方法)

  • static修饰
  • 不能访问非静态成员
  • 不能使用this/super
  • 通过类名.方法名()调用

示例:

class Counter {static int count = 0;  // 静态变量int instanceCount = 0;  // 实例变量Counter() {count++;instanceCount++;}static void printCount() {  // 静态方法System.out.println("Total count: " + count);// System.out.println(instanceCount);  // 错误!不能访问非静态成员}
}// 使用
Counter c1 = new Counter();
Counter c2 = new Counter();
Counter.printCount();  // 输出"Total count: 2"
System.out.println(c1.instanceCount);  // 输出1

9. final、finally和finalize的区别?

关键字用途作用场景
final修饰符1. 修饰类:不可继承
2. 修饰方法:不可重写
3. 修饰变量:常量(不可修改)
finally异常处理块try-catch-finally结构中,无论是否发生异常都会执行的代码块
finalizeObject类的方法垃圾回收前调用的清理方法(不推荐使用)

final示例:

final class Immutable {  // 不可继承的类final int VALUE = 100;  // 常量final void method() {  // 不可重写的方法// ...}
}

finally示例:

try {// 可能抛出异常的代码
} catch (Exception e) {// 异常处理
} finally {// 一定会执行的代码(如释放资源)System.out.println("Cleanup");
}

finalize示例(已废弃):

@Override
protected void finalize() throws Throwable {try {// 资源清理} finally {super.finalize();}
}

异常处理面试题详解

1. Java异常体系结构是怎样的?

Java异常类的继承体系:

Throwable (所有错误和异常的基类)
├── Error (严重错误,程序无法处理)
│ ├── VirtualMachineError (JVM错误)
│ │ ├── OutOfMemoryError
│ │ └── StackOverflowError
│ └── LinkageError (类链接错误)
└── Exception (可以捕获处理的异常)
├── RuntimeException (运行时异常)
│ ├── NullPointerException
│ ├── IndexOutOfBoundsException
│ └── IllegalArgumentException
└── 其他非运行时异常
├── IOException
│ ├── FileNotFoundException
│ └── EOFException
└── SQLException

关键特点:

  • Error表示系统级错误,通常不应捕获
  • Exception分为检查型异常和非检查型异常
  • 自定义异常应继承ExceptionRuntimeException

2. Error和Exception的区别?

对比维度ErrorException
严重程度严重系统错误程序可处理的异常情况
是否可恢复通常不可恢复多数情况下可以恢复
是否强制捕获不需要捕获检查型异常必须捕获或声明
来源JVM或底层系统资源问题程序逻辑问题或外部条件
典型例子OutOfMemoryErrorNullPointerException
StackOverflowErrorFileNotFoundException

Error示例:

// 递归调用导致栈溢出
public static void infiniteRecursion() {infiniteRecursion();
}
// 调用后会抛出StackOverflowError

Exception示例:

try {FileInputStream fis = new FileInputStream("nonexistent.txt");
} catch (FileNotFoundException e) {System.out.println("文件未找到");
}

3. 受检异常和非受检异常的区别?

对比维度受检异常(Checked Exception)非受检异常(Unchecked Exception)
继承关系继承自Exception但不包括RuntimeException继承自RuntimeException或Error
处理要求必须捕获或声明抛出不强制要求处理
编译检查编译器会检查编译器不检查
设计目的可预期的异常情况程序错误或不可控情况
典型例子IOExceptionNullPointerException
SQLExceptionArrayIndexOutOfBoundsException

受检异常示例:

// 必须处理(捕获或声明throws)
try {Class.forName("com.example.NonExistClass");
} catch (ClassNotFoundException e) {e.printStackTrace();
}

非受检异常示例:

// 可以不处理,但会导致程序终止
String str = null;
System.out.println(str.length());  // 抛出NullPointerException

4. try-catch-finally的执行顺序?

标准执行流程:

  1. 执行try块中的代码
  2. 如果发生异常:
    a. 跳转到匹配的catch块
    b. 执行catch块中的异常处理
  3. 无论是否发生异常:
    a. 都会执行finally块中的代码
  4. 继续执行后续代码

特殊场景:

  • try或catch中有return:先执行finally再return
  • finally中有return:会覆盖try/catch中的return值
  • System.exit():会立即终止程序,不执行finally

执行顺序示例:

public static int testFinally() {try {System.out.println("try block");return 1;} catch (Exception e) {System.out.println("catch block");return 2;} finally {System.out.println("finally block");// return 3;  // 如果取消注释,将返回3而不是1}
}
// 输出顺序:
// try block
// finally block
// 返回值为1

5. throw和throws的区别?

对比维度throwthrows
语法位置方法体内使用方法声明处使用
作用主动抛出异常对象声明方法可能抛出的异常类型
抛出数量每次只能抛出一个异常对象可以声明多个异常类型
处理要求必须显式创建异常对象只是声明,不创建异常对象
使用场景在方法内部抛出具体异常在方法签名中声明可能抛出的异常

throw示例:

public void validateAge(int age) {if (age < 0) {throw new IllegalArgumentException("年龄不能为负数");}
}

throws示例:

public void readFile() throws FileNotFoundException, IOException {FileInputStream fis = new FileInputStream("test.txt");// 其他IO操作
}

组合使用示例:

public void transferMoney(double amount) throws InsufficientBalanceException {if (balance < amount) {throw new InsufficientBalanceException("余额不足");}// 转账逻辑...
}

最佳实践:

  1. 受检异常必须在方法签名中声明
  2. 不要滥用throws声明所有异常
  3. 自定义异常应提供有意义的错误信息
  4. 在catch块中通常应该记录异常日志

集合框架面试题详解

1. Java集合框架的主要接口和类有哪些?

核心接口

  • Collection (所有集合的根接口)
    • List (有序可重复集合)
    • Set (无序不可重复集合)
    • Queue (队列)
  • Map (键值对集合)

常用实现类

  • List实现类:

    • ArrayList (基于动态数组)
    • LinkedList (基于双向链表)
    • Vector (线程安全版ArrayList)
    • Stack (继承自Vector)
  • Set实现类:

    • HashSet (基于HashMap)
    • LinkedHashSet (保持插入顺序)
    • TreeSet (基于红黑树,有序)
  • Map实现类:

    • HashMap (基于哈希表)
    • LinkedHashMap (保持插入顺序)
    • TreeMap (基于红黑树,按键排序)
    • Hashtable (线程安全,已过时)
    • ConcurrentHashMap (线程安全优化版)
  • Queue实现类:

    • PriorityQueue (优先级队列)
    • ArrayDeque (双端队列)

2. List、Set和Map的区别?

特性ListSetMap
元素重复性允许重复元素不允许重复元素Key不允许重复,Value允许重复
有序性保持插入顺序通常无序(HashSet)通常无序(HashMap)
实现基础动态数组/链表基于Map实现哈希表/红黑树
访问方式通过索引访问只能迭代访问通过Key访问Value
典型实现ArrayList, LinkedListHashSet, TreeSetHashMap, TreeMap
null支持允许null元素最多一个null(HashSet)HashMap允许一个null key

3. ArrayList和LinkedList的区别?

对比维度ArrayListLinkedList
底层结构动态数组双向链表
内存占用连续内存,占用较少节点存储,占用更多
访问效率O(1)随机访问O(n)顺序访问
插入删除尾部操作O(1),中间O(n)头尾操作O(1),中间O(n)
扩容机制默认扩容50%无需扩容
适用场景频繁随机访问频繁插入删除

代码示例:

// ArrayList访问示例
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("A");
arrayList.get(0);  // O(1)快速访问// LinkedList插入示例
LinkedList<String> linkedList = new LinkedList<>();
linkedList.addFirst("B");  // O(1)头部插入

4. HashMap的工作原理?

核心机制

  1. 哈希计算

    • 调用key的hashCode()计算哈希值
    • 通过扰动函数减少哈希冲突
    • 公式:index = (n - 1) & hash (n为数组长度)
  2. 存储结构

    • Java 8前:数组+链表
    • Java 8+:数组+链表/红黑树(链表长度>8时转换)
  3. put流程

    • 计算key的hash值
    • 如果数组为空,初始化(默认16)
    • 如果对应位置为空,直接插入
    • 如果存在冲突:
      • 链表:遍历查找,存在则覆盖,否则尾插
      • 红黑树:树形查找插入
    • 超过阈值(默认0.75)则扩容2倍
  4. get流程

    • 计算key的hash值
    • 定位数组下标
    • 遍历链表/红黑树查找

关键参数:

  • 默认容量:16
  • 负载因子:0.75
  • 树化阈值:8
  • 解树化阈值:6

5. HashMap和HashTable的区别?

对比维度HashMapHashTable
线程安全非线程安全线程安全(synchronized方法)
性能更高较低
null支持允许一个null key和多个null值不允许null键值
继承关系继承AbstractMap继承Dictionary
迭代器Fail-FastFail-Safe
初始容量1611
扩容机制2倍扩容2倍+1扩容

HashMap示例:

HashMap<String, Integer> map = new HashMap<>();
map.put(null, 1);  // 允许
map.put("key", null);  // 允许

HashTable示例:

Hashtable<String, Integer> table = new Hashtable<>();
// table.put(null, 1);  // 抛出NullPointerException
// table.put("key", null);  // 抛出NullPointerException

6. ConcurrentHashMap如何保证线程安全?

Java 7 及之前的 ConcurrentHashMap 实现

核心设计

  • 分段锁(Segment)机制
    • 将整个Map分成多个Segment(默认16个)
    • 每个Segment继承ReentrantLock
    • 不同Segment可并发操作

数据结构

static final class Segment<K,V> extends ReentrantLock {transient volatile HashEntry<K,V>[] table;// 其他字段...
}static final class HashEntry<K,V> {final K key;volatile V value;volatile HashEntry<K,V> next;
}

主要特点

  1. 并发度(Concurrency Level)

    • 可指定Segment数量(默认16)
    • 实际并发量受限于Segment数量
  2. 锁粒度

    • 只锁定单个Segment
    • 不同Segment的操作可并行
  3. 读操作

    • 完全无锁(volatile变量保证可见性)
    • 弱一致性迭代器
  4. 扩容

    • 单个Segment独立扩容
    • 不影响其他Segment访问

代码示例

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);  // 只锁定对应Segment

缺点

  1. 内存占用较大(每个Segment都有独立数据结构)
  2. 并发度固定,无法动态扩展
  3. 某些场景下仍存在竞争

Java 8 及之后的 ConcurrentHashMap 实现

核心改进

  • 废弃分段锁,改用:
    • CAS + synchronized 锁单个Node
    • 更细粒度的锁控制

数据结构

  • 数组 + 链表/红黑树(类似HashMap)
  • Node定义:
  static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;volatile V val;volatile Node<K,V> next;}

主要优化

  1. 锁粒度更细

    • 只锁定链表头节点/树根节点
    • 并发度理论上等于桶数组大小
  2. CAS操作

    • 无竞争时使用CAS更新
    • 减少锁的使用
  3. 扩容机制

    • 多线程协同扩容
    • 使用ForwardingNode标记迁移状态
  4. 计数机制

    • 采用LongAdder思想
    • 分散计数热点

关键方法实现

  1. putVal()流程

    • 计算hash值
    • 如果表为空则初始化
    • 如果桶为空则CAS插入
    • 如果存在hash冲突:
      • 链表:synchronized锁定头节点
      • 红黑树:锁定树根节点
    • 超过阈值则扩容
  2. get()流程

    • 根据hash定位到桶
    • 遍历链表/红黑树查找
    • 完全无锁(依赖volatile读)

代码示例

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.computeIfAbsent("key", k -> 1);  // 原子操作

性能对比

操作Java 7Java 8+
低并发put中等(分段锁开销)高(CAS优化)
高并发put中等(受限于并发度)高(更细粒度锁)
get高(无锁)高(无锁)
内存占用较高(分段结构)较低(统一结构)
扩容效率分段独立扩容多线程协同扩容

重要内部类变化

Java 7

  • Segment:继承ReentrantLock
  • HashEntry:链表节点

Java 8

  • Node:基础链表节点
  • TreeNode:红黑树节点
  • TreeBin:红黑树容器
  • ForwardingNode:扩容转发节点
  • ReservationNode:占位节点

并发控制机制对比

Java 7

  • 依赖Segment锁
  • put操作必须获取锁
  • size()方法较复杂(需统计各Segment)

Java 8

  • CAS + synchronized
  • put操作可能无锁(CAS成功时)
  • size()使用CounterCell减少竞争

为什么Java 8要重新设计?

  1. 锁粒度问题

    • Segment数量固定导致并发度受限
    • 实际场景中不同Segment的负载不均衡
  2. 内存占用

    • Segment结构带来额外内存开销
  3. 现代CPU架构

    • CAS操作在多数场景下比锁更高效
    • 减少线程上下文切换
  4. 技术演进

    • JVM对synchronized的优化(偏向锁、轻量级锁)
    • 借鉴了LongAdder的分段计数思想

典型使用场景建议

Java 7版本

  • 读多写少的并发场景
  • 键的hash分布均匀的情况

Java 8+版本

  • 高并发写入场景
  • 数据量大的情况
  • 需要原子复合操作(computeIfAbsent等)

通用最佳实践

  1. 合理设置初始容量(避免频繁扩容)
  2. 避免在遍历时修改Map
  3. 使用并发安全的方法(如computeIfAbsent)
  4. 监控并发性能指标

多线程面试题详解

1. 线程和进程的区别?

对比维度进程线程
资源分配操作系统分配资源的基本单位CPU调度的基本单位
内存空间每个进程有独立内存空间同一进程的线程共享内存
创建开销大(需要分配独立资源)小(共享进程资源)
通信方式管道、消息队列、共享内存等直接读写共享变量
稳定性进程崩溃不影响其他进程线程崩溃可能导致整个进程退出
并发性进程间并发线程间并发
数量限制通常几十个通常数百到数千个

关键点:

  • 进程是程序的一次执行,线程是进程内的执行单元
  • Java中无法直接操作进程,只能通过Runtime.exec()等有限方式

2. 创建线程的几种方式?

1. 继承Thread类

class MyThread extends Thread {@Overridepublic void run() {System.out.println("Thread running");}
}
// 使用
new MyThread().start();

2. 实现Runnable接口

class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("Runnable running");}
}
// 使用
new Thread(new MyRunnable()).start();

3. 实现Callable接口(带返回值)

class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {return "Callable result";}
}
// 使用
FutureTask<String> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
String result = task.get(); // 获取返回值

4. 使用线程池(推荐)

ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(() -> {System.out.println("Pool thread running");
});
executor.shutdown();

3. Runnable和Callable的区别?

对比维度RunnableCallable
返回值
异常处理不能抛出受检异常可以抛出异常
使用场景简单异步任务需要返回结果的任务
引入版本Java 1.0Java 5.0
函数式接口
执行方式Thread或Executor.execute()ExecutorService.submit()

4. 线程的生命周期(状态)有哪些?

Java线程的6种状态(Thread.State枚举):

  1. NEW(新建)

    • 线程被创建但未调用start()
  2. RUNNABLE(可运行)

    • 包括操作系统中的Ready和Running状态
    • 可能正在执行或等待CPU时间片
  3. BLOCKED(阻塞)

    • 等待获取监视器锁(synchronized)
    • 只有这种状态会竞争锁
  4. WAITING(等待)

    • 无限期等待其他线程显式唤醒
    • 通过以下方法进入:
      • Object.wait()
      • Thread.join()
      • LockSupport.park()
  5. TIMED_WAITING(超时等待)

    • 带时间限制的等待
    • 通过以下方法进入:
      • Thread.sleep(long)
      • Object.wait(long)
      • Thread.join(long)
  6. TERMINATED(终止)

    • 线程执行完毕

状态转换图:
NEW → start() → RUNNABLE
RUNNABLE → 获取锁 → BLOCKED
RUNNABLE → wait()/join() → WAITING
RUNNABLE → sleep()/wait(n) → TIMED_WAITING
各种等待状态 → 条件满足 → RUNNABLE
RUNNABLE → 执行完成 → TERMINATED

5. sleep()和wait()的区别?

对比维度sleep()wait()
所属类Thread的静态方法Object的实例方法
锁释放不释放锁释放锁
唤醒条件时间到期notify()/notifyAll()或超时
使用位置任意位置必须在同步块内(持有锁时)
异常处理需要处理InterruptedException需要处理InterruptedException
精度控制支持纳秒参数支持纳秒参数

6. synchronized关键字的用法?

三种使用方式

  1. 实例方法同步
public synchronized void method() {// 锁定当前实例
}
  1. 静态方法同步
public static synchronized void staticMethod() {// 锁定当前类的Class对象
}
  1. 同步代码块
public void block() {synchronized(obj) {  // 锁定指定对象// 临界区代码}
}

锁的特性

  • 可重入性:同一线程可重复获取同一把锁
  • 非公平性:不保证等待线程的获取顺序
  • 独占性:同一时间只有一个线程能持有锁
  • 不可中断性:等待锁的线程不能被中断

锁升级过程(Java 6+优化)
无锁 → 偏向锁 → 轻量级锁(CAS) → 重量级锁

7. volatile关键字的作用?

两大核心作用

  1. 可见性保证

    • 写操作立即刷新到主内存
    • 读操作直接从主内存读取
  2. 禁止指令重排序

    • 通过内存屏障实现
    • 保证happens-before关系

适用场景

  • 状态标志位(如while(!stop))
  • 单例模式的双重检查锁定
  • 一次性安全发布

不适用场景

  • 非原子操作的复合操作(如i++)
  • 需要互斥访问的临界区

示例:

class VolatileExample {private volatile boolean flag = false;public void writer() {flag = true;  // 写操作对其他线程立即可见}public void reader() {while(!flag) {}  // 能及时看到flag变化System.out.println("Flag changed");}
}

8. 什么是线程池?为什么要用线程池?

线程池核心组件

  • 工作线程(Worker):实际执行任务的线程
  • 任务队列(BlockingQueue):存放待执行任务
  • 线程工厂(ThreadFactory):创建新线程
  • 拒绝策略(RejectedExecutionHandler):处理任务过多情况

线程池优势

  1. 降低资源消耗:复用已创建的线程
  2. 提高响应速度:任务到达可直接执行
  3. 提高线程管理性:统一分配、调优和监控
  4. 防止资源耗尽:通过队列和拒绝策略

ThreadPoolExecutor核心参数

public ThreadPoolExecutor(int corePoolSize,      // 核心线程数int maximumPoolSize,  // 最大线程数long keepAliveTime,   // 空闲线程存活时间TimeUnit unit,        // 时间单位BlockingQueue<Runnable> workQueue,  // 任务队列ThreadFactory threadFactory,        // 线程工厂RejectedExecutionHandler handler   // 拒绝策略
)

四种常用线程池

1. newFixedThreadPool:固定大小线程池
2. newCachedThreadPool:可扩容线程池
3. newSingleThreadExecutor:单线程池
4. newScheduledThreadPool:定时任务线程池

执行流程

  1. 提交任务
  2. 如果核心线程未满,创建新线程执行
  3. 如果核心线程已满,加入任务队列
  4. 如果队列已满且线程数未达最大值,创建新线程
  5. 如果队列已满且线程数已达最大值,执行拒绝策略

拒绝策略

  1. AbortPolicy(默认):抛出RejectedExecutionException
  2. CallerRunsPolicy:由调用线程直接执行
  3. DiscardPolicy:直接丢弃任务
  4. DiscardOldestPolicy:丢弃队列最老的任务

示例代码:

ExecutorService pool = Executors.newFixedThreadPool(5);
pool.execute(() -> {System.out.println(Thread.currentThread().getName() + " executing task");
});
pool.shutdown();

IO与NIO面试题详解

1. Java IO流分为哪几种?

按数据流向分类

  • 输入流(InputStream/Reader
  • 输出流(OutputStream/Writer

按数据类型分类

  1. 字节流(8位字节):

    • InputStream(抽象基类)
      • FileInputStream
      • ByteArrayInputStream
      • BufferedInputStream
      • ObjectInputStream
    • OutputStream(抽象基类)
      • FileOutputStream
      • ByteArrayOutputStream
      • BufferedOutputStream
      • ObjectOutputStream
  2. 字符流(16位Unicode):

    • Reader(抽象基类)
      • FileReader
      • CharArrayReader
      • BufferedReader
      • InputStreamReader
    • Writer(抽象基类)
      • FileWriter
      • CharArrayWriter
      • BufferedWriter
      • OutputStreamWriter

按功能分类

  • 节点流(直接操作数据源)
  • 处理流(包装其他流,提供增强功能)

典型类结构:
InputStream
├── FileInputStream
├── FilterInputStream
│ ├── BufferedInputStream
│ └── DataInputStream
└── ObjectInputStream

Reader
├── FileReader
├── BufferedReader
└── InputStreamReader

2. 字节流和字符流的区别?

对比维度字节流字符流
基本单位8位字节16位Unicode字符
处理类型二进制数据(如图片、音频)文本数据
基类InputStream/OutputStreamReader/Writer
缓冲区通常无内置缓冲通常内置缓冲
编码转换不处理编码自动处理字符编码转换
典型场景文件复制、网络数据传输文本文件读写
性能处理二进制更快处理文本更方便

转换流示例:

// 字节流转字符流(指定编码)
InputStreamReader isr = new InputStreamReader(new FileInputStream("file.txt"), "UTF-8");

// 字符流转字节流

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("file.txt"), "GBK");

3. BIO、NIO和AIO的区别?

对比维度BIO (Blocking IO)NIO (Non-blocking IO)AIO (Asynchronous IO)
IO模型同步阻塞同步非阻塞异步非阻塞
数据准备线程阻塞等待数据就绪轮询检查数据就绪状态OS通知数据就绪
处理方式面向流(Stream)面向缓冲区(Buffer)面向事件和回调
线程开销每个连接需要独立线程单线程处理多连接回调机制减少线程占用
吞吐量低(线程上下文切换开销)高(减少线程数量)最高(无等待时间)
适用场景连接数少的固定架构连接数多且连接时间短连接数多且连接时间长
JDK支持Java 1.0Java 1.4Java 7

BIO示例:

ServerSocket server = new ServerSocket(8080);
while(true) {Socket client = server.accept();  // 阻塞new Thread(() -> handleClient(client)).start();
}

NIO示例:

Selector selector = Selector.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
while(true) {int ready = selector.select();  // 非阻塞Set<SelectionKey> keys = selector.selectedKeys();// 处理就绪的通道
}

4. NIO的核心组件有哪些?

1. Buffer(缓冲区)

  • 数据容器,提供结构化访问
  • 核心实现类:
    • ByteBuffer(最常用)
    • CharBuffer
    • IntBuffer
    • 等基本类型Buffer
  • 重要属性:
    • capacity(容量)
    • position(当前位置)
    • limit(读写限制)
    • mark(标记位置)

2. Channel(通道)

  • 双向数据传输管道
  • 主要实现:
    • FileChannel(文件IO)
    • SocketChannel(TCP)
    • ServerSocketChannel(TCP服务端)
    • DatagramChannel(UDP)
  • 特点:
    • 可异步读写
    • 必须配合Buffer使用

3. Selector(选择器)

  • 多路复用器,单线程管理多个Channel
  • 核心方法:
    • open():创建Selector
    • select():监控注册的Channel
    • selectedKeys():获取就绪的SelectionKey集合
  • 事件类型:
    • OP_ACCEPT:连接接受
    • OP_CONNECT:连接就绪
    • OP_READ:读就绪
    • OP_WRITE:写就绪

4. Charset(字符集)

  • 处理字符编码/解码
  • 常用字符集:
    • StandardCharsets.UTF_8
    • StandardCharsets.ISO_8859_1
    • StandardCharsets.US_ASCII

NIO文件复制示例:

FileChannel inChannel = new FileInputStream("source.txt").getChannel();
FileChannel outChannel = new FileOutputStream("target.txt").getChannel();ByteBuffer buffer = ByteBuffer.allocate(1024);
while(inChannel.read(buffer) != -1) {buffer.flip();  // 切换为读模式outChannel.write(buffer);buffer.clear(); // 清空缓冲区
}

NIO网络编程示例:

Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT);while(true) {selector.select();Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> iter = keys.iterator();while(iter.hasNext()) {SelectionKey key = iter.next();if(key.isAcceptable()) {// 处理新连接SocketChannel client = server.accept();client.configureBlocking(false);client.register(selector, SelectionKey.OP_READ);} else if(key.isReadable()) {// 处理读事件SocketChannel client = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);client.read(buffer);// 处理数据...}iter.remove();}
}

反射与泛型面试题详解

1. 什么是反射?反射的应用场景?

反射(Reflection)定义

  • 在运行时动态获取类的完整结构信息
  • 动态操作类属性、方法和构造器
  • 核心类:ClassFieldMethodConstructor

主要功能

  1. 运行时获取类的修饰符、父类、接口、注解等信息
  2. 运行时构造任意类的对象
  3. 运行时调用任意对象的方法
  4. 运行时访问/修改字段值(包括private字段)
  5. 动态创建和操作数组

应用场景

  • 框架开发(Spring的IoC容器)
  • 动态代理(AOP实现)
  • 注解处理器
  • 通用工具类(如BeanUtils)
  • 序列化/反序列化
  • IDE的代码提示

示例代码
// 获取类信息
Class<?> clazz = Class.forName(“java.lang.String”);
Field[] fields = clazz.getDeclaredFields();

// 创建实例
Constructor<?> constructor = clazz.getConstructor(String.class);
Object str = constructor.newInstance(“Hello”);

// 调用方法
Method method = clazz.getMethod(“length”);
int len = (int) method.invoke(str);

2. 如何获取Class对象?

4种获取方式

  1. 类名.class(最安全高效)
    Class stringClass = String.class;

  2. 对象.getClass()
    String s = “”;
    Class<?> clazz = s.getClass();

  3. Class.forName()(最灵活)
    Class<?> clazz = Class.forName(“java.lang.String”);

  4. 类加载器.loadClass()
    Class<?> clazz = MyClass.class.getClassLoader().loadClass(“com.example.MyClass”);

区别对比

方式是否初始化类适用场景
类名.class编译时已知类名
getClass()已有对象实例
Class.forName()动态加载类(如JDBC驱动)
ClassLoader.loadClass()需要控制类加载过程

3. 什么是泛型?泛型擦除是什么意思?

泛型(Generics)定义

  • 参数化类型,将类型作为参数
  • 主要作用:
    • 类型安全(编译时检查)
    • 消除强制类型转换
    • 代码复用(通用算法)

泛型擦除(Type Erasure)

  • Java编译器在编译时去除泛型类型信息
  • 运行时只保留原始类型(Raw Type)
  • 所有泛型类型最终都会被替换为Object或上界类型

擦除规则

  1. 无界泛型:List<T>List
  2. 有界泛型:List<T extends Number>List<Number>
  3. 泛型方法:类型参数会被擦除到第一个边界

示例说明
// 编译前
List list = new ArrayList<>();
list.add(“hello”);
String s = list.get(0);

// 编译后(擦除后)
List list = new ArrayList();
list.add(“hello”);
String s = (String) list.get(0); // 强制转换

Java 8新特性面试题详解

1. Lambda表达式的作用?

核心作用

  • 简化匿名内部类的写法
  • 实现函数式编程风格
  • 使代码更简洁、可读性更强

语法结构
(parameters) -> expression

(parameters) -> { statements; }

特点

  1. 可选类型声明:编译器可推断参数类型
  2. 可选参数圆括号:单参数时可省略
  3. 可选大括号:单条语句时可省略
  4. 可选return关键字:单条表达式时自动返回

使用示例

// 替代匿名类
Runnable r1 = new Runnable() {@Overridepublic void run() {System.out.println("Old way");}
};Runnable r2 = () -> System.out.println("Lambda way");// 集合操作
list.forEach(item -> System.out.println(item));

2. Stream API的常用方法有哪些?

常用中间操作

  • filter(Predicate):过滤元素
  • map(Function):元素转换
  • flatMap(Function):扁平化流
  • distinct():去重
  • sorted():排序
  • peek(Consumer):查看元素但不修改

常用终端操作

  • forEach(Consumer):遍历
  • collect(Collector):转换为集合
  • toArray():转换为数组
  • reduce():归约操作
  • count():计数
  • anyMatch()/allMatch()/noneMatch():匹配检查
  • findFirst()/findAny():查找元素

示例代码

List<String> result = list.stream().filter(s -> s.length() > 3).map(String::toUpperCase).sorted().collect(Collectors.toList());long count = list.stream().distinct().count();

3. 什么是函数式接口?常见的函数式接口有哪些?

函数式接口定义

  • 只有一个抽象方法的接口
  • 可用@FunctionalInterface注解标记
  • Lambda表达式的目标类型

四大核心函数式接口

  1. Consumer

    • 消费型:接受输入无返回
    • 方法:void accept(T t)
    • 示例:list.forEach(System.out::println)
  2. Supplier

    • 供给型:无输入有返回
    • 方法:T get()
    • 示例:() -> new Random().nextInt()
  3. Function<T,R>

    • 函数型:接受T返回R
    • 方法:R apply(T t)
    • 示例:String::length
  4. Predicate

    • 断言型:接受T返回boolean
    • 方法:boolean test(T t)
    • 示例:s -> s.startsWith("A")

其他常用接口

  • UnaryOperator<T>:一元操作(继承Function)
  • BinaryOperator<T>:二元操作
  • BiFunction<T,U,R>:双参数函数
  • BiConsumer<T,U>:双参数消费者

4. Optional类的作用?

设计目的

  • 优雅处理null值问题
  • 避免NullPointerException
  • 明确表示"值可能不存在"的语义

核心方法

  1. 创建:

    • Optional.of(value)(非null值)
    • Optional.ofNullable(value)(可null值)
    • Optional.empty()(空实例)
  2. 值处理:

    • get():获取值(不安全)
    • orElse(default):获取值或默认值
    • orElseGet(supplier):获取值或计算默认值
    • orElseThrow(exceptionSupplier):获取值或抛异常
  3. 条件处理:

    • isPresent():判断值是否存在
    • ifPresent(consumer):值存在时执行操作
    • filter(predicate):过滤值
    • map(function):值转换

使用示例

// 传统null检查
if(user != null) {Address address = user.getAddress();if(address != null) {return address.getStreet();}
}
return "unknown";// Optional方式
return Optional.ofNullable(user).map(User::getAddress).map(Address::getStreet).orElse("unknown");

最佳实践

  1. 不要用Optional作为方法参数
  2. 不要用Optional.get()而不检查isPresent()
  3. 优先使用orElse/orElseGet替代get()
  4. 集合返回空集合而非Optional

JVM基础面试题详解

1. JVM内存结构是怎样的?

JVM内存主要分区

  1. 程序计数器(PC Register)

    • 线程私有
    • 记录当前线程执行的字节码行号
    • 唯一不会OOM的区域
  2. Java虚拟机栈(Java Stack)

    • 线程私有
    • 存储栈帧(方法调用的局部变量、操作数栈等)
    • 可能抛出StackOverflowError/OutOfMemoryError
  3. 本地方法栈(Native Stack)

    • 线程私有
    • 为Native方法服务
  4. 堆(Heap)

    • 线程共享
    • 存储对象实例和数组
    • GC主要工作区域
    • 分代设计(新生代/老年代)
  5. 方法区(Method Area)

    • 线程共享
    • 存储类信息、常量、静态变量等
    • JDK8后由元空间(Metaspace)实现
  6. 运行时常量池(Runtime Constant Pool)

    • 方法区的一部分
    • 存储编译期生成的字面量和符号引用

内存结构图示
[JVM内存结构]
线程私有区:

  • 程序计数器
  • Java栈
  • 本地方法栈

线程共享区:

  • 方法区
    • 运行时常量池

2. 什么是垃圾回收(GC)?常见的垃圾回收算法有哪些?

GC核心概念

  • 自动管理堆内存
  • 识别并回收不再使用的对象
  • 防止内存泄漏

判断对象可回收的算法

  1. 引用计数法

    • 对象被引用时计数器+1
    • 为0时回收
    • 问题:循环引用无法处理
  2. 可达性分析(主流)

    • 从GC Roots对象开始遍历引用链
    • 不可达对象判定为可回收
    • GC Roots包括:
      • 虚拟机栈引用的对象
      • 方法区静态属性引用的对象
      • 方法区常量引用的对象
      • Native方法引用的对象

垃圾回收算法

  1. 标记-清除(Mark-Sweep)

    • 步骤:标记可回收对象 → 清除
    • 问题:内存碎片
  2. 复制算法(Copying)

    • 将存活对象复制到另一半空间
    • 适用场景:新生代(Edem:Survivor=8:1)
    • 优点:无碎片
    • 缺点:空间利用率低
  3. 标记-整理(Mark-Compact)

    • 步骤:标记 → 存活对象向一端移动 → 清理边界
    • 适用场景:老年代
    • 优点:无碎片
    • 缺点:移动成本高
  4. 分代收集(Generational Collection)

    • 新生代:复制算法
    • 老年代:标记-清除/标记-整理
    • 现代JVM主流策略

GC触发条件

  • Minor GC:Eden区满
  • Full GC:
    • 老年代空间不足
    • 方法区空间不足
    • System.gc()调用

3. 强引用、软引用、弱引用和虚引用的区别?

引用类型回收时机用途实现类
强引用永不回收(即使OOM)普通对象引用默认
软引用内存不足时回收内存敏感缓存SoftReference
弱引用下次GC时回收规范映射/临时缓存WeakReference
虚引用随时可能回收对象回收跟踪PhantomReference

代码示例

// 强引用
Object obj = new Object();// 软引用
SoftReference<Object> softRef = new SoftReference<>(new Object());// 弱引用
WeakReference<Object> weakRef = new WeakReference<>(new Object());// 虚引用
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);

引用队列(ReferenceQueue)

  • 配合软/弱/虚引用使用
  • 被引用对象回收后,引用对象入队
  • 用于执行后续清理操作

4. 类加载过程是怎样的?

类加载的生命周期

  1. 加载(Loading)
  2. 验证(Verification)
  3. 准备(Preparation)
  4. 解析(Resolution)
  5. 初始化(Initialization)
  6. 使用(Using)
  7. 卸载(Unloading)

详细过程

  1. 加载

    • 通过全限定名获取二进制字节流
    • 将字节流转化为方法区的运行时数据结构
    • 在堆中生成Class对象作为访问入口
  2. 验证

    • 文件格式验证(魔数等)
    • 元数据验证(语义分析)
    • 字节码验证(StackMapTable)
    • 符号引用验证
  3. 准备

    • 为类变量(static)分配内存
    • 设置初始值(零值)
    • final static变量直接赋实际值
  4. 解析

    • 将符号引用转为直接引用
    • 类/接口解析
    • 字段解析
    • 方法解析
  5. 初始化

    • 执行类构造器()
    • 父类先初始化
    • 线程安全执行

类加载器

  1. 启动类加载器(Bootstrap)

    • 加载JAVA_HOME/lib下的核心类
    • 由C++实现,无Java对应类
  2. 扩展类加载器(Extension)

    • 加载JAVA_HOME/lib/ext下的类
    • Java实现,sun.misc.Launcher$ExtClassLoader
  3. 应用类加载器(Application)

    • 加载classpath下的类
    • Java实现,sun.misc.Launcher$AppClassLoader
  4. 自定义类加载器

    • 继承ClassLoader实现

双亲委派模型

  1. 子加载器先委托父加载器加载
  2. 父加载器无法完成时子加载器尝试
  3. 防止核心类被篡改
  4. 避免重复加载

打破双亲委派的场景

  • SPI服务加载(如JDBC)
  • OSGi模块化
  • 热部署实现

类初始化触发条件

  1. new/getstatic/putstatic/invokestatic指令
  2. 反射调用
  3. 初始化子类时父类未初始化
  4. 主类(包含main()的类)
  5. MethodHandle调用

Java Stream 补充面试题详解

1. Stream 的中间操作和终止操作有什么区别?

特性中间操作终止操作
返回值返回新Stream返回非Stream结果或void
执行时机延迟执行(lazy)触发实际计算
可链式调用
示例filter(), map()collect(), forEach()

重要特点

  • 一个Stream只能有一个终止操作
  • 中间操作可以多个连续调用
  • 没有终止操作时,中间操作不会执行

2. 并行流(Parallel Stream)使用时要注意什么?

使用方式

List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.parallelStream().forEach(System.out::println);

注意事项

  1. 线程安全

    • 确保共享变量是线程安全的
    • 避免在lambda中修改外部状态
  2. 性能考量

    • 数据量小时可能比串行流更慢
    • 适合CPU密集型操作
    • 不适合IO密集型操作
  3. 顺序问题

    • forEachOrdered()保证顺序
    • 某些操作(如findAny)在并行流中行为不同
  4. 资源控制

    • 默认使用ForkJoinPool.commonPool()
    • 可自定义线程池:
      ForkJoinPool customPool = new ForkJoinPool(4);
      customPool.submit(() -> list.parallelStream().forEach(…));

3. Stream 和 Collection 的区别?

维度StreamCollection
数据存储不存储数据存储所有元素
数据处理函数式操作命令式操作
遍历次数一次性(用完即闭)可多次遍历
延迟执行支持不支持
并行处理原生支持需手动实现
内存占用通常更低存储全部数据

4. 常用的 Stream 收集器(Collectors)有哪些?

常用静态方法

  1. toList():收集为List
    List list = stream.collect(Collectors.toList());

  2. toSet():收集为Set
    Set set = stream.collect(Collectors.toSet());

  3. toMap():收集为Map
    Map<String, Integer> map = stream.collect(
    Collectors.toMap(Function.identity(), String::length));

  4. groupingBy():分组
    Map<Integer, List> group = stream.collect(
    Collectors.groupingBy(String::length));

  5. partitioningBy():分区
    Map<Boolean, List> partition = stream.collect(
    Collectors.partitioningBy(s -> s.length() > 3));

  6. joining():字符串连接
    String joined = stream.collect(Collectors.joining(", "));

  7. summarizingInt():统计汇总
    IntSummaryStatistics stats = stream.collect(
    Collectors.summarizingInt(String::length));

5. Stream 中的 findFirst() 和 findAny() 有什么区别?

方法行为并行流表现
findFirst()返回第一个元素(按相遇顺序)保证返回流中第一个元素
findAny()返回任意元素可能返回任意匹配元素

使用场景

  • 需要确定性的结果:用findFirst()
  • 只需要任意匹配项且不关心顺序:用findAny()(并行流性能更好)

6. Stream 的 flatMap() 和 map() 有什么区别?

map()

  • 一对一的元素转换
  • 不会改变流中元素数量
  • 示例:
  List<String> words = Arrays.asList("Hello", "World");List<Integer> lengths = words.stream().map(String::length)  // ["Hello", "World"] → [5, 5].collect(Collectors.toList());

flatMap()

  • 一对多的元素转换
  • 将嵌套流"扁平化"为一个流
  • 示例:
  List<List<String>> listOfLists = Arrays.asList(Arrays.asList("a", "b"),Arrays.asList("c", "d"));List<String> flatList = listOfLists.stream().flatMap(List::stream)  // [["a","b"],["c","d"]] → ["a","b","c","d"].collect(Collectors.toList());

7. 如何用 Stream 处理大文件?

高效读取大文件示例

try (Stream<String> lines = Files.lines(Paths.get("large.txt"))) {List<String> result = lines.filter(line -> line.contains("keyword")).limit(1000).collect(Collectors.toList());
}

优势

  • Files.lines()不会一次性加载整个文件
  • 流式处理保持低内存占用
  • 可配合limit()控制处理量

8. Stream 的 peek() 和 forEach() 有什么区别?

方法返回类型用途是否触发计算
peek()Stream调试/观察元素(中间操作)
forEach()void执行副作用(终止操作)

peek()示例

List<String> collected = Stream.of("a", "b", "c").peek(System.out::println)  // 调试用.map(String::toUpperCase).collect(Collectors.toList());

forEach()示例

Stream.of("a", "b", "c").forEach(System.out::println);  // 实际消费流

9. 如何自定义 Collector?

实现 Collector 接口

Collector<String, List<String>, List<String>> toListIgnoreCase = Collector.of(ArrayList::new,                  // supplier(list, str) -> list.add(str.toLowerCase()),  // accumulator(list1, list2) -> {              // combinerlist1.addAll(list2);return list1;},Collector.Characteristics.IDENTITY_FINISH);List<String> result = stream.collect(toListIgnoreCase);

核心参数

  1. Supplier:创建结果容器
  2. Accumulator:将元素添加到容器
  3. Combiner:合并两个容器(并行流用)
  4. Finisher:最终转换(可选)
  5. Characteristics:收集器特性

10. Stream 的 reduce() 方法如何使用?

三种形式

  1. 基本形式:
Optional<T> reduce(BinaryOperator<T> accumulator)Optional<Integer> sum = Stream.of(1, 2, 3).reduce((a, b) -> a + b);
  1. 带初始值:
T reduce(T identity, BinaryOperator<T> accumulator)int sum = Stream.of(1, 2, 3).reduce(0, (a, b) -> a + b);
  1. 复杂形式(并行流用):
<U> U reduce(U identity,BiFunction<U,? super T,U> accumulator,BinaryOperator<U> combiner)int sum = Stream.of(1, 2, 3).reduce(0,(partialSum, num) -> partialSum + num,Integer::sum);

使用场景

  • 聚合计算(求和、求积等)
  • 复杂对象构建
  • 替代某些collect()操作

Java 其他高频面试题补充

1. Java 模块化系统(Java 9+)

关键概念

  • module-info.java:模块描述文件
  • 模块路径 vs 类路径
  • 访问控制规则:
    • requires:声明依赖
    • exports:公开包
    • opens:反射开放包

面试问题

  • 模块化解决了什么问题?
  • 如何迁移传统项目到模块化系统?
  • requires staticrequires transitive的区别?

2. 记录类型(Record, Java 14+)

特点

  • 不可变数据载体
  • 自动生成:构造器、访问器、equals()、hashCode()、toString()

示例
public record Point(int x, int y) {}

面试问题

  • Record 和传统类的区别?
  • 如何自定义 Record 的方法?
  • Record 能否继承其他类?

3. 密封类(Sealed Class, Java 15+)

特点

  • 限制哪些类可以继承自己
  • 通过permits关键字指定子类

示例
public sealed class Shape
permits Circle, Square, Rectangle {…}

面试问题

  • 密封类的设计目的是什么?
  • 密封类与final类的区别?
  • 如何与Record结合使用?

4. 模式匹配(Java 16+)

instanceof 模式匹配
if (obj instanceof String str) {
System.out.println(str.length());
}

switch 表达式增强
return switch (day) {
case MONDAY, FRIDAY -> “工作日”;
case SATURDAY, SUNDAY -> “周末”;
default -> throw new IllegalArgumentException();
};

面试问题

  • 模式匹配如何简化代码?
  • switch表达式与传统switch的区别?
  • 什么情况下会抛出MatchError?

5. 虚拟线程(Virtual Thread, Java 19+)

特点

  • 轻量级线程(协程)
  • 由JVM调度,非OS线程
  • 高吞吐量(支持百万级线程)

使用方式
Thread.startVirtualThread(() -> {
System.out.println(“Virtual thread running”);
});

面试问题

  • 虚拟线程与传统线程的区别?
  • 什么场景适合使用虚拟线程?
  • 如何避免虚拟线程的pin操作?

6. 响应式编程(Reactive Streams)

核心接口

  • Publisher
  • Subscriber
  • Subscription
  • Processor<T,R>

面试问题

  • 背压(Backpressure)是什么?
  • 如何实现冷热数据流?
  • Reactor 和 RxJava 的主要区别?

7. Java 原生内存访问(Java 16+)

Foreign Function & Memory API

  • 替代JNI的更安全方式
  • 直接操作堆外内存

示例
try (MemorySegment segment = MemorySegment.allocateNative(100)) {
segment.set(ValueLayout.JAVA_INT, 0, 42);
}

面试问题

  • 为什么需要新的内存访问API?
  • MemorySegment 和 ByteBuffer 的区别?
  • 如何防止使用原生API导致的内存泄漏?

8. 协程库比较

库名特点适用场景
Quasar字节码增强传统Java项目
Kilim轻量级简单协程需求
Project LoomJVM原生支持(虚拟线程)Java 19+项目

面试问题

  • 协程与线程的本质区别?
  • 如何在Java 8项目中使用协程?
  • 协程的栈结构如何实现?

9. 微基准测试(JMH)

典型问题

  • 如何正确测量Java方法性能?
  • JMH 如何避免JIT优化干扰?
  • @BenchmarkMode 的几种模式区别?
  • 什么是死代码消除(Dead Code Elimination)?

10. 代码安全实践

重点领域

  1. 反序列化漏洞防护

    • 使用白名单控制可反序列化类
    • 替代方案:JSON/Protocol Buffers
  2. 密码学安全

    • 避免使用弱算法(MD5/SHA1)
    • 正确使用初始化向量(IV)
  3. 内存安全

    • 敏感数据及时清零(char[]处理密码)
    • 防止时序攻击

面试问题

  • 如何安全存储用户密码?
  • 为什么推荐使用BCrypt而不是SHA系列?
  • Java 的 SecureRandom 实现原理?

11. JVM 性能调优

常用工具

  1. 命令行工具:
    • jps, jstat, jstack, jmap
  2. 图形化工具:
    • VisualVM, JConsole
  3. 高级工具:
    • Async Profiler, JFR(Java Flight Recorder)

调优参数

  • 堆内存:-Xms, -Xmx
  • 元空间:-XX:MetaspaceSize
  • GC选择:-XX:+UseG1GC
  • 日志输出:-Xlog:gc*

面试问题

  • 如何分析OOM问题?
  • G1 和 ZGC 的适用场景区别?
  • 如何诊断线程死锁?

12. 现代Java开发实践

热点话题

  1. 不可变设计:

    • Record 的使用
    • 防御性拷贝
  2. 空值处理:

    • Optional 最佳实践
    • 静态分析工具(@NonNull)
  3. 并发模式:

    • CompletableFuture 组合
    • 反应式编程

面试问题

  • 为什么提倡不可变对象?
  • Optional.flatMap() 和 map() 的区别?
  • 如何设计无锁数据结构?

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词