文档说明

本文档为全网最全Java架构师定稿版,无删减、无广告、无水印;

全覆盖:Java基础、OOP、异常、集合(新增Queue、Deque、ConcurrentHashMap深度原理)、并发、JMM、JVM、IO、NIO、网络(URL访问全过程、TCP三次握手四次挥手超详细)、Netty、反射、注解、序列化、动态代理、深浅拷贝、阻塞队列、SPI、对象内存布局、OOM、线程中断、设计模式10种、滑动窗口、面试真题。

全文架构师口吻、生产级总结、背诵即可面试通关。

第一部分 Java基础语法 架构师底层原理

1 Java体系核心架构与跨平台底层原理(架构师深度补全版)

章节前置定位:

本章为Java最底层地基,所有高级特性(并发、JVM、中间件)全部依托本章节架构设计;面试高频追问:为什么Java跨平台?JDK/JRE/JVM本质区别?编译解释混合架构优势?生产环境版本选型原则?全部深度补齐,无废话纯干货。

1.1 JVM/JRE/JDK层级架构设计

JVM(Java虚拟机):架构核心抽象层,Java实现跨平台的根本核心。本身不识别高级Java源码,仅解析执行标准Class字节码;通过抽象虚拟机规范,屏蔽Windows、Linux、MacOS操作系统CPU指令集、内存管理、系统调用差异;JVM不参与代码开发、不提供编译工具,仅负责**字节码加载、校验、执行、内存管理、垃圾回收**,是Java程序运行的底层容器。不同操作系统适配专属JVM虚拟机,实现同一套字节码多系统通用。

JRE(Java运行时环境):程序运行最小依赖单元。由JVM虚拟机+Java核心基础类库(rt.jar、charsets.jar等)组成;核心类库封装底层操作系统能力,包含集合、IO、字符串、数学运算、网络基础通用类。JRE设计初衷:剥离开发能力,仅保留运行能力,轻量化部署生产环境;生产服务器部署业务Jar包仅需安装JRE,无需完整版JDK,减少服务器冗余依赖、降低安全漏洞风险。无编译、调试、打包等开发工具。

JDK(Java开发工具包):完整开发集成环境。层级包含JRE全部能力,额外内置开发工具链:javac编译器、java运行命令、jmap/jstack/jhat诊断工具、jar打包工具、javadoc文档生成工具。面向开发、测试、编译、调试场景;本地开发环境必须安装JDK,用于源码编译、代码调试、项目打包。企业生产规范:开发环境JDK、生产环境JRE,环境隔离,最小化生产依赖。

层级依赖:JDK ⊃ JRE ⊃ JVM。三者单向包含、职责隔离、分层解耦;架构设计亮点:职责单一、层级清晰,适配开发、运行、运维多场景。

生产高频避坑:禁止开发环境与生产环境JDK版本不一致,避免字节码版本不兼容、类加载异常、语法兼容报错。

1.1.1 三者生产面试终极对比表(必背)

对比维度

JVM

JRE

JDK

核心组成

虚拟机执行引擎

JVM+核心类库

JRE+开发工具链

使用场景

底层字节码执行

线上项目运行

本地开发编译调试

是否含编译工具

有(javac)

是否跨平台

是,系统适配专属JVM

依赖JVM实现跨平台

依赖JVM实现跨平台

1.2 编译与解释混合架构设计

前端编译+后端执行 混合编译架构:Java采用**一次编译、到处运行**的混合执行模型,分为两大阶段。

①前端编译:由javac编译器(纯Java编写)将.java源码静态编译为平台无关的.class字节码文件;字节码是一种中间加密伪指令,非机器码,人类不可直接执行,通用性极强。

②后端执行:JVM采用解释执行+JIT即时编译双引擎混合架构。解释器逐行解析字节码,适配冷门代码、保障启动速度;JIT编译器(C1/C2)监控代码执行热度,将高频热点代码编译为本地机器码,直接交由CPU执行,大幅提升运行性能。

架构取舍:兼顾跨平台通用性、启动速度、运行高性能,规避纯解释执行效率低、纯编译执行启动慢的缺陷。

1.2.1 Java 编译与运行完整机制?

详细答案:

  1. 编写Java源代码:后缀 .java
  2. 编译阶段:使用 javac 命令,把.java 源码编译生成.class 字节码文件
  3. 运行阶段:使用 java 命令,JVM 加载并执行.class 字节码
    • JVM 加载 .class 字节码
    • JVM 解释 / 即时编译执行字节码
  4. Java 是 编译 + 解释混合型语言,先整体编译成字节码,再由 JVM 逐段解释执行,实现跨平台。
1.2.2 字节码跨平台底层本质(面试必问)

字节码并非操作系统可识别的机器码,而是JVM自定义的一套通用指令集;无论Windows、Linux、Mac,全部JVM统一解析同一套字节码指令。源码编译后剥离系统相关语法,保留通用逻辑指令,实现编译一次、全平台运行。对比C/C++:直接编译为本地机器码,不同系统需要单独编译包,无跨平台能力。

架构总结:JVM虚拟机+通用字节码=Java跨平台核心基石。

1.3 环境变量架构设计初衷

 1.3.1 JAVA_HOME

        JDK根路径抽象变量,统一指向 JDK 根目录,后续 Tomcat、Maven、IDE 都依赖它。

        设计核心目的:解耦IDE、Maven、Gradle、Tomcat等工具对JDK路径的硬编码依赖;统一全局JDK引用入口,企业多版本JDK切换(JDK8/JDK17)仅需修改JAVA_HOME配置,无需改动项目代码。

        底层原理:所有开发工具优先读取环境变量,获取JDK核心工具、依赖库路径,实现版本灵活管控。

1.3.2 Path系统环境变量

全局命令路由,将JDK/bin目录加入系统全局识别路径,让任意文件目录下可直接执行java、javac、jps、jmap等命令,无需书写绝对路径。

        环境配置:配置 %JAVA_HOME%\bin,让系统在任意命令行都能识别 javacjava 命令不配就会提示:不是内部或外部命令。

        生产规范:禁止直接写死磁盘JDK路径,全部依赖JAVA_HOME引用,适配服务器集群批量部署。

不配置会提示不是内部或外部命令,无法编译运行 Java 程序。

1.3.3 额外必备环境变量(生产配置)

ClassPath:类加载路径,JDK1.8之后默认废弃,JVM自动加载核心类库;

作用:指定第三方Jar、自定义类加载扫描路径,早期版本用于手动配置依赖包。生产新版本无需手动配置,了解底层设计即可。

1.4 Debug架构师思维

Debug架构师思维:摒弃学生式断点逐行查看,生产线上无本地调试环境,核心能力为线上问题定位、链路溯源、故障复盘。

底层调试原理:JVM挂起线程、保存线程栈快照、冻结内存数据,实现无侵入观测程序运行状态。核心调试维度:线程栈追踪(查看方法调用链路、死锁堆栈)、内存快照(观测对象创建、引用关系)、CPU占用监控、变量内存实时观测、异常链路溯源。

企业标准快捷键强化

断点:在代码行号左侧点击出现红色圆点

F8:单步跳过、逐行往下走,不进入方法内部

F7:步入方法、进入自定义方法内部

Shift+F7:强制步入(进入源码)、

F9:跳到下一个断点、

Alt+F8:实时计算变量 / 表达式值(动态计算代码、修改变量)、

Ctrl+F8:临时断点。

作用:查看程序执行流程、监控变量变化、快速定位 bug,是工作必备技能。

生产调试规约:禁止线上Debug断点阻塞业务线程,线上使用日志+ arthas诊断工具排查故障。

1.4.1 Debug底层字节码原理

调试模式下,javac编译时携带调试信息行号、变量表、源码映射关系;JVM启用JDWP调试协议,建立调试端与虚拟机通信通道,实现断点暂停、内存读取、变量修改,无额外业务性能损耗,仅占用少量通信资源。

2 标识符关键字与语法架构设计规范(架构师补全扩充版)

2.1 标识符语法约束与底层架构目的

合法组成:字母、数字、下划线_、美元符$

注意:

 1.不能以数字开头

 2.不能使用 Java 关键字、保留字

 3.严格区分大小写

 4.不能用中文、特殊符号

 5.规范:见名知意,小驼峰 / 大驼峰统一风格

设计目的:统一词法解析规则,降低编译器语法解析复杂度,保持语法一致性。

底层编译原理:javac编译器词法分析阶段,通过字符逐行扫描,依据标识符规则分割代码令牌(Token),过滤非法字符、识别命名实体,若违反规则直接抛出编译语法异常。

架构补充:美元符$生产禁止自定义使用,JDK底层自动生成匿名内部类、代理类、字节码辅助类大量使用$符号,人为使用易造成类名冲突、字节码混淆。

2.2 关键字与保留字设计思想+全量分类

关键字:编译器预留语法词,定义结构、权限、生命周期,禁止自定义使用。保留字goto/const:语言架构预留扩展位,兼容后续版本演进,语法层面禁止业务使用。

架构师全量分类(必背)

①权限修饰:private、protected、public、default;

②类结构:class、interface、enum、extends、implements、abstract;

③变量修饰:static、final、volatile、transient;

④流程控制:if、else、for、while、do、switch、case、break、continue、return;

⑤异常处理:try、catch、finally、throw、throws;

⑥对象相关:new、this、super、instanceof;

⑦原生类型:byte、short、int、long、float、double、char、boolean;

⑧逻辑空值:void、null;

⑨保留字:goto、const,Java 预留但目前没使用,同样禁止用作标识符

底层特性:所有关键字全部小写、不可作为自定义标识符,编译期被编译器特殊标记解析。

2.3 企业架构级命名规范(生产强制规约)

类/接口:大驼峰,定义模块抽象与领域能力。(UserInfo)

变量/方法:小驼峰,定义业务属性与行为。(userName)

常量:全大写下划线分隔,用于架构配置、业务常量,防篡改。(MAX_AGE)

包名:全小写域名倒置,实现组织架构、模块分层、领域隔离。(com.xxx.project)

架构师进阶命名规约(生产落地)

  1. 工具类:后缀统一Utils、Helper,如StringUtils、DateHelper,无业务状态、全部静态方法;

  2. 实体类:数据库实体后缀Entity、DTO传输实体、VO视图实体、BO业务实体,分层隔离;

  3. 接口与实现类:接口能力命名(UserService)、实现类后缀Impl(UserServiceImpl);

  4. 枚举类:后缀Enum,统一状态码、业务标识,杜绝魔法值;

  5. 异常类:后缀Exception,分层自定义异常,精准区分故障类型;

  6. 布尔变量:统一is/has/need前缀,如isSuccess、hasPermission,语义直白无歧义。

2.4 语法大小写敏感底层原理(面试冷门题)

Java严格区分大小写,底层由编译器词法解析规则决定:JVM字节码、源码编译全部基于Unicode编码,大小写字母Unicode编码值不同,编译器判定为两个完全不同字符。

对比编程语言:SQL、Python部分版本大小写不敏感。

生产避坑:编码统一小写包名、规范大小写命名,避免大小写混淆引发的低级BUG,同时适配Linux服务器大小写敏感文件系统。

2.5 注释架构分级规范(生产文档标准)

注释不是冗余代码,是架构可读性、可维护性的核心保障,生产分为三级注释:

  1. 单行注释//:用于局部代码逻辑说明,标注复杂算法、特殊业务判断,禁止无意义冗余注释;

  2. 多行注释/* */:用于代码块、废弃逻辑批量注释,临时屏蔽代码,生产上线必须删除;

  3. 文档注释/** */:类、接口、公共方法、常量专用,生成JavaDoc文档,标注作者、版本、入参、出参、业务逻辑,框架源码全部采用文档注释。

架构注释禁忌:禁止注释描述简单基础语法、禁止堆砌无效注释、禁止中文全角空格造成编译乱码、上线代码杜绝注释残留调试代码。

2.6 空白字符编译优化原理

空格、换行、制表符统称为空白字符,编译期javac编译器会自动剔除多余空白字符,仅保留语法分隔必要空格,不生成多余字节码,无运行期性能损耗。

编码规范:统一使用空格缩进、禁止Tab制表符,适配多系统编辑器格式统一,规避代码格式化错乱问题。

2.7 本章高频面试真题+生产避坑总结

  1. 为什么不建议自定义使用$美元符?:JDK底层自动生成类大量使用,易引发类名冲突、字节码混淆;

  2. goto为什么是保留字却不能使用?:防止随意跳转破坏代码结构化,规避代码混乱、难以维护,保留是为版本扩展兼容;

  3. Java大小写敏感底层原因?:大小写Unicode编码不同,编译器判定为不同字符;

  4. 生产布尔变量命名规范?:is/has前缀,语义清晰,禁止无意义flag命名;

  5. 注释生产红线?:禁止残留调试注释、禁止无效冗余注释、线上代码无废弃代码注释。

2.8 语法架构设计总结(架构师视角)

标识符、关键字、命名规范看似基础语法,本质是Java语言标准化架构约束:通过统一词法规则降低编译器解析成本,通过关键字预留语法保障语言扩展性,通过命名规范实现团队代码统一、降低协作维护成本。

架构设计核心思想:约束大于自由、规范优于个性,所有底层语法设计均为适配大型工程、集群协作、长期迭代的企业级开发场景。

3 变量内存模型与成员/局部变量底层差异(架构师深度补全版)

3.1 变量分类与JVM内存分配底层架构

Java变量依据定义位置划分为成员变量局部变量两大类,二者内存分配模型、生命周期、初始化规则完全隔离,是JVM内存设计分层思想的基础落地。

成员变量(实例变量+静态变量):实例变量绑定对象、分配在堆内存对象实例数据区;

静态变量绑定类、分配在元空间静态变量区,全局唯一共享。

局部变量:定义在方法、代码块、形参中,全部分配在虚拟机栈的栈帧局部变量表,线程私有,无共享竞争。

3.2 架构核心差异(全维度对比+底层原理)

对比维度

成员变量(实例变量)

局部变量

内存存储位置

堆内存(对象实例数据)

虚拟机栈(栈帧局部变量表)

生命周期

随对象创建而生、GC回收而灭

方法入栈创建、方法出栈销毁

默认初始化值

有,JVM自动零值初始化

无,必须手动赋值才可使用

权限修饰符

支持private/protected/public/default

不支持任何权限修饰符

线程安全性

线程共享,并发不安全

线程私有,绝对线程安全

垃圾回收

依赖JVM GC回收,有内存开销

栈自动销毁,无GC开销

访问方式

对象.变量名访问

直接变量名访问

3.3 变量初始化底层字节码机制

成员变量初始化流程:对象创建时,JVM在堆内存分配空间后,优先执行内存零值初始化(基础类型0、引用null),再执行代码块、构造方法自定义赋值;该流程由new指令底层驱动,字节码自动补充初始化逻辑,保障对象属性不会出现脏数据。

局部变量初始化规则:虚拟机栈无内存清零机制,栈内存复用率极高,上一个栈帧销毁后内存数据残留;为规避脏数据、空数据风险,javac编译器在编译期做语法校验,未手动赋值的局部变量直接编译报错,无任何运行期容错。

3.4 final修饰变量底层语义与差异化规则

final修饰变量核心语义:有且仅能赋值一次,赋值后不可修改,不同位置变量规则不同:

①final成员变量:必须在代码块、构造方法中完成赋值,JVM不会自动默认初始化,赋值后存入对象常量槽位;

②final局部变量:方法内任意位置赋值一次即可,编译期生成常量标记;

③编译期常量:final修饰字面量赋值变量,编译期触发常量折叠,直接替换为字面量,无变量内存占用。底层字节码:final变量会被标记为ACC_FINAL,禁止字节码层面二次赋值,保障不可变性。

3.5 静态变量特殊内存模型(成员变量分支)

静态变量属于特殊成员变量,不属于对象、归属类模板;类加载阶段在准备阶段完成默认零值初始化,初始化阶段完成自定义赋值,常驻元空间。

特性:全局唯一、所有对象共享、优先于对象加载、无需实例化即可访问;并发场景下静态变量共享竞争激烈,极易出现线程安全问题,生产禁止随意定义公共静态业务变量。

3.6 变量作用域与内存回收机制

成员变量:作用域贯穿对象全生命周期,只要对象存活,变量永久占用堆内存;即使方法执行结束,对象未被GC回收,变量依然存在。

局部变量:作用域严格受限,仅当前代码块/方法内有效;代码块执行完毕,局部变量从局部变量表销毁,内存立即释放,无内存滞留。架构优化原则:业务编码优先使用局部变量,减少堆内存占用,降低GC压力。

3.7 生产高频坑点+编码黄金规约

  1. 线程安全避坑:成员变量线程共享,多线程场景必须加锁;局部变量线程私有,天然安全,并发业务优先局部变量。

  2. 内存优化规约:短生命周期业务数据、临时计算变量全部定义为局部变量,减少堆内存常驻对象。

  3. 初始化避坑:不要依赖成员变量默认值,业务代码手动赋值,提升代码可读性;局部变量必须显式初始化,杜绝编译报错。

  4. final使用规范:不可变业务数据、配置常量优先final修饰,基础类型常量触发编译期折叠,节省内存。

  5. 静态变量禁忌:禁止用静态变量存储业务临时数据、用户会话数据,避免多用户数据污染。

3.8 变量分类:成员变量 & 局部变量 完整区别

  • 成员变量:分配在堆内存,随对象生命周期绑定,受 GC 管理
  • 局部变量:分配在虚拟机栈,方法栈帧创建即分配,栈帧销毁立即回收,无 GC 开销

        1.定义位置

                成员变量:类中、方法外;局部变量:方法、代码块、形参内。

        2.内存位置

                成员变量:堆内存;局部变量:栈内存。

        3.默认值

                成员变量:有默认值;局部变量:无默认值,必须手动赋值。

        4.生命周期

                成员变量:随对象创建而存在,对象回收才销毁;局部变量:方法执行完毕立刻销毁。

        5.访问权限

                成员变量:可加权限修饰符;局部变量:不能加权限修饰符。

3.9. 常量定义、特点、命名规范?

  • final 修饰的变量就是常量
  • 特点:只能赋值一次,赋值后不可修改
  • 命名:全部大写,下划线分隔 USER_MAX_NUM
  • 作用:固定配置、固定参数,提高可读性和维护性

3.10. 为什么局部变量没有默认值?

  • 局部变量在栈内存,栈空间用完即释放,虚拟机不会自动初始化
  • 生命周期短,只在当前方法有效
  • Java 语法强制要求:局部变量必须手动初始化才能使用,避免脏数据

3.11 面试高频真题(必背)

  1. 为什么局部变量没有默认值?:栈内存复用率高、存在残留脏数据;编译期强制赋值,规避脏数据风险,同时减少内存初始化开销。

  2. 成员变量和局部变量哪个线程安全?为什么?:局部变量安全,栈内存线程私有;成员变量堆内存共享,多线程并发修改存在线程安全问题。

  3. final修饰成员变量和局部变量区别?:成员变量必须构造/代码块赋值;局部变量任意位置赋值一次,编译期优化更彻底。

  4. 静态变量存放在哪里?什么时候初始化?:元空间,类加载准备阶段默认初始化,初始化阶段自定义赋值。

  5. 业务开发为什么优先使用局部变量?:无GC开销、线程安全、内存回收快、减少堆内存常驻压力。

4 八大基本类型与类型转换底层设计及工程避坑(架构师深度补全升级版)

类型 字节 范围 包装类 默认值
byte 1 -128~127 Byte 0
short 2 -2^15~2^15-1 Short 0
int 4 -2^31~2^31-1 Integer 0
long 8 -2^63~2^63-1 Long 0L
float 4

小数

Float 0.0f
double 8

高精度小数

Double 0.0d
char 2 0~65535 Character '\u0000'
boolean 1 true/false Boolean false

4.1 基础类型分类、内存占用与底层存储规则

Java八大基本类型分为四大类别,区别于引用类型,直接存储原始数值、无对象头、无GC开销、内存占用极低,是Java底层数据存储的最小单元;统一遵循补码存储规则,全部数值以二进制补码存入虚拟机栈局部变量表。完整内存占用+取值范围+默认值汇总:

  1. 整型体系(有符号):byte(1字节/-128~127)、short(2字节/-32768~32767)、int(4字节/-2³¹~2³¹-1)、long(8字节);默认初始值0。

  2. 浮点体系:float(4字节、单精度)、double(8字节、双精度);默认初始值0.0,采用IEEE754浮点编码规范。

  3. 字符类型:char(2字节、无符号);取值范围0~65535,存储Unicode编码,默认空字符'\u0000'。

  4. 布尔类型:boolean(1字节);仅存储true/false,默认false,JVM底层无专属字节码,int替代存储(1=true、0=false)。

架构设计初衷:按业务数值范围分层设计,小范围数据用低字节类型节省内存,大数据采用高字节类型;适配嵌入式、服务端、大数据多场景,平衡内存占用与数值承载能力。

4.2 底层编码原理与类型本质特性

4.2.1 整型底层:补码存储机制

所有整型底层强制使用二进制补码存储,正数原码、反码、补码一致,负数补码=反码+1;该设计统一CPU加减法电路,硬件仅需加法器即可完成所有数值运算,消除正负零冗余编码,拓展负数取值区间。

4.2.2 字符类型底层:Unicode编码适配

char固定2字节,直接映射Unicode万国码,天然兼容中、英、日韩多国文字;区别于C语言char单字节ASCII编码,无需转码即可存储中文,底层无编码转换开销。

生产坑点:char无法存储emoji表情,emoji占用4字节,超出char取值范围。

4.2.3 浮点类型底层:IEEE754编码缺陷

float、double采用符号位+阶码位+尾数位二进制近似编码,无法精准表示十进制有限小数(如0.1、0.2);小数部分采用二进制无限进位近似存储,导致精度丢失。

精度层级:double精度远高于float,有效小数位15~16位,float仅6~7位。

4.2.4 布尔类型底层字节码真相

Java语法层面boolean为布尔类型,但是JVM底层无boolean专属指令

编译期自动转为int类型存储:1代表true,0代表false;

数组boolean类型单独占用1字节,非紧凑存储,规避内存对齐开销。

4.3 类型转换架构模型+字节码底层执行

4.3.1 隐式自动转换(编译期安全优化)

转换规则:低位类型→高位类型,无精度丢失、无溢出风险、编译期自动完成;底层字节码无需额外指令,直接复用数值内存。

标准转换链路:byte→short→int→long→float→double。

特殊转换规则:char可直接转为int(Unicode编码值),

byte、short负数转高位类型保留符号位;所有整型转浮点类型,内存存储结构重构,大数据整型转浮点存在精度丢失。

4.3.2 显式强制转换(人工风险兜底)

转换规则:高位类型→低位类型,语法必须手动强转;底层执行高位截断,保留低位有效字节,存在数值溢出、精度丢失、符号错乱三大风险。

典型案例:int强转byte,直接截断高3字节,仅保留末尾1字节;超过取值范围数值直接错乱,无任何编译期校验,风险完全由业务代码承担。

4.3.3 表达式类型提升(编译器自动防护)

编译期强制语法规则:byte、short、char三类低字节类型,只要参与算术运算、位运算,自动提升为int类型,防止数值溢出、符号丢失;运算结束后若赋值给原低位类型,必须手动强转。

底层字节码原理:编译器插入i2b、i2s等转换指令,完成类型升降级,无运行期性能损耗,属于编译期语法防护机制。

4.3.4 强制显式转换

大类型→小类型,存在数据溢出、精度截断风险,业务层需自行兜底。

4.4 生产高频致命坑点+代码实战避坑

  1. 浮点精度丢失坑(金融红线):0.1+0.2≠0.3,底层二进制无法精准表示十进制小数;生产金融、支付、计量业务严禁使用float/double,强制使用BigDecimal字符串构造器初始化,杜绝浮点失真。

  2. 低位类型运算赋值坑:byte a=1; a=a+1; 编译报错,a+1自动提升为int,需手动强转a=(byte)(a+1);生产编码尽量避免byte/short运算,优先int通用整型。

  3. char中文空字符坑:char默认值'\u0000'(空白不可见字符),非空字符串;数据库存储char类型需手动判空,避免空字符入库导致展示异常。

  4. 大数整型转浮点坑:long类型超过2^53数值,转double会丢失低位精度;超大数值运算禁止浮点转换,统一使用BigInteger。

  5. 无符号认知坑:char是唯一无符号基本类型,取值无负数;byte、short、int、long全部为有符号类型,高位为符号位。

  6. 布尔数组内存坑:boolean[]数组每个元素单独占用1字节,并非紧凑存储;大批量布尔标记建议用位运算压缩存储,节省堆内存。

4.5 基本类型与包装类适配边界(关联铺垫)

八大基本类型无对象特性、不支持泛型、无空值、无工具方法;为适配面向对象体系,衍生对应包装类。底层适配规则:基本类型存栈内存、无GC;包装类存堆内存、有对象头、支持null空语义。

生产编码规约:局部临时计算用基本类型;数据库映射、接口传输、集合存储用包装类。

4.6 表达式类型提升编译规则

byte/short/char参与运算,编译期直接提升为int,规避数值溢出;表达式最终类型由运算最大类型决定,属于编译器自动安全防护。

4.7 架构师编码黄金规约(强制落地)

  1. 常规整型运算优先使用int,适配绝大多数业务数值,兼顾内存与性能;

  2. 金额、汇率、精密计量强制使用BigDecimal,禁用所有浮点类型;

  3. 避免手动使用byte、short,除非明确需要极致内存压缩;

  4. 不同类型混合运算,手动预判类型提升,规避编译报错;

  5. 强制转换必须做数值范围校验,防止溢出产生脏数据;

  6. char类型仅用于单个字符判定,禁止用于字符串存储。

4.8 本章高频面试真题(必背)

  1. boolean底层存储是什么?占几个字节?:JVM无布尔指令,底层int存储,1=true、0=false;单个boolean变量4字节,数组boolean元素1字节。

  2. 为什么byte运算会自动提升为int?:防止低位类型运算溢出,编译器自动做类型提升防护。

  3. 0.1+0.2为什么不等于0.3?怎么解决?:IEEE754二进制编码精度丢失;使用BigDecimal字符串构造器运算。

  4. char为什么能存储中文?:char 固定占 2 字节,Java 字符编码采用 Unicode,一个汉字刚好占用 2 字节,所以可以直接存中文。。

  5. long转double会精度丢失吗?:超过2^53的long数值转double,低位数据丢失,精度失真。

  6. 基本类型和包装类怎么选型?:临时计算用基本类型、栈内存无GC;对象存储、泛型、允许为空用包装类。

  7. 浮点型为什么不能用于金额精确计算?float、double 底层采用二进制科学计数法近似存储;十进制小数无法精准转换成二进制,存在精度丢失;金额必须用:BigDecimal。

5 包装类与装箱拆箱、缓存池架构底层原理(架构师深度完整版)

5.1 包装类架构定位与设计初衷

Java为八大基本类型一一对应提供包装类,核心解决基本类型非对象缺陷

基本类型是底层原始数据,不具备对象特性:无类结构、无方法、无空值、无法适配泛型、无法存入集合。

包装类架构定位:抹平基本类型与引用类型差异,补齐面向对象能力;适配集合存储、泛型约束、空值判空、类型工具转换四大业务场景。

八大包装类对应关系:Byte、Short、Integer、Long、Float、Double、Character、Boolean;全部继承Number抽象类,统一数值转换规范。

作用:

  1. 基本类型不能为 null,包装类可以
  2. 集合只能存对象,不能存基本类型
  3. 提供大量工具方法(类型转换、进制转换)

5.2 自动装箱拆箱语法糖底层字节码拆解

JDK1.5引入自动装箱拆箱语法糖,编译期由javac自动完成代码脱糖,无运行期性能损耗,仅简化编码写法,底层本质仍是原生方法调用。

装箱:基本类型转为包装类,底层编译调用包装类静态方法valueOf();

拆箱:包装类转为基本类型,底层编译调用xxxValue()(intValue、byteValue等)。字节码指令佐证:装箱执行invokestatic调用valueOf,拆箱执行invokevirtual调用实例取值方法。

代码脱糖示例:Integer a = 10; 编译后等价于 Integer a = Integer.valueOf(10); int b = a; 编译后等价于 int b = a.intValue();

5.3 包装类缓存池架构设计(核心面试重难点)

5.3.1 缓存池适用范围与底层源码逻辑

Java为频繁使用的小数值包装类创建静态缓存池,复用对象、减少频繁创建销毁、降低GC压力,属于JDK内置内存优化机制。

支持缓存的包装类:Byte、Short、Integer、Long、Character;Float、Double无缓存池(浮点数值无限连续,无法缓存固定范围数值);Boolean缓存固定返回TRUE/FALSE常量对象。

核心缓存区间:Integer/Short/Long/Byte固定缓存-128 ~ 127;Character缓存0~127ASCII通用字符。

以Integer为例:底层维护Integer[]静态数组cache,类加载时提前初始化缓存区间内所有Integer对象,常驻内存,无需重复new创建。

5.3.2 缓存池可调整参数与底层限制

Integer缓存池上限默认127,支持JVM参数手动调整:-XX:AutoBoxCacheMax=255,扩大缓存上限;下限固定-128不可修改

生产调优场景:业务频繁使用128~255区间整型数据,可适当调高缓存最大值,减少对象创建、优化内存占用。

底层源码限制:缓存数组为静态常量数组,类加载完成后不可动态扩容,仅支持启动前参数修改。

5.3.3 缓存池生产致命坑点(高频面试错题)

等值判断坑:缓存区间内数值,==判断为true(复用同一缓存对象);超出区间==判断为false(新建堆对象);例:Integer a=127、Integer b=127,a==b为true;a=128、b=128,a==b为false。

包装类与基本类型比较:包装类自动拆箱为基本类型后比较,仅对比数值,无缓存差异,永远为true。

Float/Double无缓存:浮点型无限小数,不存在固定缓存区间,所有赋值均新建对象,==永远不相等。

手动new对象绕过缓存:Integer a = new Integer(10); 强制新建堆对象,不使用缓存,同数值缓存对象对比==为false。

5.4 包装类不可变底层原理

所有数值型包装类均被final修饰,底层存储数值的成员变量private final T value,赋值后不可修改。无对外提供修改数值的方法,任何数值变更都会生成新包装对象。

架构设计目的:保障线程安全、支持缓存复用、防止数值篡改、适配常量池缓存机制。

区别于可变StringBuilder,包装类属于典型不可变类。

5.5 包装类与基本类深度差异化对比(生产选型表)

对比维度基本类型包装类内存位置虚拟机栈,无对象头堆内存,含对象头、填充字节默认值数值零值(int=0)默认null,支持空语义泛型适配不支持泛型、集合存储支持泛型、可存入集合可变性变量值可反复修改不可变类,赋值不可修改性能开销无GC、占用内存极小有对象开销、少量GC损耗

5.6 包装类生产编码黄金规约(强制落地)

等值判断强制equals:所有包装类数值比较,禁止==,一律使用equals规避缓存坑点;

业务场景精准选型:临时计算、循环运算用基本类型;数据库实体、接口入参、集合存储用包装类;

禁止手动new包装类:优先自动装箱,底层复用缓存对象,减少堆内存创建;

空指针防护:包装类默认null,业务代码必须做非空判断,防止自动拆箱空指针异常;

浮点包装类禁用等值判断:Float、Double禁止==和equals直接比较,采用精度区间判定。

5.7 本章高频面试真题(必背)

         1.装箱拆箱底层原理?编译后做了什么?:语法糖脱糖,装箱调用valueOf(),拆箱调用xxxValue();

        2.哪些包装类有缓存池?缓存范围?:Byte/Short/Integer/Long(-128~127)、Character(0~127);浮点无缓存;

        3.Integer a=128,b=128,a==b为什么false?:超出缓存区间,两次装箱新建两个堆对象,引用地址不同;

        4.包装类为什么是不可变类?:底层final value存储数值,无修改方法,保障缓存复用与线程安全;

        5.自动拆箱常见空指针场景?:包装类为null时,自动拆箱赋值给基本类型,触发NullPointerException;

        6.Integer和int区别?生产怎么选型?:栈堆存储、默认值、泛型适配不同;计算用基本类型、存库传参用包装类。

        7.Integer 缓存机制详解?

      缓存范围:-128 ~ 127;

     范围内数值,Integer.valueOf() 直接复用缓存对象;

      超出范围,new 新对象。

Integer a=100,b=100; a==b → true
Integer c=200,d=200; c==d → false

       8.包装类 == 和 equals 区别?

    ==:比较两个变量的内存地址是否相同;

   equals:比较两个包装类内部的实际数值是否相同;

  开发规范:包装类数值比较必须使用 equals,禁止使用 ==。

6 运算符底层架构、位运算源码原理与企业实战(架构师深度完整版)

6.1 运算符层级架构与优先级底层设计

6.1.1 运算符完整分类:Java运算符按底层执行优先级、功能维度划分为七大体系,全部由JVM专属字节码指令支撑,优先级由编译器静态绑定,无需运行期判定。

完整分类:算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符、三元运算符、特殊运算符(instanceof、()、.)。其中位运算为硬件级指令,无需经过复杂语法解析,执行效率远高于普通运算符,是中间件、JDK源码高频优化手段。

6.1.2 优先级底层架构规则:优先级本质是javac编译器语法树解析顺序,编译期确定执行先后,无运行期开销。

核心优先级排序(面试必背):括号 > 一元运算(++/--/!) > 算术运算(乘除>加减) > 位运算(移位>按位与>异或>或) > 关系运算(><==) > 逻辑运算(&>^>| >&&>||) > 三元运算 > 赋值运算。

生产编码规约:禁止依赖原生优先级,复杂表达式强制加括号,提升代码可读性,规避运算优先级歧义Bug。

6.2 自增自减(i++/++i)字节码底层拆解与性能差异

6.2.1 底层字节码执行逻辑

i++、++i语法层面均为自增1,但是字节码执行链路完全不同,这也是性能差异的根本原因。

i++(后置自增):先读取当前变量值压入操作数栈,再执行变量自增,最后返回栈中旧值;底层多出一次栈副本保存操作,字节码指令:iload→dup→iinc。

++i(前置自增):直接执行变量自增,再读取新值,无副本冗余,字节码指令:iinc→iload,指令更少、内存开销更低。

6.2.2 生产选型与避坑:for循环、迭代遍历场景强制优先使用++i,减少栈内存副本拷贝,高频循环下性能优化明显;

复合赋值场景(a = i++)严格把控赋值顺序,防止业务取值错乱;

底层本质:二者运行期时间复杂度均为O(1),差异在于字节码冗余度,无性能量级差距,属于代码优化细节规约。

6.3 逻辑运算符短路机制+字节码裁剪原理

6.3.1 逻辑运算符分类与底层差异

分为短路逻辑运算符(&&、||)、非短路逻辑运算符(&、|),底层字节码跳转逻辑截然不同。

&&短路与:前置条件为false,直接终止后续判断,字节码生成条件跳转指令;

||短路或:前置条件为true,直接终止后续判断;

&amp;/|非短路:无论前置条件真假,强制执行全部表达式,无字节码裁剪。

6.3.2 生产高频致命坑点
  1. 空指针风险:短路特性可做非空前置判断,如obj!=null && obj.get(),前置false直接截断,规避空指针;若误用&,前置null仍执行后续方法,直接抛出NPE;

  2. 自增失效坑:int a=1; false && (a++>0),短路截断,a值不变仍为1;

  3. 适用场景划分:业务条件判断统一用&&/||;需要强制校验双条件、位运算场景,使用&/|。

6.4 位运算底层二进制原理+JDK源码应用

位运算直接操作二进制补码,依托CPU底层指令集,无需进制转换,执行纳秒级耗时,是Java极致性能优化的核心手段,HashMap、ThreadLocal、Redis、Netty源码大量复用。

全部位运算仅支持整型(byte/short/int/long/char),浮点类型无位运算能力。

6.4.1 六大核心位运算详解
  1. 按位与 &:同位均为1则为1,否则为0;核心用途:奇偶判定(x&1=1为奇数)、掩码截取、权限校验;

  2. 按位或 |:同位有1则为1,否则为0;核心用途:状态位合并、权限叠加、数值补位;

  3. 按位异或 ^:同位不同为1、相同为0;特性:自身异或为0、0异或自身不变;用途:无临时变量交换、数据加密、去重判定;

  4. 按位取反 ~:二进制01反转;底层公式:~x = -x-1,用于位状态反转、掩码生成;

  5. 有符号右移 >>:高位补符号位,负数补1、正数补0;等价整除2,向下取整;

  6. 无符号右移 >>>:高位强制补0,无视符号位;负数移位后转为超大正数,用于哈希散列、无符号数值运算。

6.4.2 经典高频位运算公式(源码通用)
  1. x&(x-1):清除二进制最后一位1;用途:判定2的幂、统计二进制中1的个数、HashMap哈希扰动;

  2. x&(-x):获取二进制最后一位1;底层依托补码负数规则,用于树状数组、二进制分片;

  3. 1<<n:快速生成2的n次幂,HashMap扩容阈值底层实现;

  4. (x >> n) & mask:分段截取二进制位,用于状态压缩、多状态标记存储。

6.5 移位运算深度剖析+底层硬件优化

6.5.1 左移运算 <<

二进制整体左移、低位补0;等效乘以2的n次幂,无精度丢失;底层CPU移位指令比乘法指令更快,大数据乘2运算优先使用左移。注意:左移超出变量二进制位数,高位直接截断,仅保留低位有效字节。

6.5.2 有符号VS无符号右移生产区别

有符号右移>>:保留数值正负性,符号位不变;负数移位后仍为负数,向下取整;适合常规数值减半运算。

无符号右移>>>:彻底抹除符号位,全部高位补0;典型应用:HashMap哈希扰动、UUID压缩、随机数生成,规避负数哈希冲突。

生产禁忌:负数业务数值禁止使用>>>,防止数值错乱溢出。

6.6 三元运算符语法糖+编译期优化原理

三元运算符(条件?a:b)为最简分支语法糖,编译期脱糖为if-else字节码,无运行期性能损耗;优先级最低,赋值场景必须加括号。

底层编译优化:若前后返回值为常量,编译器触发常量折叠,直接编译为固定数值;若为变量,生成条件跳转字节码。

生产坑点:三元运算符自动类型提升,如true?1:1.0,结果自动提升为double类型,造成整型精度丢失。

6.7 赋值运算符复合优化+隐式强制转换

复合赋值运算符(+=、-=、&=)底层自带隐式强制转换,区别于普通运算赋值。示例:byte a=1; a+=2; 编译不报错;底层字节码自动插入i2b强制转换指令,无需手动强转;而a=a+2会触发int类型提升,编译报错。

底层原理:复合赋值指令底层封装运算+截断逻辑,编译期自动完成低位适配,是编译器语法优化机制。

6.8 位运算企业级实战落地场景(高频复用)

  1. 权限控制系统:用二进制位标记权限(1可读、2可写、4删除),通过|叠加权限、&判定权限、^撤销权限,替代枚举集合,节省内存;

  2. 状态机标记:订单状态、设备状态用二进制位存储,多状态合并为一个整型字段,数据库单字段存储多种状态;

  3. 集合底层优化:HashMap扩容(2的幂)、数组长度计算、哈希扰动(hash&(length-1));

  4. 加密算法:异或简单加密、数据脱敏,明文+密钥异或加密,再次异或解密;

  5. 高性能计算:乘除2幂次数运算,全部替换为移位运算,提升CPU执行效率;

  6. 内存压缩:布尔标记批量存储,一个int存储32个布尔状态,替代数组,大幅节省堆内存。

6.9 本章生产避坑规约+高频面试真题

6.9.1 生产编码强制避坑点
  1. 复杂表达式禁止裸写优先级,强制加括号,杜绝语法歧义;

  2. 业务判断优先短路运算符,减少无效执行、规避空指针;

  3. 负数严禁使用无符号右移,防止数值溢出错乱;

  4. byte/short复合赋值注意隐式强转,避免无意识截断;

  5. 业务代码禁止滥用位运算,复杂逻辑注释说明,提升可读性;

  6. 循环遍历优先++i,减少栈副本开销,养成编码习惯。

6.9.2 面试必背真题(含标准答案)
  1. i++和++i字节码区别?哪个性能高?:i++先取值后自增,存在栈副本;++i先自增后取值,指令更少;循环场景++i性能更优。

  2. &和&&区别?生产怎么选型?:&无短路、全部执行;&&短路截断、效率高;业务判断用&&,位运算用&。

  3. x&(x-1)作用?底层原理?:清除二进制最后一位1;用于2的幂判定、统计1的个数,HashMap底层高频使用。

  4. 无符号右移和有符号右移区别?:>>有符号右移:高位补符号位正数补 0,负数补 1;>>>无符号右移:高位统一补 0负数右移后会变成很大正数,用于哈希扰动。

  5. byte a=1; a+=2为什么不报错,a=a+2报错?:+=自带隐式强制转换;a+2自动提升为int,赋值给byte需手动强转。

  6. 异或运算三大特性?实战用途?:自身异或为0、0异或不变、交换律;用于无临时变量交换、加密、权限撤销。

  7. 逻辑运算符 &&、&、||、| 区别?

  • && 短路与:左边 false,右边不执行
  • & 不短路与:两边都执行,也可做位运算
  • || 短路或:左边 true,右边不执行
  • | 不短路或:两边都执行开发优先用 &&、||,效率更高。

     8.位运算有哪些?企业常用哪些?

  & 与 | 或 ^ 异或 ~ 取反 << 左移 >> 有符号右移 >>> 无符号右移

       企业实战:

     (1)x & 1 判断奇偶

    (2)x << 1 等价 ×2

    (3)x >> 1 等价 ÷2

    (4)a ^ b 两数交换、判同异

    (5)x & (x-1) 消去二进制最后一个 1

      9.异或 ^ 运算特点与用途?

        规则:相同为 0,不同为 1用途:

        (1)无需临时变量交换两个整数

        (2)判断两个数值是否相等

        (3)简单加密、解密

6.1 运算符架构分层

算术、赋值、关系、逻辑、三元、位运算;位运算属于硬件级指令,执行效率远高于普通算术运算。

6.2 i++与++i字节码架构差异

i++:先取值后自增,多一次副本赋值;++i:先自增后取值,字节码更少、性能更优。工程规范:循环遍历优先使用++i。

6.3 逻辑运算符短路架构设计

&&/||具备短路语义,编译期裁剪无效分支,提升执行效率;&/|无短路特性,全部执行,仅适用于位运算与双逻辑强制校验场景。

6.4 位运算架构师实战应用

x&1 判断奇偶;<< >> 左右移替代乘除2;^异或实现无临时变量交换、权限掩码、状态标记;x&(x-1) 清除二进制最后一个1,用于2的幂判定与二进制统计。JDK源码、中间件、权限系统、状态机底层大量使用位运算。

6.5 有符号右移与无符号右移架构差异

>> 有符号右移,高位补符号位,保持数值语义;>>> 无符号右移,高位补0,负数转为大数,适用于无符号数值运算。

7 原码反码补码编码架构与进制底层设计思想

7.1 三码架构规则

正数:原码、反码、补码完全一致;

负数:原码定符号、反码中间映射、补码最终存储。

底层规范:JVM所有数值仅以补码形式存储

原码:符号位 + 数值位
反码:负数原码取反
补码:反码 + 1,计算机只存补码
进制:二进制 (0b)、八进制 (0)、十进制、十六进制 (0x)

7.2 补码架构设计核心目的

统一硬件加减法电路,CPU只需加法器即可完成加减运算;消除+0与-0重复编码;扩展负数取值范围,提升编码空间利用率。

7.3 整型底层:补码存储机制

所有整型底层强制使用二进制补码存储,正数原码、反码、补码一致,负数补码=反码+1;

该设计统一CPU加减法电路,硬件仅需加法器即可完成所有数值运算,消除正负零冗余编码,拓展负数取值区间。

7.4 原码、反码、补码编码架构深度拆解(面试必考冷门重难点)

(1)、进制底层设计思想:计算机底层硬件仅识别高低电平,天然适配二进制(0/1);摒弃人类习惯的十进制,

核心架构目的:硬件电路极简设计、降低芯片制造成本、信号识别无歧义。二进制逢二进一、八进制压缩二进制、十六进制简化二进制书写,JVM、字节码、内存存储全部依托二进制底层逻辑。

进制转换底层规则:整数除基取余、小数乘基取整,计算机底层仅做二进制运算,十进制仅做人类交互展示。

(2)、原码(真值码):最原始编码方案

编码规则:最高位为符号位,0代表正数、1代表负数,剩余位存储数值绝对值。

架构优缺点:通俗易懂、符合人类读数习惯;

致命缺陷:

①存在正负双零(+0、-0),浪费一个编码空位;

②无法直接做跨正负加减法,硬件运算逻辑复杂,CPU需要单独设计减法器电路。

生产现状:仅用于源码层面数值展示,JVM底层不使用原码存储

示例(byte一字节):+1原码=0000 0001,-1原码=1000 0001。

(3)、反码(过渡编码):原码到补码的中间桥梁

编码规则:正数反码=原码;负数反码=符号位不变,数值位按位取反。

架构设计目的:解决负数编码逻辑过渡问题,消除原码减法歧义;

遗留缺陷:依旧存在正负双零冗余编码,无法彻底统一加减法电路,不能作为最终存储编码。

示例:-1反码=1111 1110。

(4)、补码(工业级最终编码):JVM硬件底层标准

编码规则:正数补码=原码;负数补码=反码+1。四大架构设计精髓(架构师必背):

  1. 消除双零冗余:将-0编码强制归为0,唯一零编码,释放多余编码空间,拓展负数取值范围;byte字节利用冗余编码拓展出-128,形成-128~127完整区间。

  2. 统一加减电路:硬件废除减法器,减法运算转为负数加法,CPU仅保留加法器,极简硬件架构、降低芯片功耗。

  3. 天然循环溢出:补码编码具备模运算特性,数值超出范围自动循环溢出,无需额外做边界判断,底层运算效率极高。

  4. 符号位参与运算:符号位无需单独特殊处理,直接参与二进制运算,简化字节码解析逻辑。

(5)、进制与编码生产面试终极总结

  1. 为什么计算机不用十进制?:十进制状态过多,硬件无法精准识别;二进制仅高低两种电平,电路简单、容错率高。

  2. 为什么舍弃原码、反码?:存在正负双零、硬件需减法器、运算成本高,不符合计算机极简架构思想。

  3. byte为什么最小值是-128不是-127?:补码消除-0,冗余编码位强制定义为-128,最大化利用内存编码空间。

  4. 补码底层核心公式:负数补码 = 模 - 绝对值,模为当前二进制最大容量(byte模=256)。

7.5 字符类型底层:Unicode编码适配

char固定2字节,直接映射Unicode万国码,天然兼容中、英、日韩多国文字;区别于C语言char单字节ASCII编码,无需转码即可存储中文,底层无编码转换开销。生产坑点:char无法存储emoji表情,emoji占用4字节,超出char取值范围。

7.6 浮点类型底层:IEEE754编码缺陷

float、double采用符号位+阶码位+尾数位二进制近似编码,无法精准表示十进制有限小数(如0.1、0.2);小数部分采用二进制无限进位近似存储,导致精度丢失。精度层级:double精度远高于float,有效小数位15~16位,float仅6~7位。

7.7 布尔类型底层字节码真相

Java语法层面boolean为布尔类型,但是JVM底层无boolean专属指令,编译期自动转为int类型存储:1代表true,0代表false;数组boolean类型单独占用1字节,非紧凑存储,规避内存对齐开销。

7.8 switch类型限制底层架构原因

不支持long/float/double:long范围过大无法编译期常量匹配;浮点精度丢失无法精准等值判定。仅支持byte/short/int/char/枚举/String,兼顾语法安全与编译效率。

8 流程控制语法底层编译原理与工程规范

8.1 流程控制整体架构分类与编译定位

Java流程控制语句是代码执行链路的调度核心,底层依托JVM字节码跳转指令实现代码逻辑分支、循环、中断执行;全部流程控制语法在编译期由javac完成语法校验、字节码指令生成,运行期JVM执行引擎读取跳转指令修改程序计数器执行地址。

流程控制架构分类:

        分支控制(if-else、switch)、

        循环控制(for、while、do-while)、

        中断控制(break、continue、return)。

        全部依托JVM跳转字节码修改程序计数器执行地址。

        设计核心思想:最小指令开销、代码执行链路可控、编译期语法强校验,适配复杂业务逻辑编排。

8.2 分支控制底层编译原理

8.2.1 if-else 条件分支字节码拆解

if-else是最基础的条件分支,底层基于条件跳转字节码指令实现逻辑分流。

编译流程:

①javac编译阶段解析布尔判断条件,将表达式压入操作数栈;

②通过ifeq(等于0跳转)、ifne(不等于0跳转)、iflt(小于跳转)等字节码指令判定条件;

③条件成立跳转至执行代码块,不成立则跳过当前分支,执行else逻辑;

④分支执行完毕后通过goto指令跳出分支体系,防止代码穿透执行。

底层特性:if-else无固定判断阈值,适配任意布尔表达式,编译期动态生成跳转指令,灵活性最高。

8.2.2 switch分支编译原理与两种底层实现

switch专为固定枚举值匹配设计,编译期优化程度高于if-else,底层分两种字节码实现机制,由javac自动判定适配:

  1. tableswitch(表跳转):匹配数值连续、区间紧凑(如1、2、3、4、5);底层生成有序跳转表,通过数值偏移量直接定位代码行,查询时间复杂度O(1),执行效率极高;

  2. lookupswitch(散列跳转):匹配数值离散、无连续规律(如1、7、15、22);底层生成哈希映射表,遍历比对匹配数值,时间复杂度O(n),性能略低于tableswitch。

核心底层优化:JDK7+支持String字符串switch,编译期自动将字符串转为hashCode哈希值做整数匹配,底层依旧复用tableswitch/lookupswitch指令;字符char类型底层转为ASCII数值判定,本质均为整数匹配。

生产避坑:switch必须携带default默认分支,兜底异常数值,避免无匹配逻辑造成业务空执行。

8.3 循环控制底层字节码执行机制

8.3.1 while&amp;do-while循环底层原理

while:先判定条件、后执行循环体,底层采用前置条件校验

字节码逻辑:程序计数器指向条件判断代码→压入判断条件→条件成立跳转至循环体,执行完毕后goto回条件入口,循环往复;条件不成立直接跳出循环。

do-while:先执行循环体、后判定条件,至少执行一次;底层采用后置条件校验,字节码顺序倒置,优先执行业务代码,末尾追加条件跳转指令。

底层共性:无循环计数器,依赖外部变量控制循环终止,无限循环判定简单(while(true)),字节码仅需一条无条件跳转指令。

8.3.2 for循环(普通for)底层编译优化

普通for循环(初始化;条件;迭代)是语法级封装最优的循环结构,编译期javac会重组代码执行顺序:

①初始化代码仅在循环开始执行一次;

②循环条件前置判定;

③循环体执行完成后优先执行迭代逻辑,再跳转回条件判定。

字节码优化亮点:循环迭代变量作用域严格受限,编译期自动回收循环局部变量,内存占用更低;对比while循环,for循环结构闭环、变量隔离,规避外部变量污染。

8.3.3 增强for循环(for-each)语法糖脱糖原理

for-each是JDK1.5语法糖,编译期自动脱糖为迭代器遍历,无运行期性能损耗。

底层脱糖规则:

①数组类型:脱糖为普通for循环,通过数组下标遍历取值;

②集合类型:脱糖为Iterator迭代器,调用hasNext()、next()方法遍历。

底层硬性限制:遍历过程中禁止增删集合元素,触发并发修改异常ConcurrentModificationException;无法获取遍历下标,无自定义遍历起始位置能力。

生产场景:仅做纯遍历读取,不做集合结构修改。

8.4 中断控制关键字底层原理

8.4.1 break&continue字节码跳转逻辑

break:终止当前循环/分支,执行循环外后续代码;底层生成无条件goto跳转指令,直接跳出循环代码块。

continue:终止本次循环,直接进入下一次循环;底层跳转至循环迭代位置,跳过本次循环剩余逻辑,不跳出循环体系。

标签跳转高级特性:Java支持自定义代码块标签,break/continue+标签可精准跳出多层嵌套循环,底层修改跳转指令指向指定标签行地址;生产极少使用,避免代码逻辑混乱、可读性变差。

8.4.2 return返回指令底层机制

return为方法终止指令,底层分两类字节码:

①无返回值方法:执行return void指令,直接清空栈帧、销毁局部变量、方法出栈;

②有返回值方法:将返回结果压入操作数栈,拷贝至调用方栈帧,再销毁当前方法栈帧。

底层核心:return执行后当前线程栈帧立即销毁,无残留内存占用,是栈内存自动回收的底层触发点。

8.5 嵌套循环编译损耗与性能优化

8.5.1 嵌套循环底层性能痛点

多层嵌套循环会生成大量跳转字节码,JVM执行引擎频繁修改程序计数器地址,CPU指令跳转缓存失效;内层循环高频创建局部变量,栈内存频繁分配销毁,造成轻微性能损耗。

最坏场景:三层及以上嵌套循环、循环次数过万,CPU跳转开销指数级上升。

8.5.2 架构师循环优化黄金规则
  1. 循环层数压缩:业务逻辑尽量控制两层以内嵌套,三层及以上拆分方法、扁平化处理;

  2. 循环外定义变量:循环内不变的对象、集合、常量提前定义在循环外,避免反复创建销毁;

  3. 大集合优先迭代器:大数据量遍历禁用普通for下标遍历,减少下标寻址开销;

  4. 循环内杜绝复杂计算:判断条件、数学运算提前预处理,循环内仅做简单逻辑判定;

  5. 巧用break终止:满足业务条件立即跳出循环,减少无效遍历次数。

8.6 流程控制生产编码规约(强制落地)

  1. 分支规约:if-else层级不超过3层,深层级采用策略模式、枚举优化;switch必须写default兜底分支,禁止遗漏异常场景;

  2. 循环规约:高频循环优先使用for、迭代器,禁用while无限裸循环;循环内禁止new对象、禁止数据库/IO连接创建;

  3. 中断规约:禁止滥用标签跳转、禁止循环内大量break/continue打乱执行链路;方法末尾统一return,杜绝多分支零散return;

  4. 语法规约:流程代码块必须加大括号{},禁止单行省略写法,规避后期代码追加造成逻辑穿透;

  5. 空值规约:分支判断优先做非空校验,obj!=null && 业务判定,利用短路特性规避NPE。

8.7 本章高频面试真题(必背)

  1. switch底层两种跳转表区别?:数值连续用tableswitch(O(1))、离散用lookupswitch(O(n));

  2. for-each底层原理?能不能增删元素?:编译脱糖为迭代器,遍历禁止增删,触发并发修改异常;

  3. 多层嵌套循环为什么性能差?:频繁字节码跳转、CPU指令缓存失效、栈内存反复分配;

  4. while(true)无限循环底层字节码?:仅一条无条件goto跳转指令,极简无性能损耗;

  5. return执行后JVM做什么?:销毁当前方法栈帧、回收局部变量、返回结果至调用方;

  6. 字符串switch底层如何实现?:编译期转为hashCode整数,复用整型switch跳转逻辑。

8.8 流程控制架构总结(架构师视角)

流程控制语法看似简单逻辑编排,底层本质是JVM字节码跳转指令的工程化封装;javac编译期完成语法优化、指令重组,JVM运行期高效执行跳转逻辑。

架构设计核心取舍:以极简字节码指令实现复杂逻辑分支,兼顾编码简洁性、执行高效性。

生产编码核心原则:少嵌套、平层级、早终止、预加载,减少CPU跳转开销、降低代码维护成本,适配高并发、大数据量业务场景。

9 数组底层内存架构、初始化机制与二维数组本质

9.1 数组本质定义

  1. 数组是引用数据类型,存储在堆内存
  2. 数组是相同数据类型、连续内存空间的集合
  3. 数组一旦创建,长度不可改变(这是底层核心特性)
  4. 数组对象在内存中独有一个唯一地址,栈中只保存引用

连续内存分配、同类型固定长度、内存地址连续;初始化即锁定长度,不可变。

架构选型:固定结构用数组,动态扩容业务放弃数组选用ArrayList。

9.2 数组底层内存架构

9.2.1 内存分区
  • 栈内存:存储数组引用变量(地址值),线程私有
  • 堆内存:存储数组实体(真实数据),线程共享
9.2.2 内存模型图解
栈内存 (引用)          堆内存 (实体)
  arr   ------->      [ 10, 20, 30 ]
(地址0x11)            长度固定=3
9.2.3 底层关键特性
  1. 连续内存分配:查询速度极快(O (1))
  2. 索引定位公式:首地址 + 索引 × 数据类型字节数 = 数据位置
  3. 数组对象独有属性:length(JVM 自动维护,不可修改)
  4. 越界访问直接抛出:ArrayIndexOutOfBoundsException

9.3 数组初始化机制(两种方式 + 底层区别)

9.3.1 动态初始化(指定长度,默认赋值)
int[] arr = new int[3];

底层执行流程:

  1. JVM 在堆中开辟连续内存空间
  2. 根据数据类型自动赋默认值
    • int/short/byte/long → 0
    • float/double → 0.0
    • boolean → false
    • char → \u0000
    • 引用类型 → null
  3. 将堆内存地址赋值给栈引用
9.3.2 静态初始化(指定元素,长度自动推算)
int[] arr = {10,20,30};
int[] arr = new int[]{10,20,30};

底层执行流程:

  1. 计算元素个数 → 确定数组长度
  2. 开辟连续空间
  3. 依次将元素存入内存
  4. 地址赋值给引用
9.3.3 初始化必须遵守的底层规则
  • 先初始化,后使用,未初始化无法访问
  • 不能同时指定长度 + 静态赋值(编译报错)
  • 数组引用不指向堆实体 → 空指针 NullPointerException

9.4 二维数组底层本质

9.4.1 核心结论

二维数组本质:一个数组,里面的每一个元素又是一个一维数组

9.4.2 内存架构

text

栈:arr ---->  堆:一维数组 (地址0x11)
                       [ 0x22, 0x33, 0x44 ]
                          /       |       \
              堆中三个一维数组 [1,2] [3,4] [5,6]
9.4.3 底层特点
  1. 二维数组名存储第一层数组地址
  2. arr.length → 获取一维数组长度(行数)
  3. arr[i].length → 获取第 i 行的列数
  4. 支持不规则二维数组(每一行长度不同)
9.4.4 不规则二维数组(底层证明)
int[][] arr = new int[3][]; // 只定义行,不列
arr[0] = new int[2];
arr[1] = new int[5];
arr[2] = new int[3];

✅ 完全合法,因为二维数组就是数组的数组

9.5 高频面试题(必背)

  1. 数组长度为什么不可变?底层是连续内存空间,创建时已确定大小,无法扩容 / 缩容。
  2. 数组为什么是引用类型?实体存在堆内存,栈只存地址,符合引用类型内存模型。
  3. 二维数组的地址怎么存储?外层数组存内层数组的地址,不是直接存数据。
  4. 数组有没有继承关系?所有数组默认继承 Object,具有 clone()toString() 方法。

10 方法体系架构、重载设计原理、递归栈底层与参数传递模型

10.1 方法架构设计价值

模块化拆分、逻辑解耦、代码复用、职责单一,便于架构分层与迭代维护。

10.2 方法重载语言设计原理

同类同名,以参数个数/类型/顺序区分方法签名;编译期静态绑定,与返回值、权限修饰符无关。适用场景:同一行为多参数适配,统一API语义。

10.3 递归虚拟机栈底层原理

递归本质是方法栈帧迭代入栈;必须具备递归出口+递推公式,否则触发StackOverflow栈溢出。

架构取舍:递归代码简洁但栈开销大、性能低,生产复杂遍历优先迭代实现。

11 Java值传递模型架构师终极定论

Java只有值传递,没有引用传递

基本类型:传递数值副本,方法内修改不影响原变量;

引用类型:传递对象地址副本,修改对象属性外部可见,方法内重新new赋值不影响原引用。

底层本质:所有参数传递都是栈帧拷贝,仅拷贝内容为数值或内存地址。

12 语法冷门盲点底层机制与工程坑点

局部代码块作用:收缩变量作用域、提前释放栈内存、隔离同名变量,避免命名冲突与业务歧义。

13 语法高阶:栈追踪、语法糖、编译期优化(架构师深挖)

13.1 Java语法糖全集(编译期脱糖底层)

语法糖:简化编码语法,编译期javac脱糖翻译为基础字节码,无运行期开销,不改变程序执行逻辑。生产高频语法糖底层拆解:

①增强for循环→迭代器遍历;

②自动装箱拆箱→valueOf/xxxValue;

③泛型→类型擦除;

④可变参数→数组封装;

⑤字符串+拼接→编译期StringBuilder;

⑥try-with-resources→自动生成finally关闭代码。

架构原则:语法糖仅简化编码,底层原理必须吃透,规避脱糖后隐藏的线上Bug。

13.2 字符串拼接底层字节码优化

字面量常量拼接:编译期常量折叠,直接合并为完整字符串,无对象创建;

变量混合拼接:编译期自动new StringBuilder,append拼接,最后toString;

循环内拼接坑点:JDK8之前循环每次new StringBuilder,产生大量垃圾对象、加重GC;

优化方案:循环手动创建StringBuilder,复用缓冲区。

生产规范:业务循环字符串拼接,禁止直接+拼接,手动使用StringBuilder。

13.3 可变参数底层原理与生产禁忌

语法格式:数据类型... 参数名,底层编译期直接转换为数组;支持0个或多个入参,兼容数组传参;重载优先级:固定参数方法优先级高于可变参数方法。

生产坑点:

①可变参数必须放在参数列表末尾;

②一个方法仅允许一个可变参数;

③可变参数数组可传入null,需做非空判断,防止空指针。

14 关键字深度剖析(冷门关键字+底层语义)

14.1 this/super/static/final 四大高频关键字总结

static:修饰静态资源,类加载初始化、全局唯一、优先于对象、无this引用;

final:不可修改,修饰变量不可赋值、修饰方法不可重写、修饰类不可继承;

this:当前实例引用,仅限成员上下文;

super:父类元数据引用,用于父子类层级拓展。

底层共性:均为编译期标记,字节码生成专属指令,管控资源生命周期与访问权限。

14.2 volatile原生关键字底层(基础前置铺垫)

基础阶段前置认知:volatile为并发关键字,底层禁止指令重排、保证可见性、不保证原子性;修饰变量,不修饰方法、类、代码块;内存屏障实现底层语义,为后续JMM、并发章节做铺垫。

生产用途:状态标记、轻量可见性同步,禁止做计数运算。

14.3 native本地关键字

修饰本地方法,无方法体、无实现代码;底层由C/C++编写,封装操作系统底层能力;字节码标记native标识,JVM调用本地方法栈执行;

典型方法:Object.hashCode()、System.arraycopy()、线程休眠方法。

架构价值:打通Java与操作系统底层壁垒,弥补Java底层硬件操作短板。

15 栈内存执行模型(架构师核心:看懂字节码执行)

15.1 栈帧完整结构

每个方法执行对应一个栈帧,栈帧私有独立内存空间,线程隔离互不干扰;

栈帧内部组成:局部变量表、操作数栈、动态链接、方法返回地址、附加信息。

局部变量表存放方法参数、局部变量;操作数栈负责算术运算、赋值、数据传递,所有代码运算底层均为操作数栈入栈出栈。

15.2 方法执行入栈出栈规则

线程启动开辟虚拟机栈,方法调用压入栈帧,方法执行完毕弹出栈帧;

栈内存先进后出,无垃圾碎片、回收效率极高;

栈内存大小固定,可通过JVM参数-Xss调整,默认1M;

递归过深、方法嵌套过多直接触发StackOverflowError栈溢出。

15.3 栈内存VS堆内存 架构师终极对比

对比维度

虚拟机栈

堆内存

存储内容

局部变量、引用地址

对象实例、成员变量

生命周期

方法开始创建、结束销毁

对象创建至GC回收

回收机制

自动出栈,无GC开销

依赖GC垃圾回收

线程安全性

线程私有,绝对安全

线程共享,并发不安全

访问速度

极快,无内存寻址开销

较慢,需要指针寻址

16 基础语法高频面试真题+生产避坑总结(必背)

16.1 基础语法高频面试简答题

  1. 为什么局部变量没有默认值?:栈内存无初始化机制,编译期强制赋值,规避脏数据,减少内存冗余开销。

  2. i++和++i字节码区别,生产怎么选?:i++生成临时副本,字节码更多;优先++i,性能更优。

  3. 浮点类型为什么不能用于金额计算?:二进制近似编码,精度丢失,必须使用BigDecimal。

  4. Java只有值传递怎么理解?:基本类型传数值,引用类型传地址副本,无引用传递。

  5. 缓存池范围及避坑原则?:-128~127,包装类比较强制使用equals。

16.2 基础语法生产编码黄金禁忌

  1. 禁止使用浮点类型存储金额、精密计量数据,统一BigDecimal;

  2. 循环遍历优先++i,减少副本创建,优化循环性能;

  3. 包装类等值判断一律equals,杜绝==判断;

  4. 循环内禁止字符串+拼接,手动复用StringBuilder;

  5. 方法内大对象手动置空,加速栈内存回收;

  6. 禁止无限递归,必须明确递归出口,防止栈溢出;

  7. 变量遵循最小作用域,局部变量优先,减少堆内存占用。

第二部分 面向对象OOP 架构师底层原理

12 OOP前置:对象与类底层本质

12.1 类与对象架构定义

类:对象的抽象模板,封装属性与行为,属于编译期静态元数据,存储在方法区(元空间);

对象:类的实例产物,运行期通过new指令创建,分配堆内存,持有独立对象头、实例数据;

架构设计初衷:抽象共性、封装复用、解耦业务,是所有高级语言面向对象的底层基石。

12.2 new对象底层字节码执行流程

  1. new指令:JVM校验类是否加载,未加载则触发双亲委派类加载,解析字节码生成Class元数据;

  2. 分配堆内存:依据类结构计算内存大小,连续分配内存空间,初始化默认零值;

  3. 对象头赋值:填充MarkWord、KlassPointer,标记对象锁状态、哈希码、分代年龄;

  4. 执行构造方法:代码块→父类构造→子类构造,完成自定义属性赋值;

  5. 栈引用指向堆对象:栈帧引用变量存储堆内存地址,完成对象创建。

     架构重点:构造方法仅用于初始化赋值,不创建对象;内存分配在构造方法执行之前。

13 面向对象三大特性与四大设计思想

13.1 三大特性底层本质

封装:隐藏内部实现、暴露对外接口,降低耦合、便于维护;底层通过权限修饰符控制访问范围。继承:代码复用、层级抽象、父子类多态基础;底层基于类加载与父类结构复用。

多态:父类引用指向子类对象,运行时动态绑定;底层靠方法重写、动态分派实现。

13.2 四大设计思想

封装、继承、多态、抽象;

工程落地准则:高内聚、低耦合、职责单一、面向接口编程。

13.3 架构师视角:三大特性取舍原则

  • 封装:强制落地,所有业务实体严格封装,杜绝公开成员变量;

  • 继承:谨慎使用,避免类层级过深造成代码冗余、耦合加重,优先组合替代继承;

  • 多态:核心落地,适配业务多实现、多分支场景,提升代码扩展性。

14 重载与重写底层原理及生产坑点

14.1 方法重载(静态多态)

同类同名,参数个数/类型/顺序不同;编译期静态绑定,和返回值、权限无关。

适用:同一行为多参数适配,统一API语义。

14.2 方法重写(动态多态)

子类重写父类非私有、非final、非静态方法;

运行时动态绑定,遵循:两同两小一大

两同:方法名、参数列表相同;

两小:返回值协变、异常范围更小;一大:访问权限更大。

14.3 生产坑点

静态方法不能重写、只能隐藏;final方法禁止重写;私有方法子类不可见,无重写。

14.4 静态绑定与动态绑定底层区别

静态绑定:编译期判定方法调用,优先级:静态方法>重载>私有方法,依据引用类型绑定;

动态绑定:运行期判定方法调用,针对重写方法,依据堆内存真实对象类型绑定,是多态实现的核心底层。

15 权限修饰符作用域与工程架构规范

15.1 四级权限作用域

修饰符 本类 同包 子类 任意类 适用场景
private 私有属性、私有方法、工具类内部方法
default(缺省) 包内封装、不对外暴露的实现类
protected 继承、模板方法、框架扩展点
public 对外接口、API、工具方法、实体字段

private:本类可见;

default(包访问):本包可见;

protected:本包+子类可见;

public:全局任意可见。

15.2 生产设计规范

成员变量尽量private,通过get/set封装;

工具类方法用public static;

框架扩展方法用protected预留子类重写;

禁止随意扩大权限,破坏封装与架构隔离。

15.3 权限修饰符架构设计层级逻辑

权限逐级开放,遵循最小权限原则:

private用于私有属性、内部工具方法;

default用于同包模块内交互,实现模块隔离;

protected用于框架预留扩展点,适配子类拓展;

public用于对外暴露通用能力,严格管控暴露范围。

15.4 权限修饰符 企业级使用规范(强制)

    15.4.1 private 规范

  • 所有成员变量必须 private
  • 所有内部工具方法 private
  • 禁止直接访问变量,必须用 get/set

    15.4.2 default(不写修饰符)规范

  • ServiceImpl 实现类 default,不对外暴露
  • Mapper / Dao 层 default
  • 只允许同包调用,禁止外部包访问

    15.4.3 protected 规范

  • 用于抽象父类模板方法
  • 业务开发极少用,避免滥用继承

   15.4.4 public 规范

  • Controller 必须 public
  • Service 接口必须 public
  • VO / DTO / 枚举 / 通用工具类 必须 public
  • 对外提供的能力必须 public

15.5 架构封装原则(企业灵魂)

    15.5.1 最小权限原则(最重要)

  • 能不给公开就不公开
  • 能隐藏的实现必须隐藏

   15.5.2 面向接口编程

  • Service 只暴露接口,隐藏实现
  • 便于替换、测试、扩展

  15.5. 3 禁止跨层访问

  • ❌ controller → mapper
  • ❌ controller → entity(强制使用 DTO/VO)

   15.5.4 禁止滥用 public

  • 90% 的实现类都不需要 public
  • 只有接口、API、数据类、工具类需要 public

16 抽象类与接口底层设计差异

16.1 抽象类(abstract class)

本质:半实现父类,属于类继承体系,单继承机制,存在构造方法,拥有成员变量、普通方法、抽象方法。

字节码:编译后仍是 class 文件,拥有父类层级结构。

设计定位:模板抽象、复用代码。

16.2 接口 (interface)

本质:行为能力契约,属于行为规范,多实现机制,默认常量, 默认公有抽象方法, 无构造方法,JDK8 默认 /static 方法、JDK9 私有方法;多实现。

字节码:编译后是独立接口字节码,无类继承层级,仅定义能力

设计定位:行为规范、能力定义。

16.3 工程选型

抽象类做层级模板,接口做行为解耦;架构遵循面向接口编程

16.5 底层语法结构差异

对比维度 抽象类 abstract class 接口 interface
继承关系 单继承 extends 多实现 implements
构造方法 有构造器,可被子类调用 无构造器,不能实例化
成员变量 可定义:普通变量、常量

JDK7:只能常量 public static final

JDK8+:可默认静态常量

成员方法 普通方法 + 抽象方法

JDK7:全部抽象

JDK8:默认方法、静态方法

JDK9:私有方法

权限修饰 支持 4 种权限 默认public,只能 public
代码块 静态代码块、实例代码块 无实例代码块,仅静态代码块
内存结构 存在父类引用,堆内存有继承结构 仅保存方法签名,无对象实例结构
实例化 不能直接 new,只能子类继承实例 不能 new,只能实现类实例

16.6 JDK版本接口迭代底层优化

   1. JDK7:接口仅允许全局常量、抽象方法,约束严格,适配基础规范定义;

  • 抽象类:既有实现又有抽象模板
  • 接口:纯行为规范,无任何实现,所有方法必须重写

    2. JDK8:新增default默认方法、static静态方法,解决接口升级兼容问题,无需全部实现;

     接口新增两种底层实现方法:

            (1)default 默认方法底层:接口内部携带默认实现逻辑,实现类可不重写,

                解决接口升级兼容问题。

            (2)static 静态方法底层:属于接口本身,实现类无法继承,只能接口名调用。

     底层设计目的:接口不再是纯契约,可携带通用默认逻辑      

     3. JDK9:新增私有方法,抽取接口内部通用逻辑,代码复用、隔离内部实现;

     接口内部可写private私有方法,仅接口内部复用,对外隐藏,完善接口代码复用能力。

16.7 设计思想与架构定位(企业核心)

16.7.1  抽象类设计思想

is-a 是什么(继承关系)

  • 描述事物本质共性
  • 提取子类通用属性 + 通用行为
  • 模板架构、骨架设计
  • 适用:同一族群、拥有共同特征

例子:动物→猫 / 狗、员工→程序员 / 测试、支付通用骨架

16.7.2 接口设计思想

has-a 能做什么(能力关系)

  • 描述具备某种行为能力
  • 只定标准,不限定出身
  • 多能力自由组合
  • 适用:功能扩展、行为规范、解耦

例子:可跑步、可飞行、可支付、可登录、可缓存

16.8 企业工程架构使用规范(硬性标准)

16.8.1 抽象类使用场景(架构骨架)

  1. 统一公共字段抽取(id、创建时间、创建人)
  2. 统一通用业务逻辑封装(分页、基础 CURD)
  3. 搭建项目底层模板架构
  4. 控制子类统一执行流程(模板方法模式)

工程写法:后台管理系统、基础业务模块,封装新增、修改、分页、列表通用逻辑

/**
 * 通用业务服务抽象父类
 * 封装基础CURD通用逻辑,子类直接复用
 */
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO> {

    @Autowired
    protected M baseMapper;

    /** 新增通用逻辑 */
    @Transactional(rollbackFor = Exception.class)
    public boolean save(T entity) {
        // 统一填充创建人、时间
        UserContext user = UserContext.getUser();
        entity.setCreateInfo(user.getUserId());
        return baseMapper.insert(entity) > 0;
    }

    /** 修改通用逻辑 */
    @Transactional(rollbackFor = Exception.class)
    public boolean updateById(T entity) {
        entity.setUpdateInfo();
        return baseMapper.updateById(entity) > 0;
    }

    /** 通用分页查询 */
    public Page<T> pageList(PageQuery query) {
        Page<T> page = new Page<>(query.getPageNum(), query.getPageSize());
        return baseMapper.selectPage(page, getQueryWrapper(query));
    }

    /** 由子类自定义查询条件(模板方法) */
    protected abstract LambdaQueryWrapper<T> getQueryWrapper(PageQuery query);
}

业务实现类

@Service
public class UserServiceImpl extends BaseServiceImpl<UserMapper, SysUser> 
        implements UserService {

    // 仅实现独有查询条件,基础增删改全部继承
    @Override
    protected LambdaQueryWrapper<SysUser> getQueryWrapper(PageQuery query) {
        UserQuery userQuery = (UserQuery) query;
        return Wrappers.lambdaQuery(SysUser.class)
                .like(StrUtil.isNotBlank(userQuery.getUsername()), SysUser::getUsername, userQuery.getUsername())
                .eq(SysUser::getDeleted, 0);
    }

    // 编写独有业务方法
    @Override
    public UserVO login(String username, String password) {
        // 独有登录业务逻辑
    }
}

16.8.2  接口使用场景(行为解耦)

  1. 定义业务能力规范
  2. 多实现灵活替换(策略模式)
  3. 对外暴露业务 API 契约
  4. 解耦业务与实现,面向接口编程

工程写法:微信支付、支付宝、银行卡、余额支付多方式统一调度

/**
 * 支付行为统一接口
 * 定义所有支付方式必须实现的能力
 */
public interface PayStrategy {
    /** 获取支付类型 */
    Integer getPayType();
    /** 发起支付 */
    PayResult pay(PayDTO dto);
    /** 支付回调处理 */
    void payCallBack(String params);
    /** 订单退款 */
    RefundResult refund(RefundDTO dto);
}

微信支付实现类:

@Service
public class WechatPayStrategy implements PayStrategy {
    @Override
    public Integer getPayType() {
        return 1;
    }

    @Override
    public PayResult pay(PayDTO dto) {
        // 微信支付SDK调用
    }

    @Override
    public void payCallBack(String params) {
        // 微信回调解析
    }
    
    @Override
    public RefundResult refund(RefundDTO dto) {}
}

支付宝支付实现类:

@Service
public class AliPayStrategy implements PayStrategy {
    @Override
    public Integer getPayType() {
        return 2;
    }
    // 实现对应支付逻辑
}

统一支付工厂调用:

@Component
public class PayStrategyFactory {
    @Autowired
    private Map<Integer, PayStrategy> payStrategyMap;

    public PayStrategy getStrategy(Integer payType) {
        return payStrategyMap.get(payType);
    }
}

16.9 开发绝对禁忌(底层避坑)

  • 抽象类不要定义过多可变属性,不利于扩展
  • 接口禁止定义实体业务字段,只定行为
  • 项目底层基础通用父类用抽象类
  • 业务功能、扩展能力全部用接口
  • 尽量少用接口默认方法,避免架构混乱
  • 抽象类侧重复用代码,接口侧重复用标准

17 四大内部类底层原理与使用场景

17.1 四大内部类

1. 成员内部类

依赖外部类实例,可直接访问外部所有属性;易造成内存泄漏,生产少用。

2. 静态内部类

不依赖外部实例,等同于独立类;适合封装工具常量、枚举、静态组件;独立存在,无引用,懒加载,架构首选。

3. 局部内部类

定义在方法内,仅当前方法有效,作用域受限;方法内临时类,作用域极小。

4. 匿名内部类

无类名、一次性使用;简化回调、线程、接口临时实现;无类名快速实现,现已被 Lambda 替代。

5. 底层共性:

  • 编译后都会生成独立 class 文件
  • 命名规则:外部类名$内部类名.class
  • 作用:高内聚、封装私有、简化代码、解耦结构

17.2  四大内部类核心对比表

类型 是否依赖外部对象 有无外部引用 能否使用 static 作用域 工程常用度
成员内部类 必须依赖 仅 final 常量 整个外部类内
静态内部类 不依赖 完全支持 全局 ★★★★★ 极高
局部内部类 依赖 不支持 方法内 极低
匿名内部类 依赖 不支持 当场使用 低(Lambda 替代)

17.3 成员内部类(非静态内部类)

1. 定义

写在外部类成员位置,不加 static

public class Outer {
    // 成员内部类
    public class Inner{
        
    }
}

2. 底层原理

        (1)依附外部类实例存在,没有外部对象就不能创建内部对象

        (2)内部类隐式持有外部类引用 Outer.this

        (3)可以直接访问外部类所有权限属性 / 方法(private 也能访问)

        (4)外部类也能访问内部类私有成员

        (5)不能定义静态成员(除了static final常量)

3. 创建对象语法

// 方式1
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

// 方式2 简写
Outer.Inner inner = new Outer().new Inner();

4. 权限特点

  • 可使用 public / default / protected / private
  • private 成员内部类:外部其他类完全不可见,极强封装

5. 使用场景

  1. 强关联一对一结构(车→发动机、人→心脏)
  2. 只想让外部类独有使用,拒绝外界访问
  3. 外部类需要频繁调用内部类私有功能
  4. 集合节点、实体内部附属结构

6. 缺点

  • 存在外部类引用,容易引发内存泄漏
  • 不能拥有独立静态资源

17.4  静态内部类(嵌套静态类)

1. 定义

成员位置 + static

public class Outer {
    public static class StaticInner{
        
    }
}

2. 底层原理(重点)

        (1)不依赖外部类实例,属于外部类静态资源

        (2)不持有外部类对象引用,无隐式 Outer.this

        (3)只能访问外部类静态属性、静态方法

        (4)可定义普通成员、静态成员

        (5)完全独立,生命周期和外部类无关

3. 创建对象

Outer.StaticInner inner = new Outer.StaticInner();

4. 企业高频使用场景(开发最常用)

场景 1:实体分层嵌套(DTO 分层、树形结构)

public class UserDTO {
    private String name;
    // 静态内部类:用户地址
    public static class Address{
        private String city;
    }
}

场景 2:工具类分组、常量分组

场景 3:单例模式最优写法(静态内部类懒汉单例)

public class Singleton {
    private Singleton(){}
    // 静态内部类
    private static class InnerHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){
        return InnerHolder.INSTANCE;
    }
}

底层优势:懒加载、线程安全、无锁、效率最高

场景 4:Builder 建造者模式(业务实体最常用)

public class User {
    private String name;
    private int age;

    private User(Builder builder){
        this.name = builder.name;
        this.age = builder.age;
    }

    public static class Builder{
        private String name;
        private int age;
        public Builder name(String name){
            this.name = name;
            return this;
        }
        public User build(){
            return new User(this);
        }
    }
}

调用:

User user = new User.Builder().name("张三").age(20).build();

5. 总结

无引用、无内存泄漏、独立存在、工程首选内部类


17.5 局部内部类(方法内)

1. 定义

定义在方法、代码块、构造器内部

public class Outer {
    public void test(){
        // 局部内部类
        class LocalInner{
            
        }
        LocalInner inner = new LocalInner();
    }
}

2. 底层原理

        (1)作用域仅限当前方法内,方法外完全不可见

        (2)可以访问外部类所有成员

        (3)JDK8 之前:只能访问方法内 final 局部变量

        (4)JDK8+:自动隐式 final,不用手动写

        (5)不能使用 public/private/protected/static

3. 使用场景

        (1)方法内临时专用类,用完即弃

        (2)简化方法内独立逻辑封装

        (3)极少业务开发使用,基本被 Lambda 替代


17.6  匿名内部类(无类名)

1. 定义

没有类名,直接 new 接口 / 抽象类直接实现

// 接口
interface Run{
    void run();
}

// 匿名内部类
Run run = new Run() {
    @Override
    public void run() {
        
    }
};

2. 底层原理

  1. 编译生成:外部类$数字.class
  2. 本质:快速创建一个临时子类 / 实现类
  3. 同样持有外部类引用
  4. 可使用方法内局部变量(隐式 final)

3. 使用场景

  1. 快速实现接口、抽象类一次性使用
  2. 线程快速创建
new Thread(new Runnable() {
    @Override
    public void run() {
        
    }
}).start();
  1. 事件回调、监听器、简易回调
  2. 线程池任务、定时任务临时实现

4. 淘汰趋势

JDK8 之后全部被 Lambda 表达式替代,业务开发几乎不再手写匿名内部类

18 OOP进阶:组合、继承、依赖架构关系

18.1 三大类关系底层定义

  • 继承(泛化):is-a关系,父子类强绑定,单继承,代码复用;

  • 组合:contains-a关系,整体包含部分,生命周期绑定,不可拆分;

  • 依赖:use-a关系,临时调用、弱关联,生命周期独立,耦合度最低;

  • 聚合:has-a 弱拥有,可分离;

  • 关联:use-a 长期持有引用;

五大关系强度排序(从强到弱):组合 > 聚合 > 关联 > 继承 > 依赖

区分口诀:

  • 继承:我就是你
  • 组合:我里面天生包含你,生死一起
  • 聚合:我包含你,你可以走
  • 关联:我认识你,长期持有
  • 依赖:我临时用一下你

18.2 逐个底层原理 + 代码实战 + 工程场景

1. 继承  (is-a)(泛化关系)

定义

子类 extends 父类,属于同一事物体系,父子层级,单继承。

底层特点

  • 编译期确定关系
  • 子类隐式拥有父类非私有成员
  • 构造执行:先父后子
  • 存在方法重写、向上转型

代码

// 父类
public class Animal{}
// 子类 is-a
public class Dog extends Animal{}

使用场景

  1. 抽取通用公共属性 / 通用模板
  2. 模板方法统一流程
  3. 统一父类规范行为

开发禁忌

  1. 禁止为了复用代码强行继承
  2. 禁止多层深继承(超过 3 层架构臃肿)
  3. 优先组合代替继承

2. 依赖关系  ( use-a)(最弱)

定义

临时调用,方法参数、局部变量、静态调用,用完断开。

核心特征

  • 生命周期不同步
  • 临时使用,不长期持有
  • 代码最弱耦合

三种依赖形式

  1. 方法形参
  2. 方法内局部对象
  3. 静态方法调用

标准代码

public class UserService {
    // 依赖:临时调用工具类
    public void sendMsg(SmsUtil sms){
        sms.send();
    }
}

场景

工具类调用、第三方接口调用、临时校验、临时计算


3. 关联关系 (use-a) (长期持有引用)

定义

长期持有成员变量引用,平等双向 / 单向关系。

特征

  • 全局成员变量持有
  • 生命周期互不绑定
  • 平等关系,无从属

代码

public class Student {
    // 关联:长期持有老师引用
    private Teacher teacher;
}

场景

用户 - 角色、账号 - 权限、业务平等对象引用


4. 聚合关系  (has-a) (弱拥有)

定义

整体包含部分,部分可脱离整体独立存在

核心特点

  • 整体和部分生命周期无关
  • 部分可以被多个整体共用
  • 松耦合

例子

班级 → 学生;公司 → 员工学生离职依旧存在

代码

public class ClassRoom {
    // 聚合:学生可独立存在
    private List<Student> studentList;
}

5. 组合关系  (contains-a) 强拥有(最强)

定义

整体包含部分,同生共死,不可拆分

核心特点

  1. 整体创建 → 内部直接 new 部分
  2. 整体销毁 → 部分一起销毁
  3. 部分不能脱离整体独立存在
  4. 耦合最高

经典例子

人 → 心脏;汽车 → 发动机;订单 → 订单项

标准代码

public class Car {
    // 组合:车创建同时创建发动机,不能单独存在
    private Engine engine = new Engine();
}

工程高频场景

  1. 主实体内置子明细实体
  2. 上下文内置内部核心组件
  3. 业务主流程内置核心执行单元

18.3 组合复用优于继承底层原因

继承属于静态绑定,编译期确定层级关系,父类修改直接影响子类,耦合度极高;组合属于动态关联,运行期自由组装对象,互不侵入、扩展灵活。

生产编码规范:优先组合、谨慎继承,规避继承带来的层级耦合、代码侵入问题。

18.4 工程开发使用规范(硬性标准)

  1. 通用基础字段、全局模板 → 用抽象类继承
  2. 工具能力、业务通用逻辑 → 全部用组合 / 依赖
  3. 整体不可拆分强绑定 → 组合
  4. 整体可拆分、可复用 → 聚合
  5. 平等业务对象引用 → 关联
  6. 临时调用工具 / 接口 → 依赖
  7. 业务扩展、多实现 → 接口 + 组合

18.5  实战架构分层关系搭配

  • 上层 Controller → 依赖 Service
  • Service → 组合 Mapper + 工具类
  • 基础实体 → 继承 BaseEntity
  • 业务枚举 / 能力 → 接口实现 + 组合注入
  • 树形结构 → 内部组合自身节点

19 this与super关键字底层字节码原理

19.1 this关键字底层逻辑

this本质:当前对象引用,JVM在构造方法、成员方法中隐式传入;可调用本类属性、方法、构造器;底层字节码为aload_0,加载栈中当前对象地址,无性能开销。静态方法禁止使用this,无实例引用。

19.2 super关键字底层逻辑

super本质:父类对象引用,指向父类元数据;可调用父类非私有属性、重写前方法、父类构造器;底层不会创建父类新对象,仅做引用跳转;构造方法中super()必须第一行,优先完成父类初始化。

19.3 this与super生产避坑

构造方法不可相互嵌套死循环调用;静态上下文禁止使用this/super;重写方法中super可保留父类通用逻辑,适配业务增强场景。

20 不可变类架构设计(生产高频)

20.1 不可变类定义与特性

实例创建后,属性不可修改、内存数据不可变更、任何方法都不能修改对象内部数据、所有字段初始化后固定;线程绝对安全、无需加锁、无并发冲突;

典型案例:String、包装类(Integer/Long)、BigDecimal、枚举。

底层原理:私有常量属性+无修改方法+禁止继承。

不可变类就是:final 类 + final 字段 + 无 setter + 构造器初始化 + 防御性拷贝,一旦创建永不修改,天生线程安全,高并发架构基石。

20.2 自定义不可变类8大规范

  1. 类添加final修饰,禁止被继承、防止子类篡改逻辑;

  2. 所有成员变量private final修饰,赋值后不可修改;

  3. 仅提供getter、无setter修改方法,所有方法只读,不修改任何属性;

  4. 构造方法深拷贝引用类型属性,防止外部引用篡改;

  5. 重写equals、hashCode,保证一致性判定;

  6. 构造器私有化 + 静态工厂 / Builder 创建

  7. 所有字段通过构造器一次性初始化;

  8. 实现 Cloneable 谨慎,或直接禁止克隆;

20.3 生产使用场景

配置常量、用户身份信息、支付订单快照、缓存固定数据;高并发场景优先使用不可变类,省去锁竞争开销,提升吞吐量。

1. DTO / VO / 响应体(最常用)

前端返回对象、接口参数,绝不允许被篡改

2. 配置类、常量类

系统启动加载后永不改变

3. 缓存对象(Redis、本地缓存)

防止缓存被意外修改导致脏数据

4. 多线程共享对象

无需加锁,直接并发安全使用

5. 枚举、错误码、字典类

固定不变,标准不可变

6. 领域对象(DDD 架构核心)

Value Object(值对象)必须不可变

21 OOP代码块执行顺序(面试必考)

21.1 四大代码块分类

  1. 静态代码块 static{}:(仅加载一次)。
  2. 实例代码块 {}
  3. 构造方法:静态资源随类加载初始化,实例资源随对象创建初始化。
  4. 普通成员方法

21.2 核心优先级(全局总规则)

静态优先于实例 → 父类优先于子类

执行顺序:

   父类静态 → 子类静态 → 父类实例 → 父类构造 → 子类实例 → 子类构造 → 普通方法

21.3 父子类继承执行顺序

  1. 父类静态代码块 → 子类静态代码块(类加载阶段,仅执行一次);

  2. 父类成员代码块 → 父类构造方法;

  3. 子类成员代码块 → 子类构造方法。

核心原理:静态优先、父类优先、静态只执行一次;生产禁止在静态代码块做耗时IO、数据库连接操作,拖慢类加载速度。

21.4 无继承:单个类内部执行顺序

public class Demo {
    // 1. 静态变量初始化
    private static int a = initStatic();
    // 2. 静态代码块
    static {
        System.out.println("静态代码块");
    }
    // 3. 实例变量初始化
    private int b = initInstance();
    // 4. 实例代码块
    {
        System.out.println("实例代码块");
    }
    // 5. 构造方法
    public Demo(){
        System.out.println("构造方法");
    }
    // 普通方法
    public void test(){}
}

执行顺序

  1. 静态成员变量赋值
  2. 静态代码块:静态内容只执行一次,类加载时执行。
  3. 实例成员变量赋值
  4. 实例代码块
  5. 构造方法:实例内容每次 new 对象都执行。
  6. 调用普通成员方法

21.5 有继承:父子类完整执行顺序(重中之重)

最终固定顺序(背诵版)

  1. 父类静态变量
  2. 父类静态代码块
  3. 子类静态变量
  4. 子类静态代码块—————— 静态全部结束(只执行一次)——————
  5. 父类实例变量
  6. 父类实例代码块
  7. 父类构造方法
  8. 子类实例变量
  9. 子类实例代码块
  10. 子类构造方法—————— 对象创建完成 ——————
  11. 调用重写 / 普通成员方法

极简口诀:

先静后实,先父后子,变量先行,块在构造前

21.6 使用场景

场景 1:静态代码块 + 实例代码块 + 构造

class Father{
    static {System.out.println("父静态");}
    {System.out.println("父实例");}
    Father(){System.out.println("父构造");}
}
class Son extends Father{
    static {System.out.println("子静态");}
    {System.out.println("子实例");}
    Son(){System.out.println("子构造");}
}
// new Son();

输出顺序父静态 → 子静态 → 父实例 → 父构造 → 子实例 → 子构造

场景 2:存在方法重写(多态执行顺序)

父类构造里调用重写方法

  1. 先走父类实例、父类构造
  2. 此时子类还未初始化完成
  3. 直接执行子类重写后的方法

开发禁忌:构造方法内不要调用可重写方法,极易出现空指针

场景 3:静态代码块执行时机

  • 主动 new 对象
  • 访问静态变量 / 静态方法
  • 子类初始化会触发父类静态
  • 引用类名不创建对象也会执行静态块

21.7 最全执行顺序总结(一页背完)

  1. 父类静态变量 → 父类静态代码块
  2. 子类静态变量 → 子类静态代码块
  3. 父类实例变量 → 父类实例代码块
  4. 父类构造方法
  5. 子类实例变量 → 子类实例代码块
  6. 子类构造方法
  7. 执行普通成员方法

21.8 企业开发规范

  1. 静态代码块只做全局一次性初始化(配置、加载资源)
  2. 实例代码块少用,统一写进构造
  3. 构造方法禁止调用抽象方法、重写方法
  4. 静态资源尽量懒加载,避免项目启动卡顿

22 OOP架构师编码黄金规约

  1. 遵循单一职责:一个类只负责一类业务,禁止大杂烩臃肿类;

  2. 最小权限原则:属性私有、方法按需开放,严控访问权限;

  3. 优先组合复用:业务拓展优先组合,少用多层继承;

  4. 接口抽象隔离:依赖抽象而非实现,降低代码耦合;

  5. 慎用可变对象:核心业务实体优先设计不可变类,保障并发安全;

  6. 重写规范:重写方法必须保留@Override,严格遵循两同两小一大;

  7. 构造方法私有化:工具类、单例类私有构造,禁止实例化。

23 方法体系架构、重载原理、重写原理、递归栈底层、参数传递模型

23.1 方法体系架构

1 完整语法结构

[修饰符] 返回值类型 方法名(形参列表) [异常列表]{
    方法体
    return 结果;
}
  • 方法签名:仅由方法名 + 形参列表组成,返回值、修饰符、异常都不参与
  • 核心作用:代码复用、逻辑解耦、模块化拆分

2 方法分类

  1. 实例方法:隶属于对象,必须实例调用,可访问静态 / 实例成员
  2. 静态方法:隶属于类,类名直接调用,不能直接访问实例变量与实例方法
  3. 构造方法:无返回值、名与类同名,创建对象时自动执行,负责成员初始化
  4. 私有方法:仅本类内部调用,对外隐藏实现

3 底层执行流程

  1. 方法调用 → JVM 在虚拟机栈创建栈帧并入栈
  2. 形参入局部变量表、局部变量初始化、操作数栈运算
  3. 代码执行完毕 / 遇见return → 当前栈帧出栈销毁
  4. 回到调用位置继续执行

4 方法执行内存规则

  • 所有局部变量、形参、方法内常量都存虚拟机栈
  • 方法执行结束,栈帧销毁,局部变量立刻失效

23.2 方法重载 Overload 设计原理

1 定义

同一个类中,方法名相同,参数列表不同,构成重载。

2 构成条件

  1. 方法名必须一致
  2. 参数个数、类型、顺序任意一项不同
  3. 返回值、权限修饰符、异常不能作为重载判断依据

3 底层实现原理

  • 绑定时机:编译期静态绑定
  • 编译阶段编译器根据实参类型自动匹配最优方法
  • 字节码层面:同名重载方法拥有不同方法描述符,JVM 可直接区分
  • 匹配优先级:精准匹配 → 自动类型转换 → 可变参数

4 场景

同一行为适配多种参数,如println、工具类多参构造


23.3 方法重写 Override 设计原理

1 定义

子类继承父类,子类重新定义与父类完全一致的方法,覆盖父类原有逻辑。

2 重写硬性规则:两同两小一大

  • 两同:方法名相同、形参列表相同
  • 两小
    1. 子类抛出异常范围 ≤ 父类异常
    2. 子类访问权限 ≥ 父类权限
  • 一大:子类返回值类型可以是父类返回值的子类(协变返回)

3 底层实现原理

  1. 绑定时机:运行期动态绑定
  2. 底层依赖虚方法表
    • 父类方法存入虚方法表
    • 子类重写后,直接替换虚方法表中对应方法入口
  3. 程序运行时,依据对象真实类型查找虚方法表执行对应方法
  4. static/final/private 属于静态方法,不能被重写

4 重载与重写核心区别速记

表格

对比 重载 重写
位置 同类 父子类
时机 编译期绑定 运行期绑定
签名 参数不同 签名完全一致
多态 不体现多态 面向对象多态核心

23.4 递归调用栈底层原理

1. 递归本质

方法自身调用自身,必须定义递归出口(终止条件)

2. 底层栈执行流程

  1. 每一次递归调用,都会新建独立栈帧压入虚拟机栈
  2. 未触发出口前,所有栈帧持续堆叠积压
  3. 满足终止条件后,从最内层栈帧开始逐层返回、弹栈回溯
  4. 所有栈帧弹出完成,递归结束

3. 底层风险

  • 递归深度过大 → 虚拟机栈内存溢出 → 抛出 StackOverflowError
  • 优化方案:限制深度、改用迭代、尾递归优化

4. 常用场景

树形遍历、目录扫描、阶乘、斐波那契、分治算法


23.5 Java 参数传递模型(架构师定论)

1. 终极结论

Java 只有值传递,不存在任何引用传递

2. 基本数据类型传参

  • 传递内容:栈中原始数值副本
  • 操作影响:方法内修改形参,不会改变外部实参
  • 原理:两份独立内存,互不干涉

3. 引用数据类型传参(对象、数组、集合)

  • 传递内容:堆内存地址值副本
  • 特点:形参、实参指向同一个堆内存对象
  1. 修改对象内部成员属性:外部实参同步生效
  2. 直接给形参重新赋值新对象:外部实参无任何变化

4. 高频面试易错点

  1. String 传参修改无效:String 不可变,赋值只是更换地址副本
  2. 自定义对象改属性生效:共用同一堆实体
  3. 数组传参修改元素生效,重新 new 数组无效

5. 一句话总结

所有参数一律拷贝副本传入方法,基本类型拷贝值,引用类型拷贝内存地址。

23.6 高频背诵面试题

1.方法重载和重写区别?

        重载同类编译期,重写父子运行期。

2.方法签名包含什么?

        方法名 + 参数列表,不含返回值。

3.递归为什么容易栈溢出?

        层层压栈,栈帧过多超出栈内存上限。

4.Java 到底是值传递还是引用传递?

        纯值传递,引用类型传递的是地址副本。

        静态方法能否调用非静态方法?不能,静态优先加载,无对象实例。

第三部分 Java异常体系 架构师底层原理(完整版无删减)

13 异常顶层架构与源码继承体系

13.1 异常顶层继承链路(源码级)

整体异常架构族谱:

java.lang.Throwable  【异常顶层根类】
├─ Error  系统级严重错误 → 程序无法处理
└─ Exception  程序可处理异常
   ├─ RuntimeException  运行时异常(非受检)
   └─ 编译时异常(受检异常 CheckedException)

Java异常体系根节点为Throwable,下辖两大分支:Error(系统致命错误)、Exception(业务可处理异常)。

完整继承链路:Throwable → Error/Exception → RuntimeException。

所有异常类底层都继承Throwable,底层依赖JVM原生方法实现异常栈快照、堆栈追踪。

Throwable 顶层:

  • 所有异常 / 错误共同父类
  • 底层携带:异常信息、异常原因、堆栈追踪栈帧
  • JVM 抛出、代码手动抛出都继承此类

13.2 Error 系统级致命错误

定义:JVM 级别致命错误、操作系统层面的不可修复严重错误,无需业务捕获处理,程序直接终止。

底层特征:不受代码管控、不可捕获、无需声明抛出。

高频生产Error:

  •   StackOverflowError栈溢出
  •   OutOfMemoryError内存溢出
  •    NoClassDefFoundError类加载失败
  •    LinkageError链接异常

架构原则:Error属于底层硬件、JVM故障,业务代码禁止捕获、无需try-catch,资源耗尽,不属于业务异常。

13.3 Exception 业务可处理异常

Exception分为受检异常(编译异常)非受检异常(运行时异常),是业务开发核心管控异常。受检异常:继承Exception、非RuntimeException,编译期强制校验,必须throws抛出或try-catch捕获;

非受检异常:继承RuntimeException,编译期无校验,运行时触发,无需强制处理。

1. 运行时异常 RuntimeException(非受检异常)

  • 编译不强制捕获,运行时才抛出
  • 根源:代码逻辑 BUG
  • 底层触发:JVM 指令执行异常、数组越界、空指针、类型强转失败
  • 常见:NullPointerExceptionArrayIndexOutOfBoundsClassCastExceptionArithmeticException

2. 编译期受检异常(CheckedException)

  • 编译强制处理:要么 try-catch 捕获,要么 throws 向上抛出
  • 根源:外界环境不可控,不是代码 bug
  • 底层设计初衷:强制开发者提前预判风险
  • 常见:IOExceptionSQLExceptionParseException

13.4 异常底层执行机制

  1. 程序正常执行 → 字节码顺序执行
  2. 一旦触发异常 → JVM 中断当前执行流
  3. 立即创建异常对象,封装当前方法调用栈信息
  4. 向上逐层抛出,寻找匹配catch
  5. 找到则进入异常分支执行;找不到最终抛给 JVM,线程终止

14 异常分类详解+生产高频异常汇总

14.1 三大异常分类架构区别

异常类型

继承关系

编译校验

处理方式

生产场景

Error致命错误

Throwable直接子类

无校验

不可捕获、无需处理

OOM、栈溢出、类加载失败

受检异常

Exception直系子类

强制校验

throws/try-catch

IO流、文件、网络、数据库连接

运行时异常

RuntimeException子类

无校验

主动预判、捕获兜底

空指针、参数非法、类型转换

14.2 生产高频异常清单(面试+线上故障)

  1. 运行时异常:NullPointerException空指针、ArrayIndexOutOfBoundsException数组越界、ClassCastException类型转换、IllegalArgumentException非法参数、ConcurrentModificationException并发修改、ArithmeticException算术异常。

  2. 受检异常:IOException IO流异常、SQLException数据库异常、ClassNotFoundException类未找到、TimeoutException超时异常。

  3. 架构级异常:NoSuchMethodException反射方法不存在、IllegalAccessException权限拒绝。

15 异常底层字节码执行原理

15.1 try-catch-finally字节码指令

编译期javac会将try-catch翻译成异常表(Exception Table),无需额外线程、无复杂指令。

核心字节码指令:athrow主动抛出异常、jsr跳转指令、ret返回指令。

异常表记录:异常起始行、结束行、捕获行、异常类型。

JVM执行代码时,触发异常后匹配异常表,跳转至对应catch代码块。

15.2 finally底层强制执行机制

编译期优化:无论正常执行、异常抛出、return返回,编译器都会在分支末尾插入finally代码。

唯一不执行finally场景:System.exit(0) 直接终止JVM进程,切断虚拟机执行链路。

底层原理:字节码层面复制finally代码,嵌入所有出口分支,保障资源强制释放。

15.3 return+finally返回值底层坑点

底层执行顺序:return先拷贝返回值存入临时栈帧 → 执行finally代码块 → 返回临时栈帧值。

特性:finally修改基础类型返回值无效、修改引用类型属性生效。

生产禁忌:禁止在finally中写return、throw,会覆盖上层异常、丢失异常栈信息,造成线上故障隐蔽难查。

16 异常抛出、捕获语法底层规范

16.1 throw 主动抛出异常

语法层面手动抛出单个异常对象,中断当前执行链路;底层athrow指令直接抛出栈中异常实例,终止方法执行。

适用场景:参数校验、业务规则不满足、主动拦截非法请求。

16.2 throws 方法声明抛出

作用于方法签名,声明该方法存在异常风险,将异常责任抛给调用方;仅做编译期标记、无运行期性能开销。

架构设计:底层工具方法、中间件通用方法使用throws,上层业务统一捕获处理。

16.3 重写方法异常约束(面试必考)

子类重写父类方法,异常遵循缩小原则:子类抛出受检异常不能大于父类、禁止抛出父类没有的受检异常、运行时异常无严格限制。

底层原理:保证多态调用异常兼容性,避免上层调用逻辑异常崩溃。

17 try-with-resources 语法糖底层源码

17.1 诞生背景

传统try-catch-finally手动关闭流,代码冗余、极易漏关资源,造成文件句柄泄露、连接池耗尽。JDK7推出try-with-resources,自动实现资源关闭,简化编码。

17.2 底层实现原理

编译期语法糖,编译器自动生成finally代码块,调用AutoCloseable接口close()方法;

所有资源类(IO流、数据库连接、Redis连接)均实现AutoCloseable。多资源声明时,编译期生成嵌套try-finally,逆序关闭资源,规避资源依赖冲突。

17.3 生产强制规约

所有流资源、连接资源、可关闭资源,生产代码禁止手动finally关闭,一律使用try-with-resources;减少代码冗余、杜绝资源泄漏、降低线上句柄溢出风险。

18 异常栈追踪原理与打印机制

18.1 异常栈快照底层

异常创建时(new Exception),JVM同步生成当前线程栈快照,记录方法调用链路、行号、类名;底层native方法抓取栈帧信息,存入stackTrace数组。throw抛出时,栈快照同步抛出,用于日志排查。

18.2 三种异常打印区别(生产避坑)

  1. e.printStackTrace():控制台打印完整栈信息,IO阻塞、生产禁止使用;

  2. e.getMessage():仅打印异常简短描述,无栈链路,排查困难;

  3. 日志工具log.error("异常",e):打印完整异常栈、持久化日志,生产唯一规范写法。

18.3 异常栈丢失底层坑点

重复抛出同一异常对象、异步线程抛出异常、finally重抛异常,会导致异常栈覆盖、链路丢失。

解决方案:每次异常新建异常对象、异步单独捕获、禁止重复抛出。

19 自定义业务异常(架构师核心设计)

19.1 原生异常痛点

JDK原生异常语义模糊、无业务码、无法区分业务故障类型;线上排查无法快速定位业务模块,不适合分布式项目架构。

19.2 自定义异常分层架构

  1. 顶层通用业务异常:BusinessException,统一父类;

  2. 细分模块异常:用户异常、支付异常、订单异常;

  3. 系统底层异常:网关异常、限流异常、中间件异常。

19.3 自定义异常规范

继承RuntimeException(非受检),无需每层代码捕获;

封装错误码、错误描述、异常链路;

统一全局异常处理器拦截,返回标准化JSON结果;

禁止自定义受检异常,增加代码侵入性。

19.4 业务自定义异常分类

  • 继承RuntimeException非受检,项目主流用法
  • 继承Exception受检异常,强制调用方处理

20 异常性能底层剖析+优化方案

20.1 异常为什么耗时高?

异常创建阶段,JVM需要抓取线程栈帧、遍历方法链路、生成栈快照,涉及大量native底层操作;高频抛出异常会造成CPU飙升、线程阻塞,严重影响吞吐量。

20.2 高频异常优化手段

  1. 业务判断优先if前置拦截,禁止用异常做业务分支;

  2. 全局缓存异常对象,避免频繁new生成异常;

  3. JVM参数优化:-XX:-StackTraceInThrowable 关闭非必要栈追踪;

  4. 高频接口禁止抛出运行时异常,前置参数校验。

21 生产级异常编码黄金规约(架构师定稿)

  1. 精准捕获:禁止catch (Exception e) 大范围吞异常,精准捕获指定异常,防止隐蔽故障;

  2. 禁止空捕获:catch代码块禁止空实现,必须打印日志或抛出上层异常;

  3. 资源必释放:IO、连接、线程资源,优先try-with-resources,兜底finally关闭;

  4. 异常不吞栈:日志必须打印完整异常栈,禁止只打印异常描述;

  5. 禁止滥用异常:判断逻辑用if,异常仅用于非正常业务场景;

  6. 分层异常处理:底层抛异常、上层拦截、全局统一处理;

  7. 禁止修改异常栈:禁止重复抛出、禁止清空栈信息,保留原始故障链路;

  8. 分布式异常:透传异常码、上下文,跨服务追踪故障。

18 异常体系架构分类

18.1 体系结构

Throwable → Error / Exception。

Error:系统级严重错误,无需处理(OOM、栈溢出);

Exception:业务可处理异常;

受检异常(编译异常):必须显式捕获或抛出;

非受检异常(运行时异常):RuntimeException,无需强制处理。

18.2 常见生产异常

空指针、数组越界、类型转换、非法参数、并发修改、IO异常。

19 try-catch-finally底层执行机制

finally 无论是否异常、return都会执行;

唯一不执行:System.exit(0) 直接终止JVM。

生产规范:资源释放统一放在finally。

19.1 执行底层顺序:

  • try 正常执行 → 执行 finally → 方法结束
  • try 出现异常 → 跳对应 catch → 执行 finally → 结束
  • try/catch 内有 return:先执行 finally,再执行 return
  • finally 内部写 return:会吞掉上层异常,生产严禁使用

19.2 传统写法:try-catch-finally(手动关闭资源)

缺点:代码臃肿、容易忘记关闭流、嵌套多、容易出 bug。

import java.io.FileInputStream;
import java.io.IOException;

public class TryCatchFinallyDemo {
    public static void main(String[] args) {
        FileInputStream fis = null;

        try {
            // 1. 打开资源
            fis = new FileInputStream("test.txt");
            byte[] buffer = new byte[1024];
            fis.read(buffer);
            System.out.println("读取成功");

        } catch (IOException e) {
            // 2. 捕获异常
            System.err.println("文件读取异常");
            e.printStackTrace();

        } finally {
            // 3. 必须在 finally 中关闭资源
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("finally 执行:资源已关闭");
        }
    }
}

19.3 企业痛点

  • finally 里还要嵌套 try-catch
  • 忘记判空 fis != null 会空指针
  • 代码非常臃肿,生产基本不用了

20 try-with-resources底层原理

实现AutoCloseable接口的资源,可自动关闭;

编译期自动生成finally关闭代码,规避手动漏关资源;

生产IO、数据库连接、流资源一律优先使用。

20.1 底层原理(JDK7+):

  • 专门用于自动关闭流资源
  • 底层要求:资源类必须实现 AutoCloseable 接口
  • 编译期语法糖:编译器自动生成finally+close()代码
  • 优势:无需手动写关闭代码,避免资源泄漏

20.2 企业标准写法:try-with-resources

优点:自动关闭资源、代码极简、不会泄漏、官方推荐。

规则:只要实现了 AutoCloseable 接口的类,都能自动关闭。

(流、连接、Socket、Redis 连接、数据库连接等全部支持)

import java.io.FileInputStream;
import java.io.IOException;

public class TryWithResourcesDemo {
    public static void main(String[] args) {
        // 资源定义在 try() 里 → 自动关闭
        try (FileInputStream fis = new FileInputStream("test.txt")) {
            byte[] buffer = new byte[1024];
            fis.read(buffer);
            System.out.println("读取成功");

        } catch (IOException e) {
            System.err.println("文件处理异常");
            e.printStackTrace();
        }
        // 无需 finally!无需 close()!
    }
}

数据库连接 + 预处理语句 + 结果集 全部自动关闭:多个资源同时自动关闭

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class MultiResourceDemo {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "123456";

        // 多个资源用 ; 分隔 → 全部自动关闭
        try (Connection conn = DriverManager.getConnection(url, user, password);
             PreparedStatement pstmt = conn.prepareStatement("select * from user");
             ResultSet rs = pstmt.executeQuery()) {

            while (rs.next()) {
                System.out.println(rs.getString("name"));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

自定义资源使用 try-with-resources(企业高阶):只要实现 AutoCloseable,就能自动关闭。

// 自定义资源
class MyResource implements AutoCloseable {
    public void doSomething() {
        System.out.println("执行业务逻辑");
    }

    @Override
    public void close() {
        System.out.println("自动关闭:MyResource 已关闭");
    }
}

// 使用
public class CustomResourceDemo {
    public static void main(String[] args) {
        try (MyResource resource = new MyResource()) {
            resource.doSomething();
        }
    }
}

20.3 底层原理(架构师必懂)

  1. 编译后自动生成 finally + close ()
  2. 异常不会被覆盖,会抑制异常(Suppressed Exceptions)
  3. 资源一定被关闭,不会泄漏

20.4 总结

  • try-catch-finally:手动关流,代码臃肿,企业已淘汰
  • try-with-resources:自动关流、简洁安全、企业唯一标准写法

21 final、finally和finalize的区别

final关键字

final是一个修饰符,它可以用来修饰类、方法和变量。当final修饰一个类时,表示这个类不能被继承,换句话说,没有其他类可以是它的子类。当final修饰一个方法时,这个方法不能被子类重写。而当final修饰一个变量时,这个变量的值一旦被初始化之后就不能被改变,它成为了一个常量。例如:

public class FinalExample {
   public static final int SOME_CONSTANT = 42;
   final int finalInstanceVariable;
   public FinalExample() {
       finalInstanceVariable = 2; // final变量可以在构造器中初始化
   }
   public final void finalMethod() {
       // 这个方法不能被子类重写
   }
}


finally块

finally是异常处理结构的一部分,它提供了一个代码块,无论是否捕获到异常,这个代码块中的代码都会被执行。这通常用于清理资源,比如关闭文件流或数据库连接。在try-catch结构后面,可以添加一个finally块来确保执行清理操作:

try {
   // 可能会抛出异常的代码
} catch (Exception e) {
   // 处理异常
} finally {
   // 清理代码,总是会执行
}


finalize方法

finalize是Object类的一个方法,它被设计用来在对象被垃圾回收器回收之前执行清理操作。虽然Java允许使用finalize方法来清理资源,但它的执行时间是不确定的,因为垃圾回收的时机是不确定的。因此,通常不推荐使用finalize方法,并且从JDK 9开始,它已经被标记为弃用。下面是一个finalize方法的例子:

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


总结来说,final用于声明不可变的实体,finally确保关键代码块的执行,而finalize用于对象销毁前的清理工作。由于finalize的不确定性和弃用状态,推荐使用try-with-resources和其他清理机制来代替finalize方法。

22 生产异常设计原则

  1. 精准捕获,禁止catch大Exception吞异常

  2. 分层自定义业务异常,统一错误码

  3. 异常日志必须打印堆栈,便于线上排查

  4. 禁止用异常做正常业务逻辑分支

23 全局异常处理底层架构

  1. 基于AOP 思想统一拦截
  2. 底层:异常向上冒泡至顶层控制器统一捕获
  3. 作用:解耦异常处理、统一返回格式、日志统一打印、熔断兜底

24  企业项目异常分层架构(架构师标准)

24.1 分层设计思想

  1. 底层原生异常:JDK 自带所有异常
  2. 自定义基础全局异常:继承 RuntimeException
  3. 细分业务异常:登录异常、权限异常、参数异常、业务流程异常
  4. 全局统一异常处理器:@RestControllerAdvice 统一拦截

24.2 标准自定义运行时异常模板

public class BusinessException extends RuntimeException {
    // 错误码
    private Integer code;
    // 消息
    private String msg;

    public BusinessException(Integer code, String msg) {
        super(msg);
        this.code = code;
        this.msg = msg;
    }
    // 携带原始异常,构建异常链
    public BusinessException(Integer code, String msg, Throwable e) {
        super(msg, e);
        this.code = code;
        this.msg = msg;
    }
}

24.3 全局异常统一拦截架构

  1. 前端请求 → Controller
  2. 业务校验不通过 → 主动抛自定义业务异常
  3. 不手动 try-catch 业务异常
  4. 统一由 @RestControllerAdvice 捕获
  5. 封装统一 Result 结果返回前端
  6. 系统未知异常统一包装为 500 友好提示

架构优势

  • 业务代码干净无冗余 try-catch
  • 异常统一格式、统一日志、统一响应
  • 前后端异常交互标准化
  • 异常分级:参数异常、业务异常、系统异常、第三方异常

架构目标:代码少异常、异常统一抓、提示人性化、日志全溯源

25 高频架构面试必背

1.Error 和 Exception 区别

         Error 是 JVM 致命错误无法处理;Exception 是业务可捕获异常。

2.运行时异常与受检异常区别

        运行时编译不报错,是代码 bug;受检编译强制处理,是环境问题。

3.finally 一定会执行吗

        不会,调用 System.exit 直接退出 JVM 则不执行。

4.try-with-resources 原理

        编译期自动补全 finally 调用 close 方法,实现资源自动释放。

5.异常为什么影响性能

        创建异常会采集完整方法调用栈,开销大。

6.重写方法异常规则

        子类抛出异常范围必须小于等于父类。

7.空指针异常底层成因

        引用变量未指向堆对象,直接访问成员触发 JVM 空访问校验。

8.五大关键字底层原理

        (1). try

        监听代码块,监控内部所有异常,出现异常立即跳转,不再顺序执行。

        (2). catch

        异常匹配拦截,从上到下匹配,父类异常放最后,否则子类失效。底层:JVM 异常类型比对机制。

        (3). finally

                1.绝大多数场景必执行

                2.唯一不执行:System.exit(0) 直接退出 JVM

                3.底层执行顺序:try 正常 / 异常执行完毕 → 优先执行 finally再 return

                4.用途:资源关闭、连接释放、状态重置

        (4). throws

        异常向上抛,声明本方法存在异常,交给调用者处理底层:修改方法异常表,编译期校验受检异常。

        (5). throw

        手动主动抛出异常对象,中断流程,用于自定义业务异常。

第四部分 Java集合框架 完整版深度架构

22 集合框架整体架构设计思想

22.1 集合两大顶层架构

Collection 单列集合(存单个元素)
├─ List 有序可重复:ArrayList、Vector、LinkedList
├─ Set 无序不可重复:HashSet、LinkedHashSet、TreeSet
└─ Queue 队列集合:ArrayDeque、PriorityQueue

Map 双列集合(键值对 K-V)
├─ HashMap:LinkedHashMap
├─ TreeMap:
└─ HashTable/Properties

Collection单列集合:List、Set、Queue;

Map双列集合:键值对存储,Key唯一。

核心特点速记

  • Collection:单个对象存储
  • Map:键值对映射存储
  • 所有集合底层全部依赖数组 + 链表 + 红黑树实现
  • JDK1.8 是集合底层重大优化分水岭

22.2 集合架构设计初衷

替代固定长度数组,提供 动态扩容、操作繁琐缺陷;

统一 API、解耦接口与实现、泛型类型安全、多数据结构适配。

23 List集合底层源码架构与生产选型

23.1 有序可重复(索引有序)

继承结构:

List
├─ ArrayList
├─ Vector
└─ LinkedList

23.1 ArrayList 底层原理

  1. 底层结构:底层动态Object数组,JDK7+空参构造延迟初始化,默认容量10;扩容1.5倍,底层Arrays.copyOf迁移数据;
  2. 默认容量:10
  3. 扩容机制:
    1. 无参构造:初始空数组,首次 add 才初始化 10。
    2. 满容时扩容为 原容量 × 1.5(原容量 << 1 + 原容量)。
    3. 底层调用 Arrays.copyOf开辟新内存 → 数据迁移
  4. 存取特点
    1. 查询快:连续内存、数组随机访问 O (1)、遍历快。
    2. 增删慢:中间增删需元素位移、扩容有内存拷贝开销、线程不安全
  5. 线程安全:非线程安全
  6. 底层源码关键点
    1. 底层数组:transient Object[] elementData
    2. 最大容量:Integer.MAX_VALUE - 8
    3. 批量添加 addAll 高效合并

23.2 LinkedList

  1. 底层结构:底层双向链表(JDK1.6 前为循环链表),无固定容量、无扩容拷贝;
  2. 无初始化容量、无扩容机制
  3. 存取特点
    • 查询慢:无随机访问、遍历查询 O (n)、内存占用更大(节点 + 前后指针)、遍历寻址开销大。
    • 首尾增删极快:任意位置增删 仅修改指针、不移动数据、O (1)、效率高。
  4. 同时实现List + Deque,可当队列、栈使用
  5. 非线程安全

23.3 企业生产选型

查询遍历多选ArrayList;频繁中间插入删除选LinkedList;尾部追加两者性能接近。

  • 查询多、遍历多 → ArrayList
  • 首尾频繁增删,频繁中间插入 / 删除、内存宽松用 → LinkedList
  • 并发场景:CopyOnWriteArrayList

23.4 Vector

  • 底层数组,线程安全
  • 扩容:2 倍扩容
  • 全方法加synchronized,效率极低
  • 企业开发彻底淘汰

24 Set集合底层架构原理与去重机制

24.1 Set无序不可重复(无索引)

继承结构:

Set
├─ HashSet
├─ LinkedHashSet
└─ TreeSet

HashSet底层依托HashMap,元素存Key,Value为静态空对象;

去重机制:hashCode定位哈希桶 + equals比对内容双重校验;

LinkedHashSet:哈希表+双向链表,保留插入顺序;

TreeSet:依托TreeMap红黑树,自然排序或自定义比较器排序。

24.2 HashSet

  1. 底层本质底层就是 HashMap,元素存在 Key 上,Value 为固定 Object 常量。
  2. 存储元素 = HashMap 的 key,value 固定占位 Object。
  3. 去重原理:hashCode() + equals() 双重判定。
  4. 无序、去重、非线程安全。
  5. 默认容量 16,负载因子0.75。

24.3 LinkedHashSet

  • 继承 HashSet,哈希表 + 双向链表保持插入顺序
  • 底层:LinkedHashMap
  • 有序(插入顺序)+ 去重
  • 比 HashSet 多维护一条双向链表记录顺序

24.4 TreeSet

  1. 底层:底层 TreeMap(红黑树)
  2. 自动自然排序 / 自定义比较器 Comparator 排序。
  3. 去重 + 排序。
  4. 元素必须实现Comparable接口。

24.5 Set 选型

  • 只需去重无序 → HashSet
  • 去重保留插入顺序 → LinkedHashSet
  • 去重 + 自动排序 → TreeSet

25 Map集合底层架构(HashMap JDK1.8)

25.1 底层结构

继承结构:

Map
├─ HashMap(主流)
├─ LinkedHashMap
├─ TreeMap
├─ HashTable
└─ Properties

25.1.1 底层结构(JDK1.8 重大升级)

  • 数组 + 链表 + 红黑树(拉链法 + 树化优化)
    • 数组:哈希桶(默认容量 16,必须 2 的幂)。
    • 链表:解决哈希冲突,链地址法。
    • 红黑树:链表过长时优化查询。

25.1.2 树化 / 链化条件(面试必背)

  • 树化:链表长度 ≥ 8 并且 数组长度 ≥ 64
  • 链化:红黑树节点 ≤ 6,退化为链表。

25.1.3 扩容机制

  • 负载因子 0.75(时间 / 空间平衡,泊松分布最优)。
  • 扩容阈值 = 容量 × 0.75
  • 扩容为 原容量 × 2(保持 2 的幂)。

25.1.4 总结

     数组+链表+红黑树;链表长度≥8且数组长度≥64触发树化;节点≤6退化为链表。

25.2 HashMap 底层架构 JDK1.7 VS JDK1.8

(1) JDK1.7

  • 底层:数组 + 单向链表
  • 头插法插入元素
  • 链表过长查询效率极低

(2) JDK1.8 架构大改版(企业核心)

  1. 底层结构:数组 + 单向链表 + 红黑树
  2. 插入方式:尾插法
  3. 树化条件
    • 数组长度 ≥ 64
    • 链表长度 ≥ 8 → 转为红黑树
  4. 退化条件
    • 红黑树节点 ≤ 6 → 退化为链表
  5. 默认参数
    • 初始容量:16
    • 负载因子:0.75
    • 临界值 = 容量 * 0.75
  6. 扩容机制
    • 满足临界值自动扩容2 倍
    • 扩容重新哈希移位,优化寻址效率

(3) HashMap 存取流程

  1. 计算 key 哈希值 → 定位数组下标
  2. 下标为空:直接存入
  3. 下标不为空:遍历链表 / 红黑树
  4. key 相等覆盖 value,不相等追加节点
  5. 达到阈值触发树化 / 扩容

(4) 哈希冲突解决

  1. 扰动函数:优化 hash 分布,减少碰撞
  2. 链表法挂载冲突元素
  3. 长链表转为红黑树提升查询效率

(5)  HashMap 线程不安全根源

  • 多线程扩容导致 链表死循环、数据覆盖、节点丢失
  • put 无锁,并发替换。
  • 红黑树旋转并发异常。

25.3  LinkedHashMap

  • 继承 HashMap 
  • 底层维护双向链表
  • 保留插入顺序 / 访问顺序
  • LRU 最近最少淘汰缓存底层实现

25.4 TreeMap

  • 底层:红黑树
  • 按键自动排序
  • 适合有序 key 场景

25.5 HashTable

  • 线程安全、效率极低
  • 不允许 null 键 null 值
  • 已淘汰

25.6 Properties

  • 继承 HashTable
  • 专门读取配置文件.properties

25.7 扩容机制

默认容量16,负载因子0.75;扩容阈值=容量×0.75,扩容为原2倍,保持2的幂次便于哈希寻址。

25.8 线程不安全根源

多线程扩容链表形成循环引用、数据覆盖;并发put无同步控制引发数据丢失。

25.9 LinkedHashMap与HashTable 、HashTable vs HashMap

(1)  LinkedHashMap:

      LinkedHashMap:HashMap+双向链表,保留插入顺序,可实现LRU缓存淘汰;

      HashTable:全表锁、线程安全、性能低,不允许null键值。

(2) HashTable vs HashMap:

  • HashTable:synchronized 全表锁、线程安全、效率低、不允许 null Key/Value
  • HashMap:线程不安全、效率高、允许 1 个 null Key

25.10 并发安全集合(JUC 架构)

(1). 线程不安全集合解决方案

  1. Collections.synchronizedXXX 包装(效率低)
  2. JUC 高性能并发集合(企业首选)

(2). 主流并发集合底层原理

1. CopyOnWriteArrayList

  • 写时复制
  • 写操作新建数组复制,修改后替换原数组
  • 读无锁、写加锁
  • 适合读多写少
  • 缺点:内存占用高、实时性弱

2. ConcurrentHashMap(并发王者)

  1. JDK1.7:分段锁 Segment
  2. JDK1.8:CAS + synchronized 锁数组首节点
  3. 粒度更细、并发更高
  4. 空值不允许、线程安全、高效并发
  5. 业务缓存、高并发接口首选

3. ConcurrentSkipListMap

  • 并发有序 Map,高并发排序场景

25.11 集合工具类与遍历架构

1. 三大遍历方式

  1. 普通 for:有索引集合最快
  2. 增强 for:底层迭代器,简洁通用
  3. Iterator 迭代器:遍历中安全删除元素

2. 遍历删除禁忌

  • 增强 for 遍历直接 remove → 并发修改异常
  • 正确:迭代器迭代删除

3. Collections 工具类

  • 空集合创建、线程安全包装、排序、二分查找、同步集合

4. JDK8 Stream 流式架构

  • 串行流stream()、并行流parallelStream()
  • 底层 ForkJoinPool 分支合并池
  • 集合批量过滤、映射、聚合、分组、排序

25.12 集合底层核心性能对照表

集合 查询 增删 有序 去重 线程安全 底层结构
ArrayList 极快 有序 动态数组
LinkedList 极快 有序 双向链表
HashSet 无序 HashMap
TreeSet 排序 红黑树
HashMap 极快 无序 key 唯一 数组 + 链表 + 红黑树
ConcurrentHashMap 无序 key 唯一 CAS + 同步锁
CopyOnWriteArrayList 极快 很慢 有序 写时复制数组

25.13 泛型与比较器底层

  • 泛型

    • 编译期语法糖 → 编译后泛型擦除
    • 作用:类型安全、避免强转、统一 API
  • 比较器

    • Comparable:内部比较器,实体类实现,侵入性强。
    • Comparator:外部比较器,无侵入、灵活、工程优先

25.14 企业架构选型黄金准则

  1. 普通业务查询遍历 → ArrayList
  2. 高频首尾操作 → ArrayDeque
  3. 去重场景 → HashSet
  4. 有序去重 → LinkedHashSet
  5. 排序去重 → TreeSet
  6. 普通键值存储 → HashMap
  7. 有序缓存 LRU → LinkedHashMap
  8. 高并发键值存储 → ConcurrentHashMap
  9. 高并发读多写少列表 → CopyOnWriteArrayList
  10. 任务队列、消息队列 → 阻塞队列

25.15 架构师避坑核心要点

  1. ArrayList 尽量指定初始容量,减少频繁扩容
  2. HashMap 预估数据量,减少 rehash 扩容损耗
  3. 大量数据优先用并行流提升遍历效率
  4. 禁止在循环中频繁创建集合对象
  5. 集合用完及时置空,帮助 GC 回收
  6. 自定义对象存入 Set/HashMap 必须重写hashCode+equals
  7. 并发场景坚决不用 HashMap、ArrayList
  8. 避免超大集合一次性加载,分页分片处理防 OOM

25.16 高频面试题

 1. ArrayList 扩容为什么是 1.5 倍?

        平衡内存浪费与扩容频次,1.5 可充分利用之前数组空间。

2. HashMap 数组长度为什么必须是 2 的幂?

        哈希取模可用 hash & (n-1) 替代,更快、分布更均匀、减少冲突

3. JDK1.8 为什么引入红黑树?

        链表过长查询退化为 O (n),红黑树稳定 O(logn),大幅提升冲突严重时性能。

4. HashSet 如何保证去重?

        依赖 HashMap Key 唯一性:hashCode() 定位桶 + equals() 判定相同。

26 Queue、Deque队列深度架构(新增重点)

26.1 Queue队列核心特性

队列:先进先出FIFO,一端入队、一端出队;多用于生产者消费者、消息缓冲、线程池任务调度。

26.2 队列核心方法区别

抛异常:add、remove、element;

不抛异常返回空:offer、poll、peek;生产强制使用offer/poll/peek,避免异常雪崩。

26.3 Deque双端队列

Deque = Double Ended Queue,两端可入队、两端可出队;既可以做队列(FIFO),也可以做栈(LIFO)。官方废弃Stack类,生产全部使用ArrayDeque做栈。

26.4 ArrayDeque VS LinkedList

ArrayDeque:动态数组、无边界、扩容快、性能极高、无锁;

LinkedList:双向链表、频繁节点创建GC、性能差。生产优先选用ArrayDeque。

26.5 常用队列

  1. ArrayDeque:数组双端队列,效率高于 LinkedList
  2. PriorityQueue:优先级队列,自动排序
  3. 并发阻塞队列(JUC)
    • ArrayBlockingQueue 数组阻塞队列
    • LinkedBlockingQueue 链表阻塞队列
    • SynchronousQueue 同步队列
    • DelayQueue 延迟队列

26.6 队列使用场景

  • 消息缓冲、生产者消费者、任务排队、定时延迟任务

27 七大阻塞队列底层原理(面试必背)

27.1 统一底层原理

ReentrantLock + notEmpty、notFull 两个Condition;精准唤醒、线程阻塞、生产消费隔离,无空轮询浪费CPU。

27.2 七大阻塞队列详解

  1. ArrayBlockingQueue:有界数组、一把锁、支持公平锁,适合固定流量控速。

  2. LinkedBlockingQueue:链表结构、默认无界,线程池默认队列,吞吐量高。

  3. SynchronousQueue:无容量、一对一传递,插入必须等待消费,缓存线程池专用。

  4. PriorityBlockingQueue:优先级队列,自动排序,底层最小堆。

  5. DelayQueue:延时队列,任务按时间执行;订单超时、自动关闭、定时任务核心。

  6. LinkedTransferQueue:预占模式,消费优先,高性能中间件使用。

  7. LinkedBlockingDeque:双向阻塞队列,适合工作窃取算法。

28 ConcurrentHashMap 完整版原理(JDK1.7+1.8)

28.1 JDK1.7 分段锁

底层:Segment分段数组 + 内部HashMap;默认16个分段,并发度固定16;

优点:分段加锁、并发高;

缺点:锁粒度粗、内存占用大、扩容复杂。

28.2 JDK1.8 彻底重构(架构重点)

底层结构:数组+链表+红黑树;取消分段锁,采用CAS + synchronized;锁粒度降低到桶头节点,并发能力爆炸提升。

28.3 Put详细流程

  1. 计算hash定位数组下标;

  2. 桶位为空:CAS无锁直接写入;

  3. 桶位不为空:synchronized锁住桶头;

  4. 链表尾插,长度达到8且数组≥64树化;

  5. 扩容支持多线程协助迁移,提升扩容效率。

28.4 核心特性

不允许null键、null值;迭代弱一致性、不抛并发修改异常;负载因子0.75;线程安全、高并发生产唯一选择。

28.5 三者对比总结

HashMap:线程不安全;

Hashtable:全表锁、淘汰;

ConcurrentHashMap:细粒度锁、高并发生产唯一选择。

31 集合高频底层盲区补全(面试必考遗漏点)

31.1 集合通用空值规则(必考区别)

允许null总结:ArrayList、LinkedList、HashSet、HashMap(1个null键);

禁止null总结:ConcurrentHashMap、HashTable、TreeMap、TreeSet。

底层原理:并发集合为了规避并发下空指针歧义、哈希计算异常,强制禁止null键值;普通单线程集合无严格校验,允许存入null。

生产规范:并发业务严禁存null,防止空指针穿透、日志脏数据。

31.2 Fail-Fast快速失败机制底层原理

ArrayList、HashMap等非并发集合迭代器采用快速失败机制

底层维护modCount修改计数器,每次增删改自增;

迭代时expectedModCount记录初始版本,若modCount不一致直接抛出ConcurrentModificationException。

目的:防止并发修改导致集合结构错乱、数据脏读。

规避方案:迭代删除使用迭代器remove()、不使用集合原生remove();并发场景改用并发集合。

31.3 Fail-Safe安全失败机制

ConcurrentHashMap、CopyOnWriteArrayList采用安全失败;底层遍历原数组快照,不修改原数据源,遍历期间增删不影响迭代;

优点:无并发修改异常;

缺点:遍历数据非实时、存在数据弱一致性、占用额外内存。

适用:多读少写、遍历频繁的并发场景。

32 数组扩容、迁移、哈希冲突底层全集

32.1 ArrayList扩容源码细节

空参构造首次添加扩容至10;非空参构造初始容量为传入大小;

扩容公式:原容量 + 原容量右移1位(扩容1.5倍);底层依靠Arrays.copyOf复制数组,原数组废弃进入GC。末尾追加效率极高,中间插入需要数组位移、性能极差;JDK1.7以后延迟初始化,减少空集合内存占用。

32.2 HashMap扩容为什么必须是2的幂?

  1. 优化哈希寻址:hash & (length-1) 替代取模运算,位运算速度远超取模;

  2. 扩容重散列简化:扩容为2倍后,元素哈希高位判定,要么留在原下标、要么迁移原下标+旧容量,无需重新复杂计算;

  3. 减少哈希碰撞:均匀散列、降低链表堆积概率。

手动初始化HashMap容量,建议传入2的幂次数,底层不会自动规整,容易造成空间浪费、哈希偏移。

32.3 哈希冲突四大解决方案

  1. 链地址法(Java采用):冲突元素挂载链表,简单高效、内存占用低;

  2. 开放寻址法:冲突向后空位探测,ThreadLocalMap底层使用;

  3. 再哈希法:多重哈希算法二次计算,规避碰撞;

  4. 建立公共溢出区:冲突元素统一存入溢出缓冲区。

HashMap采用链地址法+扰动函数,减少哈希碰撞。

32.4 HashMap扰动函数原理

hash(key) = key.hashCode() ^ (hashCode >>> 16);高低位异或,将高位哈希特征下沉至低位;优化哈希散列均匀度,减少低位重复导致的哈希碰撞;String、自定义对象大量依靠扰动函数优化哈希分布。

33 红黑树架构原理(TreeMap/HashMap树化)

33.1 红黑树五大约束规则

  1. 节点只有红色、黑色两种颜色;

  2. 根节点必须为黑色;

  3. 叶子节点统一为空黑色节点;

  4. 红色节点子节点必须全部为黑色(不能红红相连);

  5. 任意节点到叶子节点,黑色节点数量一致。

33.2 红黑树设计目的

平衡二叉查找树,规避普通二叉树退化为链表、查询退化为O(n);

通过变色、左旋、右旋动态维持平衡;

查询、增删时间复杂度稳定O(logn);

HashMap树化后依靠红黑树保障高冲突下查询性能。

33.3 树化与退化严格条件

树化:链表长度≥8 并且 数组长度≥64;仅链表过长不树化,优先扩容;

退化:数组扩容迁移后链表长度≤6,自动退化为链表。中间区间7为缓冲阈值,防止频繁树化退化、反复转换消耗性能。

34 并发集合高阶原理(生产并发必备)

34.1 CopyOnWriteArrayList写时复制

底层不可变数组,写操作加锁、复制新数组修改,修改完成替换原数组;读无锁、读写分离;适合多读少写、白名单、配置列表;

缺点:写操作内存开销大、延迟可见、不适合高频写入。

34.2 ConcurrentSkipListMap跳表原理

替代TreeMap并发版本;底层多层有序链表、无红黑树复杂旋转;插入删除简单、并发性能优秀、天然有序;底层CAS无锁修改,适合高并发有序排序业务。

34.3 不可变集合(JDK9+ of())

List.of()、Set.of()、Map.of()生成不可变集合;底层定长数组、禁止增删改、无扩容机制、线程绝对安全;空值禁止存入;适合常量配置、固定字典、枚举映射,节约内存、防止恶意篡改。

35 集合遍历方式大全+生产禁忌

35.1 四大遍历方式优劣对比

  1. 普通for循环:带下标、可增删、适合List、效率高;

  2. 增强for:语法简洁、底层迭代器、遍历时禁止增删;

  3. 迭代器Iterator:安全删除、支持遍历修改、适配所有集合;

  4. Stream流式遍历:链式编程、筛选排序聚合、代码简洁、可读性强。

35.2 生产遍历硬性规范

  • 遍历删除元素:优先迭代器remove(),禁止for循环直接删除;

  • 大批量数据遍历:减少lambda嵌套、避免频繁创建流;

  • 并发遍历:使用并发集合、加锁遍历,禁止非并发集合多线程读写;

  • 超大集合:分批分页遍历,防止一次性加载OOM。

36 集合高频面试坑点+生产避坑总结

36.1 常见面试易错点

  1. HashMap链表为什么尾插?JDK1.7头插、扩容倒置循环链表;JDK1.8改为尾插,保留节点相对顺序,规避死链Bug;

  2. 负载因子为什么固定0.75?平衡空间利用率与哈希冲突概率,0.75为数学最优均衡值;

  3. keySet与entrySet区别?entrySet遍历效率极高,一次获取key+value;keySet二次寻址,大数据量遍历必用entrySet;

  4. LinkedHashMap怎么实现LRU?重写removeEldestEntry方法,判定删除最久未访问节点,实现本地简易缓存。

36.2 生产编码强制规约

  1. 预估集合数据量,初始化指定容量,减少频繁扩容拷贝;HashMap预估容量=原始数据量/0.75+1;

  2. 业务普通查询优先ArrayList、高并发优先CopyOnWriteArrayList;

  3. 键值存储优先ConcurrentHashMap,杜绝Hashtable;

  4. 禁止在循环中创建集合,减少频繁GC;

  5. 集合返回值尽量不可变封装,防止外部篡改;

  6. 大集合使用完毕手动clear(),帮助GC快速回收内存。

29 泛型与比较器底层原理

29.1 泛型底层

编译期语法糖,编译后类型擦除;仅编译期类型校验,运行期无泛型信息;提升代码通用性、避免强制类型转换。

29.2 泛型通配符上下限

? 任意类型;? extends T 上界通配符(只读);? super T 下界通配符(只写);

PECS原则:生产者上界、消费者下界。

29.3 比较器

Comparable:内部比较器,侵入实体类,固定排序规则;

Comparator:外部比较器,无侵入、可多套排序规则,工程优先使用。

30 深浅拷贝底层原理

30.1 浅拷贝

仅拷贝对象本身,引用类型成员共享同一内存地址;修改引用属性原对象同步变更;默认clone()、对象赋值均为浅拷贝。

30.2 深拷贝

完整拷贝所有基础类型与引用对象,内存完全隔离,互不影响;

实现方式:重写clone递归拷贝、序列化反序列化、手动赋值新建对象。

30.3 生产规范

数据传输、缓存对象、业务数据隔离必须用深拷贝,防止引用篡改互相影响。

第五部分 多线程并发 & JMM & 阻塞队列

31 多线程进程架构本质与线程状态

31.1 进程与线程

进程:操作系统资源分配最小单位,独立内存空间、进程间互不干扰;

线程:进程内执行最小单位,共享进程堆与方法区,切换开销小、轻量级。

31.2 线程七大状态

NEW 新建:new 线程对象,未启动。

RUNNABLE 就绪运行:调用 start (),等待 CPU 时间片。

RUNNING 运行:抢到 CPU 时间片,真正执行。

BLOCKED 阻塞:锁阻塞、等待阻塞、计时阻塞。

WAITING 无限等待、

TIMED_WAITING 限时等待、

TERMINATED 终止:线程代码执行完毕,生命周期结束。

32 线程创建方式

32.1 继承 Thread:耦合高、无法再继承其他类,工程不推荐。

最直接的创建线程的方式是通过继承Thread类。这种方式下,你需要创建一个Thread的子类,并重写其run()方法,然后创建该子类的实例并调用其start()方法来启动线程。

例如:

class MyThread extends Thread {
   @Override
   public void run() {
       // 线程执行的代码
   }
}
MyThread t = new MyThread();
t.start();

这种方式简单直观,但由于Java不支持多重继承,如果你的类已经继承了另一个类,就不能再继承Thread类。

32.2 实现 Runnable:无返回值、无异常抛出、解耦复用,企业最常用。

另一种常见的方式是实现Runnable接口。这种方式下,你需要创建一个实现了Runnable接口的类,实现其run()方法,然后将该类的实例作为参数传递给Thread类的构造函数,并通过Thread实例调用start()方法。

例如:

class MyRunnable implements Runnable {
   @Override
   public void run() {
       // 线程执行的代码
   }
}
Thread t = new Thread(new MyRunnable());
t.start();

这种方式比继承Thread类更灵活,因为它允许你的类继承其他类。

32.3 实现 Callable+FutureTask:有返回值、可抛异常、支持异步结果获取。

Callable接口是Runnable的一个升级版,它不仅可以在任务完成后返回值,还可以抛出异常。使用Callable时,通常会配合FutureTask使用,FutureTask可以包装Callable对象,并且实现了Runnable,因此可以提交给Thread对象执行。

例如:

class MyCallable implements Callable<Integer> {
   @Override
   public Integer call() throws Exception {
       // 线程执行的代码,并返回结果
       return 123;
   }
}
FutureTask<Integer> task = new FutureTask<>(new MyCallable());
Thread t = new Thread(task);
t.start();

Callable接口提供了更多的功能,但使用起来也更复杂。

32.4 使用线程池

线程池是一种更高级的线程管理方式,它可以复用一组线程,减少线程创建和销毁的开销。在Java中,可以通过ExecutorService接口和其实现类来使用线程池。

例如:

ExecutorService executor = Executors.newFixedThreadPool(10);
executor.execute(new MyRunnable());
executor.shutdown();

使用线程池可以提高程序的性能,特别是在需要频繁创建和销毁线程的场景中。

32.5 使用匿名类或Lambda表达式

Java 8 引入了Lambda表达式,使得创建线程变得更加简洁。你可以使用匿名类或Lambda表达式来快速定义线程执行的代码。

例如:

new Thread(() -> {
   // 线程执行的代码
}).start();

这种方式使得代码更加简洁,但是它通常只适用于代码逻辑较为简单的情况。

工程推荐接口方式,解耦灵活。

33 线程核心API与守护线程

33.1 sleep与wait核心区别

sleep:Thread静态方法、不释放锁、时间到自动唤醒、任意位置可调用;

wait:Object方法、释放锁、需notify/notifyAll唤醒、只能在同步代码块使用。

33.2 sleep 与 wait 底层核心区别(文档重点必考)

  1. 所属类不同:sleep(Thread) / wait(Object)
  2. 锁机制不同:sleep 不释放锁 / wait 强制释放锁
  3. 唤醒机制:sleep 时间到自动唤醒 / wait 必须 notify/notifyAll 唤醒
  4. 使用位置:sleep 任意位置 / wait 只能在同步代码块 / 同步方法内

33.3 notify /notifyAll 底层语义

  • notify:随机唤醒一个等待线程,可能导致线程饿死
  • notifyAll:唤醒所有等待线程,生产环境推荐使用,避免饥饿问题

33.2 守护线程

依附用户线程,所有用户线程结束,JVM直接退出;

用途:后台定时、心跳检测、日志异步刷盘。

34 线程中断底层原理

34.1 中断机制

Java中断是标记协商机制,非强制杀死线程。

34.2 三大核心方法

interrupt():设置中断标记为true;

isInterrupted():查询标记、不清除;

Thread.interrupted():查询并清除中断标记。

34.3 可中断阻塞

sleep/wait/join/lockInterruptibly 阻塞时检测中断标记,抛出InterruptedException。生产禁止使用stop()暴力终止,通过中断标记优雅退出释放资源。

35 JMM Java内存模型 和 并发三大核心特性

35.1 并发三大核心特性

  • 原子性:操作不可分割,要么全部执行、要么不执行
  • 可见性:一个线程修改共享变量,其他线程立即感知到最新值
  • 有序性:禁止 JVM 和 CPU 指令重排序,保证代码执行顺序符合业务逻辑

35.2 核心作用

屏蔽软硬件内存架构差异,统一多线程共享变量读写规则,解决可见性、原子性、有序性三大问题。

35.3 JMM八大内存操作

lock、unlock、read、load、use、assign、store、write;

规范主内存与线程工作内存变量读写流程。

35.4 内存屏障四大类型

LoadLoad、StoreStore、LoadStore、StoreLoad;

volatile底层依靠内存屏障禁止指令重排、强制刷新主内存,保证可见性与有序性。

35.5 as-if-serial

单线程内指令重排不改变执行结果;多线程重排会破坏业务逻辑,依靠volatile、synchronized、JMM约束。

36 synchronized锁升级 & volatile

36.1 锁升级流程

无锁 → 偏向锁 → 轻量级锁 → 重量级锁,升级不可逆,基于对象头MarkWord实现。

36.2 volatile作用

保证可见性、禁止指令重排;不保证原子性;适合状态标记、开关变量,不适合计数复合操作。

36.3 synchronized 底层原理

  • 底层依赖 对象头 MarkWord 实现
  • 存在锁升级机制偏向锁 → 轻量级锁 → 重量级锁,且锁升级不可逆
  • 同时保障:原子性、可见性、有序性
  • JVM 原生底层实现,自动加锁、自动释放锁

36.4 volatile 底层原理

  • 基于内存屏障实现
  • 作用:禁止指令重排、强制刷新主内存、保证可见性和有序性
  • 不保证原子性,不能替代锁做复合并发操作

37 AQS、Lock

37.1 AQS核心

抽象队列同步器,底层双向阻塞队列、state状态变量;

  1. Lock 是接口,手动锁,替代 synchronized
  2. AQS(AbstractQueuedSynchronizer):队列同步器,Lock 底层核心骨架
  3. 常用实现:ReentrantLockReentrantReadWriteLockCountDownLatchSemaphore 全都基于 AQS

37.2 AQS 核心底层架构

1. AQS 三大核心成员

// 1. 同步状态:核心标记
private volatile int state;
// 2. 同步队列头节点
private transient Node head;
// 3. 同步队列尾节点
private transient Node tail;

字段含义

  • state
    • 独占锁:0 = 无锁,1 = 已加锁,>1 = 重入次数
    • 共享锁:代表剩余许可数
  • CLH 双向阻塞队列:抢锁失败线程进入队列排队休眠

2. AQS 两种锁模式

  1. 独占模式 Exclusive:同一时刻只一个线程持有 → ReentrantLock
  2. 共享模式 Share:多线程同时持有 → CountDownLatch、Semaphore、读写锁

3. 内部 Node 排队节点

  • 存储:等待线程、等待状态、前驱后继节点
  • 抢锁失败 → 封装成 Node 入队
  • 前驱节点唤醒后继节点,有序唤醒

4. AQS 核心模板方法(架构设计)

AQS 只定义流程,把具体实现交给子类重写

  1. tryAcquire() 尝试获取独占锁
  2. tryRelease() 尝试释放独占锁
  3. tryAcquireShared() 获取共享锁
  4. tryReleaseShared() 释放共享锁

设计思想:模板方法模式

37.3 ReentrantLock 可重入锁(最常用 Lock)

1. 与 synchronized 对比

表格

特性 synchronized ReentrantLock
类型 隐式锁,自动加解锁 显式锁,手动 lock ()/unlock ()
重入 支持 支持
中断等待 不支持 支持可中断
公平锁 非公平 可选 公平 / 非公平
超时抢锁 不支持 支持 tryLock (time)
条件队列 1 个 多个 Condition
底层 监视器锁 ObjectMonitor AQS 队列同步器

2. 公平锁 VS 非公平锁(面试必考)

非公平锁(默认)

  1. 线程来了直接插队抢锁
  2. 不排队,上来先 CAS 抢 state
  3. 效率高、吞吐量高
  4. 可能线程饥饿

公平锁

  1. 严格按照请求先后顺序排队
  2. 新来线程先判断队列有无等待线程
  3. 先来先得,无饥饿,吞吐量偏低

3. 可重入原理

  1. 同一线程多次加锁
  2. state 数值累加
  3. 解锁必须次数匹配,逐层递减到 0 才算完全释放
  4. 忘记解锁 → 死锁

4. 标准写法(企业强制)

Lock lock = new ReentrantLock();
try {
    lock.lock();
    // 业务代码
} finally {
    // 必须放 finally 保证一定释放
    lock.unlock();
}

5. 三大高级用法

  1. 可中断锁 lockInterruptibly()等待过程可被中断,放弃抢锁
  2. 超时抢锁 tryLock(时间)抢不到直接放弃,避免死等死锁
  3. 多条件唤醒 Condition
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();
// 等待
notEmpty.await();
// 唤醒
notEmpty.signal();

作用:精准分组唤醒,比 wait/notify 灵活百倍

37.4  AQS 独占锁完整执行流程(架构底层)

  1. 线程执行 lock() → 调用 AQS acquire()
  2. 先调用子类 tryAcquire() 尝试抢锁
  3. 抢锁成功:修改 state=1,直接执行业务
  4. 抢锁失败:
    1. 封装成 Node 节点
    2. 加入 AQS 双向阻塞队列尾部
    3. 线程 park 休眠,让出 CPU
  5. 持有锁线程执行完毕 → unlock()
  6. 调用 tryRelease() 把 state 置 0,释放锁
  7. 唤醒队列中后继第一个等待线程
  8. 被唤醒线程再次尝试抢锁,循环执行

37.5 读写锁 ReentrantReadWriteLock

1. 特点

  • 读读共享:多线程同时读,并发极高
  • 读写互斥、写写互斥
  • 适合读多写少业务:缓存、配置、字典数据

2. AQS state 高低位拆分

  • 高 16 位:读锁数量
  • 低 16 位:写锁重入次数一个 int 存两种锁状态,极致设计

3. 锁降级(面试高频)

流程:获取写锁 → 获取读锁 → 释放写锁

  1. 写完数据先加读锁
  2. 再释放写锁
  3. 保证数据可见性,防止其他线程篡改只支持锁降级,不支持锁升级

37.6 AQS 共享锁常用工具类

37.6.1 CountDownLatch 倒计时计数器
  • 作用:等待多个线程全部完成再执行主线程
  • 底层:共享 AQS,state = 任务总数
  • countDown() 递减,await() 等待归零
37.6.2 Semaphore 信号量
  • 限流、控制并发线程数
  • state = 最大并发许可数
  • acquire() 获取许可,release() 释放
37.6.3 CyclicBarrier 循环屏障
  • 凑齐指定数量线程再一起执行
  • 可循环复用,适合批量分组任务

37.7 synchronized 与 AQS/Lock 底层本质区别

  1. synchronized

    • JVM 原生底层实现
    • 依赖对象头 MarkWord、监视器锁
    • 偏向锁→轻量级锁→重量级锁 自动膨胀
    • 阻塞线程进入 Object 等待池
  2. Lock(AQS)

    • JDK 代码层面纯 Java 实现
    • 手动控制加解锁
    • 阻塞线程进入 AQS 双向同步队列
    • 功能更强、粒度更细、并发可控

37.8 Lock 与 synchronized 架构对比

  • synchronized:JVM 底层、自动锁、阻塞不可中断、非公平为主
  • Lock:基于AQS 队列、手动加锁 / 解锁、可中断、可超时、支持公平 / 非公平锁,灵活度更高

37.9 使用规范

  1. 简单同步优先 synchronized,简洁易用
  2. 高并发、需要公平锁、超时、中断 → 用 ReentrantLock
  3. 读多写少缓存架构 → 读写锁
  4. 线程等待汇总 → CountDownLatch
  5. 接口限流、资源并发控制 → Semaphore
  6. 业务严禁手写死循环自旋抢锁,优先依托 AQS 阻塞休眠

37.10 高频面试题 

1. 简述 AQS 原理

AQS 是 Java 并发包核心同步器,内部维护volatile 修饰的同步状态 stateCLH 双向阻塞队列。提供独占、共享两种抢锁模式,采用模板方法交由子类实现抢锁释放逻辑。抢锁成功修改 state,失败进入队列休眠,锁释放后唤醒队列后继线程,实现高效线程排队同步。

2. ReentrantLock 重入怎么实现

同一线程再次获取锁直接判定当前持有线程一致,同步状态 state 数值累加;解锁逐层递减,直到 state 为 0 完全释放,实现可重入。

3. 公平锁和非公平锁流程差异

非公平锁线程上来直接 CAS 抢占锁,不管队列是否有等待线程,效率高;公平锁先判断同步队列是否存在等待线程,存在则进入队尾排队,严格遵循先来后到。

4. 为什么 Lock 一定要写在 finally 解锁

业务代码出现异常会直接跳出,若不手动解锁会导致锁永远无法释放,其他线程永久阻塞死锁,finally 保证无论正常异常都必然释放锁。

5. Condition 和 wait/notify 区别

  1. wait/notify 依附对象锁,只能一组等待唤醒
  2. Condition 依附 Lock,可创建多个条件队列,实现精准分组唤醒,业务控制力更强

6. AQS 线程排队为什么用双向队列

双向队列方便前驱节点快速唤醒后继、节点取消排队快速遍历查找,提升入队出队与唤醒效率。

7. 读写锁适用场景与锁降级

适用读多写少高并发场景;锁降级为先获取写锁再获取读锁最后释放写锁,保证数据实时可见,Java 不支持锁升级。

8. 死锁产生四大条件

  • 互斥条件
  • 请求并持有
  • 不可剥夺
  • 循环等待:  破坏任意一条即可解除死锁

38 死锁四大条件与排查规避

38.1 死锁定义

多个线程互相持有对方需要的资源,又不肯释放自己资源,互相永久等待,程序卡死无响应。

38.2 四大必要条件(缺一不可,全部满足才会死锁)

  1. 互斥条件资源同一时间只能被一个线程占用,别人拿不到
  2. 请求并持有线程已经持有一把锁,不释放,还去请求另一把锁
  3. 不可剥夺别人不能强行抢走线程已持有的锁,只能自己主动释放
  4. 循环等待线程之间形成环状依赖A 拿锁 1 等锁 2,B 拿锁 2 等锁 1

     核心结论:破坏任意一条,死锁直接消失

38.3 手写死锁标准代码(一眼看懂)

public class DeadLockDemo {
    private static final Object LOCK_A = new Object();
    private static final Object LOCK_B = new Object();

    public static void main(String[] args) {
        // 线程1:先A 后B
        new Thread(() -> {
            synchronized (LOCK_A) {
                System.out.println("线程1 获取锁A");
                try {Thread.sleep(100);} catch (Exception e) {}
                synchronized (LOCK_B) {
                    System.out.println("线程1 获取锁B");
                }
            }
        }).start();

        // 线程2:先B 后A
        new Thread(() -> {
            synchronized (LOCK_B) {
                System.out.println("线程2 获取锁B");
                try {Thread.sleep(100);} catch (Exception e) {}
                synchronized (LOCK_A) {
                    System.out.println("线程2 获取锁A");
                }
            }
        }).start();
    }
}

运行直接卡死,完美满足四大条件。

38.3 线上死锁排查 3 种实战方式

1. jps + jstack 命令排查(最常用)

  1. jps 查出 Java 进程 PID
  2. jstack 进程号 打印线程堆栈
  3. 搜索 Found one Java-level deadlock直接标出:哪两个线程、哪两把锁互相等待

2. IDEA 图形化排查

  • 运行程序 → 打开 Debug → Threads
  • 查看线程状态 BLOCKED
  • 直接看到阻塞持有锁、等待锁

3. 线上监控工具

Arthas、SkyWalking、Prometheus 直接检测死锁线程

38.5 规避方案

1. 破坏「循环等待」—— 企业最常用(首选)

统一锁的获取顺序所有线程拿锁顺序完全一致都先拿 LOCK_A,再拿 LOCK_B,杜绝环路等待

整改后无死锁代码:

// 两个线程全部:先A 后B
new Thread(() -> {
    synchronized (LOCK_A) {
        synchronized (LOCK_B) {}
    }
}).start();

new Thread(() -> {
    synchronized (LOCK_A) {
        synchronized (LOCK_B) {}
    }
}).start();

工程规范:项目内所有多锁场景,强制规定加锁先后顺序。

2. 破坏「请求并持有」

拿到第一把锁后,拿不到第二把就立刻释放已有锁不要抱着锁去等别的锁。

3. 破坏「不可剥夺」

使用 ReentrantLock 超时抢锁 tryLock ()抢锁超时直接放弃,主动释放资源,不死等。

if(lock.tryLock(1, TimeUnit.SECONDS)){
    try{
        // 业务
    }finally{
        lock.unlock();
    }
}else{
    // 超时放弃,避免死锁
}

4. 破坏「互斥条件」

尽量缩小同步范围、拆分锁、降低锁粒度能用共享锁不用独占锁(读写锁)。

38.6 项目开发死锁避坑硬性规范

  1. 禁止嵌套多层 synchronized 锁三层及以上嵌套极易死锁
  2. 业务尽量少用多把锁嵌套
  3. 必须多锁时,全局统一加锁顺序
  4. 高并发多锁场景优先用 ReentrantLock + 超时机制
  5. 锁代码块尽量短小精悍,不要包含远程调用、sleep、IO 阻塞
  6. Spring 事务 + 锁 严禁混用顺序错乱
  7. 分布式场景:分布式锁也要保证加锁顺序一致

38.7 分布式死锁简单说明

分布式死锁同样满足四大条件:

  • 多服务互相持有分布式锁
  • 循环等待对方锁资源解决方案:同样统一分布式锁获取顺序

38.8 高频面试题

 1.  什么是死锁?产生死锁必须满足哪四个条件?

死锁是多个线程互相持有对方所需资源,又不释放自身资源,陷入永久互相等待,程序卡死无法继续执行。

四大必要条件:

  1. 互斥:资源同一时刻仅允许一个线程占用
  2. 请求并持有:线程已持有锁,未释放又去申请其他锁
  3. 不可剥夺:已持有资源不能被其他线程强行抢占,只能主动释放
  4. 循环等待:线程之间形成环形资源依赖链路

2.  如何破坏死锁四大条件,分别对应什么解决方案?

  1. 破坏互斥:尽量降低锁粒度,多用共享锁替代独占锁,减少排他占用
  2. 破坏请求并持有:申请新资源前,先释放已持有全部资源,不抱着锁等锁
  3. 破坏不可剥夺:使用tryLock()超时抢锁,获取失败主动放弃并释放已有资源
  4. 破坏循环等待全局统一加锁顺序,所有线程按固定顺序获取多把锁(项目最常用)

3.  线上项目出现死锁,你如何排查定位?

  1. 使用jps命令获取异常 Java 进程 PID
  2. 执行jstack 进程号打印全量线程堆栈信息
  3. 检索关键字Found one Java-level deadlock,直接定位死锁线程、持有锁、等待锁
  4. 查看线程状态为BLOCKED,对照业务代码梳理加锁逻辑,找到循环等待链路
  5. 结合日志还原并发场景,修复加锁顺序

4.  synchronized 嵌套锁出现死锁,最快解决办法是什么?

最快最稳妥方案:

统一所有线程获取锁的先后顺序,彻底打破循环等待条件,从根源杜绝死锁;

其次精简同步代码块范围,减少锁嵌套层级,不在锁内执行耗时阻塞业务。

5.  ReentrantLock 相比 synchronized,在预防死锁上有什么优势?

  1. 支持超时抢锁tryLock(long time),抢锁失败主动放弃,不会无限死等
  2. 支持线程等待中断,可主动终止阻塞线程
  3. 可灵活控制加锁、解锁时机,代码层面更容易控制资源释放顺序
  4. 可拆分多条件队列,减少无意义长时间持有锁,降低死锁概率

39 ThreadLocal底层与内存泄漏

39.1 核心作用

线程本地变量:数据线程隔离,多线程互不干扰,每个线程独有一份副本。

常用于:用户上下文、数据源切换、事务信息、登录态透传、无锁安全。

39.2 底层结构

每个Thread持有ThreadLocalMap,Key为ThreadLocal弱引用,存储线程私有变量。

(1). 存储结构

  1. 每个 Thread 线程内部持有:
ThreadLocal.ThreadLocalMap threadLocals;
  1. 数据不存 ThreadLocal 对象里,存在当前线程自己的 Map 里
  2. 架构关系:
Thread 线程
 └── ThreadLocalMap
      ├── Entry(key, value)
      └── key = ThreadLocal 弱引用

(2). ThreadLocalMap 底层

  • 底层:自定义哈希表,数组结构
  • 存储节点:Entry 静态内部类
static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k); // key 弱引用
        value = v;
    }
}

关键点

  • key弱引用指向 ThreadLocal
  • value强引用存储业务数据

(3). 存取流程

  1. set()
    • 获取当前线程 → 拿到线程内 ThreadLocalMap
    • 以当前 ThreadLocal 为 key,存入 value
  2. get()
    • 当前线程获取自己的 Map
    • 根据 ThreadLocal 取出独有数据
  3. 不同线程互不读取对方数据,天然线程安全

39.3 弱引用作用

  1. 弱引用特点:GC 来临直接被回收,不受强引用干扰
  2. 设计目的:当外部 ThreadLocal 引用置空,key 自动被 GC 回收,避免线程一直持有无效 key
  3. 只回收 key,value 不会自动回收 → 内存泄漏根源

39.4 ThreadLocal 内存泄漏(重中之重)

Key弱引用但Value强引用,线程池线程复用导致Value常驻内存;使用完必须调用remove(),禁止存放大对象与全局业务对象。

(1). 泄漏原因

  1. ThreadLocal 引用置空 → key 弱引用被 GC 回收
  2. 但是 Entry 里的 value 是强引用
  3. 线程如果是线程池线程(核心线程不销毁)
  4. 线程一直存活 → value 强引用永远无法释放 → 内存泄漏

(2). 泄漏完整链路

  • 业务代码 tl = null 断开外部强引用
  • key 弱引用被 GC 清除
  • key 变成 null
  • 线程长期存活 → null-key 对应的 value 无法释放
  • 堆积越多,内存溢出 OOM

(3). 为什么不用强引用 key

如果 key 是强引用:

就算业务不用 ThreadLocal,线程一直持有强引用,ThreadLocal 对象永远无法 GC,泄漏更严重。

所以 JDK 选择:key 弱引用,牺牲 value 换取对象回收

39.5 JDK 自带清理机制

ThreadLocal 在 get/set/remove 时,会主动扫描清除 key==null 的过期 Entry但有局限:

  1. 长期不调用 get/set,不会自动清理
  2. 线程池常驻线程不退出,过期数据一直堆积

39.6 彻底解决内存泄漏 3 大强制规范

1. 用完必须手动 remove ()(最核心)

try {
    threadLocal.set(user);
    // 业务执行
} finally {
    // 强制清空,断绝泄漏
    threadLocal.remove();
}

2. 禁止使用静态 ThreadLocal 滥用

static ThreadLocal 生命周期和类一致,极易造成大批量泄漏

3. 线程池使用严格注意

  • 线程池复用线程,线程不会销毁
  • 任务执行完毕必须 remove 清空上下文
  • 否则下个任务拿到脏数据 + 内存泄漏

39.7 ThreadLocal 三大使用场景

  1. 登录用户信息全局透传(最常用)
  2. 多数据源动态切换
  3. 事务管理器、连接池连接绑定

39.8 ThreadLocal 优缺点

优点

  1. 完美线程隔离,无锁并发高效
  2. 隐式传参,简化代码
  3. 上下文全局无缝获取

缺点

  1. 使用不当极易内存泄漏
  2. 父子线程默认无法继承数据
  3. 过多使用增加排查难度

39.9 父子线程数据传递

  1. 普通 ThreadLocal:子线程拿不到父线程数据
  2. InheritableThreadLocal:父子线程自动拷贝数据,子线程可获取父线程本地变量
  3. 缺陷:线程池复用场景会出现数据错乱

39.10 高频面试题 

1. ThreadLocal 底层原理

每个 Thread 线程内部维护一个 ThreadLocalMap 哈希表,以 ThreadLocal 对象为 key,业务数据为 value 实现线程私有存储,数据归当前线程独有。key 采用弱引用设计,实现 ThreadLocal 对象自动回收,实现线程间数据隔离,多线程互不干扰。

2. ThreadLocal 为什么会内存泄漏?

ThreadLocalMap 中 Entry 的 key 是弱引用,外部引用消失后 key 可被 GC 回收变为 null,但 value 是强引用无法自动释放;若线程长期存活(线程池核心线程),value 无法被回收,持续占用内存造成泄漏。

3. 如何避免内存泄漏?

  1. 使用完毕在 finally 块手动调用remove()清空数据
  2. 及时断开外部引用,不随意定义 static 全局 ThreadLocal
  3. 线程池任务执行完强制清理本地变量
  4. 业务尽量缩小 ThreadLocal 生命周期

4. key 为什么设计成弱引用?

为了让 ThreadLocal 对象在没有外部强引用时,能被 GC 正常回收;如果用强引用,线程一直持有会导致 ThreadLocal 对象永远无法回收,造成更严重内存泄漏。

5. ThreadLocal 是线程安全的吗?

天然线程安全,数据线程私有无共享,不存在并发竞争,无需加锁。

6. InheritableThreadLocal 作用与坑

作用:可以让子线程继承父线程 ThreadLocal 数据;

缺点:在线程池复用线程时会出现旧数据残留,业务慎用。

7. ThreadLocal 数据存在哪里
不存在 ThreadLocal 对象中,存在当前调用线程内部的 ThreadLocalMap 里。

8. ThreadLocalMap 的 Entry 结构
Entry继承弱引用,key 是弱引用指向 ThreadLocal,value 是强引用存业务值。

9. JDK 自带清理机制
调用get/set/remove时,会主动遍历清理 key 为 null 的过期 Entry;但长期不调用不会自动清理,无法彻底杜绝泄漏。

10. 常用业务场景
登录用户信息上下文透传
多数据源动态切换
事务、数据库连接线程绑定
日志链路 ID 传递

40 多线程进阶高频盲区(面试必问)

40.1 线程虚假唤醒(经典坑点)

定义:线程在没有被notify/notifyAll唤醒的情况下,莫名其妙从WAITING变为RUNNABLE。底层为操作系统内核机制,无法人为避免。

解决方案:wait()必须写在while循环内,被唤醒后二次校验条件,禁止if判断。

标准模板:while(条件不满足){obj.wait();}。生产所有生产者消费者代码必须遵循while循环判定,规避虚假唤醒导致的数据错乱。

40.2 线程优先级与操作系统映射

Java优先级1~10,默认5;仅仅是建议优先级,不强制CPU调度;

Linux系统下不分优先级,时间片轮询;Windows会映射系统优先级。

生产禁止依赖优先级控制业务执行顺序,不可靠、系统无关。

40.3 线程上下文切换底层开销

CPU保存线程栈、程序计数器、寄存器上下文、切换执行线程;频繁切换损耗CPU算力、浪费内核资源。

造成原因:线程过多、锁竞争、IO阻塞、频繁休眠。

优化:控制线程数量、合并短任务、减少锁粒度、使用无锁并发。

40.4 伪共享(False Sharing)CPU缓存行大坑

CPU缓存行默认64字节,多个线程频繁修改同一缓存行内不同变量,引发缓存行频繁失效、缓存一致性风暴、CPU飙升。

解决方案:填充占位字节、缓存行隔离;JDK内置@sun.misc.Contended注解自动填充。

应用场景:高并发计数器、Disruptor、并发框架底层。

41 synchronized 锁升级完整源码级详解

41.1 对象头MarkWord存储结构

64位JVM MarkWord固定64bit:哈希码(25bit)+分代年龄(4bit)+偏向线程ID(54bit)+锁标记位(2bit)+偏向锁标识(1bit)。锁升级全部依靠修改MarkWord二进制位完成,无操作系统重量级调用、开销极低。

41.2 偏向锁原理与撤销

偏向锁:单线程无竞争场景,偏向线程直接绑定对象,无需加锁、无需CAS;JDK15默认关闭偏向锁,初始化延迟4秒开启。

偏向锁撤销条件:出现第二条竞争线程、调用hashCode、批量重偏向、批量撤销。偏向锁撤销需要全局安全点STW,开销较大;高并发竞争场景偏向锁反而拖累性能。

41.3 轻量级锁自旋机制

多条线程交替竞争、无并行争抢;底层CAS自旋乐观锁,不阻塞线程、不进入内核;自旋次数默认10次,JVM自适应调整;自旋失败膨胀为重量级锁。

适用:锁持有时间短、竞争不激烈场景。

41.4 重量级锁底层原理

底层依赖操作系统Mutex互斥量;线程阻塞挂起、进入内核态、加入阻塞队列;CPU让出时间片、资源释放、上下文切换开销极大。

适用:锁持有时间长、大量线程激烈争抢场景。

41.5 锁降级是否存在?

官方定论:JDK无锁降级

锁升级单向不可逆:无锁→偏向→轻量级→重量级。一旦膨胀重量级锁,不会退回轻量级;网上锁降级仅为早期实验版JVM,正式生产JDK全部禁用降级。

42 Lock体系 & AQS 深度源码解析

42.1 AQS三大核心组件

volatile int state:同步状态标记,0无锁、1已加锁、>1重入次数;

双向FIFO阻塞队列:存放争抢失败的阻塞线程;

Condition条件队列:精准唤醒、线程分组等待。

42.2 ReentrantLock可重入原理

同一线程多次加锁,state自增;解锁state递减,归零彻底释放;底层判断当前持有线程是否为当前线程,是则直接放行,无需竞争。synchronized同样支持可重入,原理一致。

42.3 公平锁 & 非公平锁源码差异

非公平锁(默认):加锁不排队、直接CAS抢占,抢占失败入队;吞吐量高、可能线程饥饿。

公平锁:严格判断队列是否存在前驱线程,有则排队;无插队、无饥饿、吞吐量偏低。

生产默认非公平锁,追求高吞吐。

42.4 Condition精准唤醒

一把锁绑定多个Condition,线程分组阻塞;唤醒指定队列线程,无需全局唤醒,减少无效竞争。经典案例:阻塞队列notFull、notEmpty双条件精准唤醒。

43 volatile 生产级重难点补充

43.1 内存屏障四大指令实操

LoadLoad、StoreStore、LoadStore、StoreLoad;

volatile写前后插入StoreLoad屏障,强制刷新主内存;

volatile读插入LoadLoad屏障,禁止重排、强制读取最新主内存数据。

43.2 volatile为什么不保证原子性?

复合操作非原子(i++=读取+修改+写入);volatile仅保证单次读写可见,无法锁住中间计算过程;多线程同时读取旧值、同时覆盖写入,造成数据丢失。

解决方案:Atomic原子类、synchronized、ReentrantLock。

43.3 DCL双重检查锁单例 volatile必要性

禁止指令重排,防止对象半初始化;

若无volatile,编译器可能重排:分配内存→赋值引用→初始化对象,导致其他线程拿到空属性半初始化对象,触发空指针异常。DCL必须加volatile,生产标准写法。

44 原子类 & CAS 无锁并发原理

44.1 CAS底层原理

Compare And Swap 比较并交换;

内存原值、预期值比对,一致则更新、失败重试;

CPU硬件级指令cmpxchg、无锁、无阻塞、并发性能极强。

44.2 CAS三大致命问题

        1.ABA问题:值A→B→A,线程无法感知变动;

                解决方案:版本号戳AtomicStampedReference。

        2.自旋空耗CPU:竞争激烈无限循环、CPU飙升;

                解决方案:自适应自旋、阻塞队列。

        3.只能保证单个变量原子性:无法批量复合操作;

                解决方案:AQS锁、事务。

44.3 常用原子类分类

基本类型:AtomicInteger、AtomicLong;

引用类型:AtomicReference;

数组类型:AtomicIntegerArray;

字段更新器:AtomicFieldUpdater;

累加器:LongAdder(分段求和、超高并发优先)。

44.4 LongAdder优于AtomicLong原理

高并发下分散热点,将单一value拆分多个cell数组;不同线程修改不同cell,减少CAS失败次数;最终汇总求和,吞吐量远超AtomicLong;

缺点:最终求和存在弱一致性,不适合强精准实时计数。

45 七大阻塞队列深挖(生产落地+源码细节)

45.1 阻塞队列

通用架构统一架构:ReentrantLock + 双Condition;put阻塞、take阻塞;线程安全、自动阻塞、自动唤醒;JDK线程池全部依托阻塞队列实现任务缓冲。

45.2 ArrayBlockingQueue

定长数组源码一把全局锁、一个数组、双指针维护头尾;支持公平/非公平锁;容量固定不可扩容;生产流量削峰、限流缓冲首选,内存可控无OOM。

45.3 LinkedBlockingQueue

无界陷阱默认无界、最大容量Integer.MAX_VALUE;

两把分离锁(takeLock、putLock),读写并行、吞吐量极高;

线程池默认队列,业务堆积过大直接OOM,生产必须手动限制容量。

45.4 SynchronousQueue

零容量穿透无数组、无链表、不存储元素;生产者必须等待消费者、一对一交付;底层Transfer栈交换模式;CachedThreadPool专用,适合瞬时爆发高并发任务。

45.5 DelayQueue延时队列底层

底层优先阻塞队列+最小堆;任务自带过期时间,堆顶最先到期;leader线程抢占机制,减少无效轮询、节省CPU;

应用:订单超时、优惠券过期、定时关闭资源、重试机制。

45.6 优先级队列排序规则

PriorityBlockingQueue无界、自然排序、自定义比较器;

底层最小堆二叉树;

插入自动上浮、取出自动下沉;

优先级高任务优先执行,适合任务分级业务。

46 线程池完整源码级详解

46.1 为什么要用线程池

  1. 降低资源消耗:复用线程,避免频繁创建销毁开销
  2. 提高响应速度:任务到达直接执行,无需新建线程
  3. 方便管控:统一管理线程数量、拒绝策略、任务排队
  4. 解耦任务与执行:业务只管提交任务,不关心线程细节

46.2 线程池核心顶层架构

Executor 顶层接口
└── ExecutorService 扩展接口
    └── ThreadPoolExecutor 核心实现类(源码重点)

所有业务线程池,底层全是 ThreadPoolExecutor:

线程基础 → 线程状态 → synchronized 锁升级 → volatile → CAS → AQS 独占 / 共享 → Lock → 死锁 → ThreadLocal → 线程池源码

46.3 线程池运行状态(源码位运算)

// 高三位存状态,低29位存线程数
private final AtomicInteger ctl;

1.RUNNING(正常接收任务):正常接收任务、处理任务。

2.SHUTDOWN(不接收新任务、执行存量):不接收新任务,继续执行队列剩余任务。

3.STOP(中断全部任务):不接新任务、中断正在执行任务、清空队列。

4.TIDYING(整理资源):所有任务结束,线程清零,即将终止。

5.TERMINATED(彻底销毁): 线程池彻底关闭。

采用int高位存储状态、低位存储线程数,一次CAS修改状态+线程数,节省内存、并发安全。

状态流转: 

RUNNING → shutdown () → SHUTDOWN → 队列空 → TIDYING → TERMINATEDRUNNING → shutdownNow () → STOP → 清空任务 → TIDYING → TERMINATED

46.4 线程池七大核心参数

  1. corePoolSize(核心线程数):常驻存活线程,空闲也不会被回收。
  2. maximumPoolSize(最大线程数):线程池能容纳最大总线程数。
  3. keepAliveTime(非核心线程空闲超时时间):非核心线程空闲超过该时间自动销毁。
  4. unit(时间单位):时间单位
  5. workQueue(阻塞任务队列):存放等待执行的 Runnable 任务。
  6. threadFactory(线程工厂):线程工厂,自定义线程命名、优先级、守护线程。
  7. handler(拒绝策略):任务满了无法执行时的兜底策略。

46.5 线程池任务执行完整流程

  1. 提交任务,判断核心线程数是否未满,新建核心线程执行;

  2. 核心线程已满,判断队列是否未满,任务入队缓冲;

  3. 队列已满,判断最大线程数,新建非核心线程;

  4. 达到最大线程数,触发拒绝策略;

  5. 空闲线程超时销毁非核心线程,释放资源。

简洁:

  1. 提交任务 execute()
  2. 当前运行线程数 < 核心线程数→ 新建核心线程执行任务
  3. 核心线程已满→ 任务存入阻塞队列排队
  4. 队列已满→ 新建非核心线程执行任务
  5. 总线程数达到最大线程数→ 触发拒绝策略

流程口诀:核心满进队列,队列满开临时,全员满拒接

46.6 四大拒绝策略生产场景

  1. AbortPolicy(默认):直接抛RejectedExecutionException异常,业务终止,适合强一致性业务;

  2. CallerRunsPolicy:让提交任务主线程自己执行,削峰平缓、不丢数据,生产环境最常用、最稳妥

  3. DiscardPolicy:静默丢弃任务,无日志、无异常,生产禁用;

  4. DiscardOldestPolicy:丢弃队列队头最老任务,执行新任务,适合消息实时性要求高场景。

46.7 四大阻塞队列源码选型

1.ArrayBlockingQueue 有界数组队列

        必须指定容量,公平 / 非公平可选

2.LinkedBlockingQueue 无界 / 有界链表队列

        不指定容量无限存,极易 OOM

3.SynchronousQueue 同步队列

        不存储任务,直接提交线程执行

4.DelayedWorkQueue 延迟队列

        定时任务专用(ScheduledThreadPool)

46.8 核心源码方法深度解析

(1). execute () 提交任务入口源码逻辑

public void execute(Runnable command) {
    // 1. 线程数 < 核心线程数 → 创建核心线程
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 2. 核心满,尝试加入阻塞队列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 二次校验:线程池已关闭则移除任务拒绝
        if (!isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 3. 队列满,创建非核心线程
    else if (!addWorker(command, false))
        // 4. 线程满,执行拒绝策略
        reject(command);
}

(2). addWorker () 创建工作线程核心

  1. 自旋 CAS 增加线程数量
  2. 校验线程池状态、线程总数边界
  3. 创建Worker 工作线程对象
  4. 启动线程执行任务

(3). Worker 内部工作原理(源码核心)

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
  1. Worker 本身是AQS 独占锁作用:标记线程是否正在执行任务,防止中断运行中线程
  2. 线程启动后执行 runWorker()
  3. 循环获取任务执行
    • 优先执行当前绑定任务
    • 再从阻塞队列 getTask() 阻塞拉取任务
  4. getTask() 控制线程回收
    • 超时获取任务 → 超时失败 → 非核心线程销毁

(4). 非核心线程超时回收原理

  1. getTask() 带超时拉取队列任务
  2. 空闲超时没拿到任务返回 null
  3. runWorker 跳出循环,线程结束销毁
  4. 核心线程默认无限阻塞等待,不会回收

46.9 Executors 内置线程池(生产全部禁用)

  1. newFixedThreadPool 固定线程池无界队列,任务堆积 OOM
  2. newSingleThreadExecutor 单线程池无界队列,大量任务内存溢出
  3. newCachedThreadPool 可缓存线程池最大线程无限制,高并发瞬间创建海量线程宕机
  4. newScheduledThreadPool 定时线程池定时任务可用,谨慎设置队列

企业强制规范:禁止使用 Executors 快捷创建,手动 ThreadPoolExecutor 构造

46.10 生产环境线程池最佳实践

1. 核心线程数合理配置

  • CPU 密集型:核心线程 = CPU 核心数 + 1
  • IO 密集型:核心线程 = CPU 核心数 * 2 或 20~50
  • 业务接口阻塞多:适当调大核心线程

2. 强制自定义线程名称

方便线上日志排查线程来源

new ThreadFactoryBuilder().setNameFormat("biz-pool-%d").build()

3. 拒绝策略优先使用 CallerRunsPolicy

不抛异常、不丢数据,压力平缓降级。

4. 业务拆分不同线程池

  • 接口业务池
  • 异步日志池
  • 定时任务池线程池隔离,避免互相阻塞雪崩

5. 线程池监控

监控:活跃线程数、队列积压数、完成任务数、拒绝次数告警:队列堆积过多预警。

46.11 线程池关闭两大方法区别

  1. shutdown()温和关闭:停止接收新任务,执行完队列所有任务再关闭
  2. shutdownNow()暴力关闭:中断运行任务,清空队列,返回未执行任务列表

46.12 生产通用线程池模板(直接项目复用)

public class ThreadPoolConfig {
    // 核心线程
    private static final int CORE_POOL_SIZE = 16;
    // 最大线程
    private static final int MAX_POOL_SIZE = 32;
    // 空闲超时
    private static final long KEEP_ALIVE = 60L;
    // 队列容量
    private static final int QUEUE_CAPACITY = 200;

    public static ThreadPoolExecutor getBizThreadPool(){
        return new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadFactoryBuilder().setNameFormat("biz-task-pool-%d").build(),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }
}

 46.13 自定义线程池拒绝策略

(1) 底层原理

线程池拒绝策略顶层接口:

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

两个入参

  1. Runnable r:被拒绝的任务
  2. ThreadPoolExecutor executor:当前线程池对象

自定义策略只需实现该接口,重写 rejectedExecution 方法,自己定义任务满了之后如何处理。


(2)  自定义拒绝策略三步走

  1. 新建类实现 RejectedExecutionHandler
  2. 重写拒绝执行方法,编写自己业务逻辑
  3. 创建线程池时,把自定义策略传入构造参数

(3)  实战自定义拒绝策略代码

1. 自定义策略类

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 自定义拒绝策略:任务满了存入本地缓存/延时重试
 */
public class CustomRejectHandler implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable task, ThreadPoolExecutor pool) {
        // 1. 打印告警日志
        System.err.println("线程池已满,触发自定义拒绝策略,当前队列数:"
                + pool.getQueue().size()
                + " 活跃线程数:" + pool.getActiveCount());

        // 2. 自定义业务处理(任选一种)
        // 方案1:存入阻塞队列本地缓存,后续异步重试
        // 方案2:存入MQ消息队列削峰
        // 方案3:休眠一段时间后重新提交任务
        // 方案4:降级返回默认结果
        // 方案5:打印日志+丢弃并记录异常

        // 示例:简单休眠后重新尝试提交
        try {
            Thread.sleep(500);
            pool.execute(task);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("任务重试提交失败", e);
        }
    }
}

2. 创建线程池使用自定义策略

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolTest {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,
                4,
                30,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                new ThreadFactoryBuilder().setNameFormat("custom-pool-%d").build(),
                // 传入自定义拒绝策略
                new CustomRejectHandler()
        );

        // 批量提交任务测试触发拒绝
        for (int i = 1; i <= 20; i++) {
            int num = i;
            pool.execute(() -> {
                System.out.println("执行任务:" + num + " 线程:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

(4)  常用自定义业务策略方案

方案 1:重试提交(适合允许短暂延迟)

休眠片刻后再次把任务丢入线程池执行

方案 2:任务存入 MQ/Redis 延时队列(生产最常用)

拒绝后不丢弃、不报错,投递到消息队列,削峰异步消费

方案 3:服务降级策略

直接执行降级逻辑,返回默认数据,放弃当前异步任务

方案 4:打印日志 + 持久化落地

记录失败任务参数,落地文件 / 数据库,方便事后补偿补发

方案 5:动态扩容线程池(谨慎使用)

拒绝时临时修改最大线程数,瞬时扩容应对峰值


(5)  面试标准答案

问题:如何自定义线程池拒绝策略?

  1. 实现 RejectedExecutionHandler 接口,重写 rejectedExecution 方法;
  2. 在方法内拿到被拒绝任务与线程池对象,自定义降级、重试、存入消息队列、日志记录等逻辑;
  3. 构建 ThreadPoolExecutor 时,将自定义拒绝策略作为参数传入即可生效。

问题:生产中你自定义过什么拒绝策略?

业务高峰期不用默认抛异常策略,自定义策略将满溢任务存入 Redis 延时队列或 MQ,实现流量削峰,既不丢失业务数据,又不会压垮服务,空闲时再逐步消费执行。


(6) 注意事项

  1. 自定义策略内部禁止死循环无限制重试,防止阻塞主线程
  2. 重试一定要加休眠间隔,避免瞬间打爆线程池
  3. 涉及线程操作记得恢复中断标识 Thread.currentThread().interrupt()
  4. 高并发优先用消息队列削峰,比本地重试更稳定

46.14 线程池坑点与优化

禁止Executors快捷创建:FixedThreadPool无界队列、CachedThreadPool无限线程;

生产必须手动ThreadPoolExecutor;

自定义线程工厂、自定义线程名、方便线上堆栈排查;线程池必须手动销毁,防止服务关闭残留线程阻塞退出。

46.15 高频源码面试题

1. 线程池七大参数说一遍

核心线程数、最大线程数、空闲超时时间、时间单位、阻塞队列、线程工厂、拒绝策略。

2. 线程池执行流程

任务提交,先开核心线程,核心满进队列,队列满开临时线程,总线程打满走拒绝策略。

3. Worker 为什么继承 AQS

Worker 实现独占锁,用来标记线程工作状态,线程执行任务时加锁,避免运行中被随意中断,空闲线程才可响应中断销毁。

4. 核心线程会被回收吗

默认不会;开启allowCoreThreadTimeOut后,核心线程空闲超时也会被回收。

5. 为什么禁止用 Executors 创建线程池

内置线程池大多使用无界队列 / 无限最大线程,高并发下极易任务堆积、线程暴涨引发 OOM 服务宕机,生产必须手动指定参数创建。

6. 线程池状态有几种,流转关系

RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED;shutdown 走向温和关闭,shutdownNow 走向强制中断关闭。

7. getTask () 作用

从阻塞队列获取任务,同时实现非核心线程超时回收逻辑,是线程复用与销毁的核心方法。

8. 如何合理设置线程池参数

根据业务是 CPU 密集还是 IO 密集设定核心线程,预估峰值设置最大线程,设置有界队列防止无限积压,拒绝策略优先主线程执行降级。

9. 线程池如何实现线程复用

工作线程启动后死循环不断从阻塞队列拉取任务执行,线程本身不销毁,循环复用处理多个任务,实现线程复用。

10. 大批量任务积压怎么处理

  1. 调高核心线程数
  2. 扩容有界队列
  3. 拆分业务异步化
  4. 限流削峰
  5. 服务降级配合拒绝策略

47 CompletableFuture 异步编程高阶(JDK8+)

47.1 设计初衷

弥补Future缺点:阻塞get()、无法回调、链式困难;CompletableFuture实现Future+CompletionStage,异步回调、链式编排、异常自动捕获、多任务组合。

47.2 四大核心回调

thenRun无入参无返回、thenAccept有入参无返回、thenApply有入参有返回、handle异常兜底;主线程不阻塞,异步完成自动回调。

47.3 多任务组合业务实操

allOf全部完成、anyOf任意一个完成;适合多接口并行查询、批量异步处理、复杂链路拆分,大幅度缩短接口响应时间。

47.4 生产强制规范

自定义线程池执行异步任务,禁止共用ForkJoinPool公共池;

公共池线程被占用会导致全局异步任务卡顿;

异步代码必须异常捕获,防止异常吞掉无日志。

48 死锁、活锁、饥饿 三者区别

48.1 死锁DeadLock

互相持有对方锁、循环等待、永久阻塞;CPU无消耗、线程卡死;必须重启服务。

48.2 活锁LiveLock

线程不断退让、不断重试、无法执行完成;线程不阻塞、CPU占用高;代码逻辑冲突导致。

48.3 线程饥饿Starvation

低优先级线程长期抢不到锁、永久得不到执行;非公平锁极端场景容易出现;

解决方案:公平锁、定时超时锁。

49 并发工具类生产大全

49.1 Java 并发工具类全集

 1. CountDownLatch减法计数器

一次性使用、不可重置;主线程等待子线程全部完成;适合批量初始化、多任务汇总、并行加载。

2 CyclicBarrier循环栅栏

可循环复用、集齐指定数量线程统一放行;适合分阶段批量任务、分段并行计算。

3 Semaphore信号量

控制最大并发量、限流熔断;

底层许可令牌,获取放行、释放归还;

秒杀、接口限流、连接池控并发首选。

4 Exchanger线程交换器

两两线程数据交换、成对阻塞;极少使用,多用于数据校对、成对传输。

49.2 AQS 家族(最核心、最常用)

49.2.1 CountDownLatch —— 等待多任务完成

场景:主线程等待 N 个异步任务全部执行完,再继续。

用法:

CountDownLatch latch = new CountDownLatch(3);
// 子线程执行完 countDown()
latch.countDown();
// 主线程阻塞等待
latch.await();

生产场景:

  • 接口并行调用多个第三方服务,汇总结果
  • 启动时初始化多个组件
  • 多线程数据导入汇总

坑:

  • 只能用一次,不能重置
  • countDown () 必须放 finally

49.2.2 CyclicBarrier —— 线程凑齐一起执行

场景:一组线程互相等待,全部到齐再同时开始。

用法

CyclicBarrier barrier = new CyclicBarrier(5);
// 线程内等待
barrier.await();

生产场景

  • 并发压测
  • 批量任务同时开始执行
  • 游戏多玩家同步

特点

  • 可循环使用
  • 支持回调函数

49.2.3 Semaphore —— 信号量 / 限流

场景:控制同时执行的线程数量(限流神器)。

用法

Semaphore semaphore = new Semaphore(10);
semaphore.acquire(); // 获取许可
semaphore.release(); // 释放许可

生产场景

  • 接口限流
  • 连接池限流
  • 并发控制(如同时只允许 20 个请求访问第三方)

  • release 必须放 finally
  • 许可数不能动态修改

49.2.4 Phaser —— 强大的分段栅栏

场景:比 CountDownLatch + CyclicBarrier 更强,支持多阶段、动态增减线程。

企业场景

  • 分阶段任务
  • 动态任务数
  • 复杂并发流程控制

49.3  显式锁工具类(Lock 体系)

49.3.1 ReentrantLock —— 可重入锁

场景:需要可中断、可超时、多条件、公平锁时使用。

标准写法

lock.lock();
try {
    // 业务
} finally {
    lock.unlock();
}

高级功能

  • tryLock (超时)
  • lockInterruptibly () 可中断
  • newCondition () 多条件唤醒

49.3.2 ReentrantReadWriteLock —— 读写锁

场景:读多写少,缓存、配置、静态数据。

规则

  • 读读共享
  • 读写互斥
  • 写写互斥

生产场景

  • 本地缓存
  • 配置中心
  • 热点数据查询

49.3.3 StampedLock —— 读写锁升级版(JDK8)

比 ReadWriteLock 更快,无饥饿、高性能

适合:超高并发读场景。


49.4 线程同步与等待唤醒

49.4.1 Condition —— 精准分组唤醒

替代 Object wait/notify

Condition notEmpty = lock.newCondition();
notEmpty.await();
notEmpty.signal();

优势

  • 一个锁多个等待队列
  • 精准唤醒,不浪费

49.5 线程池工具类(生产必用)

49.5.1 ThreadPoolExecutor —— 手动线程池(企业标准)

禁止使用 Executors!

标准模板

new ThreadPoolExecutor(
    coreSize, maxSize,
    keepAlive, unit,
    有界队列,
    自定义线程工厂,
    拒绝策略
);
49.5.2 ScheduledThreadPoolExecutor —— 定时任务

场景:延时执行、周期执行。


49.6 线程控制工具类

49.6.1 ThreadFactory —— 自定义线程名

必须用,否则线上无法排查线程来源。

new ThreadFactoryBuilder().setNameFormat("order-pool-%d").build()
49.6.2 ThreadPoolExecutor 拒绝策略

企业常用

  • CallerRunsPolicy(主线程执行,最稳)
  • 自定义拒绝策略(存入 MQ/Redis 削峰)

49.7 原子类(无锁并发)

1. AtomicInteger / Long / Boolean …

无锁、CAS、高性能

适合:计数、ID 生成、状态标记

2. AtomicReference —— 原子引用

3. AtomicStampedReference —— 解决 ABA 问题

4. LongAdder —— 高并发计数(比 AtomicLong 快 10 倍)

高并发必用!


49.8 ThreadLocal 系列(线程上下文)

1. ThreadLocal —— 线程本地变量

场景

  • 用户上下文
  • 多数据源切换
  • 事务、日志链路
  • 数据库连接绑定

规范

  • 必须 finally remove ()
  • 线程池必须清空

2. InheritableThreadLocal —— 父子线程传值

:线程池复用会数据错乱


49.9 并发容器(生产高频)

1 ConcurrentHashMap —— 高并发 KV 容器

企业缓存首选

2 CopyOnWriteArrayList —— 读多写少列表

场景:白名单、配置、黑名单

3 BlockingQueue —— 阻塞队列(线程池、MQ 底层)

常用

  • ArrayBlockingQueue(有界,生产推荐)
  • LinkedBlockingQueue
  • SynchronousQueue
  • DelayQueue(延时队列)

49.10 同步工具类(锁与同步器)

1. LockSupport —— 线程阻塞唤醒(AQS 底层)

park() / unpark()

2. CountDownLatch

3. CyclicBarrier

4. Semaphore


49.11 ForkJoin 并行计算

1. ForkJoinPool —— 大任务拆分并行

场景

  • 大数据量计算
  • 流式并行处理

49.12 CompletableFuture —— 异步编排(企业王者)

1. CompletableFuture

JDK8+ 最强异步工具,没有之一

支持

  • 异步执行
  • 多任务组合
  • 超时控制
  • 异常处理
  • 回调函数

生产场景

  • 接口并行调用
  • 异步任务编排
  • 微服务聚合

示例

CompletableFuture.allOf(f1, f2, f3).join();

49.13 最终总结:企业真正高频使用的 20 个

  1. CountDownLatch
  2. CyclicBarrier
  3. Semaphore
  4. ReentrantLock
  5. ReentrantReadWriteLock
  6. Condition
  7. ThreadPoolExecutor
  8. ScheduledExecutor
  9. ThreadFactory
  10. AtomicInteger / LongAdder
  11. ThreadLocal
  12. ConcurrentHashMap
  13. CopyOnWriteArrayList
  14. BlockingQueue
  15. LockSupport
  16. CompletableFuture
  17. ForkJoinPool
  18. Phaser
  19. StampedLock
  20. 自定义拒绝策略

50 并发编程生产编码黄金规约

  1. 尽量缩小锁范围,锁代码块优于锁方法,降低锁竞争粒度;

  2. 禁止锁String常量、锁包装类,避免全局共用锁引发诡异阻塞;

  3. 并发修改集合一律使用并发包,禁止手动加锁普通集合;

  4. 循环内不创建线程、不创建锁对象,减少频繁创建销毁开销;

  5. 异步任务必须指定线程池、指定线程名、异常兜底;

  6. 高并发计数优先LongAdder、精准计数使用AtomicLong;

  7. ThreadLocal必须手动remove,严禁线程池复用导致脏数据;

  8. 生产禁止使用stop、suspend、resume废弃API,线程中断优雅退出。

第六部分 JVM 架构师底层原理(完整版补全·生产调优+面试必背)

40 JVM整体架构与四大核心模块

40.1 JVM整体架构分层

JVM是跨平台字节码执行虚拟机,

四大核心模块协同工作:

1.类加载子系统:负责加载外部 .class 字节码到内存。

2.运行时数据区:JVM 内存五大区域,所有程序运行内存载体。

3.执行引擎:解释器 + JIT 即时编译器,执行字节码指令。

4.本地方法接口:对接操作系统 Native 底层方法,屏蔽系统差异。

全部基于栈架构设计,区别于CPU寄存器架构,降低硬件适配成本。

核心执行流程:源码→javac编译class字节码→类加载加载元数据→运行时数据区分配内存→执行引擎解析执行→GC回收无用内存。

核心设计思想:一次编译、到处运行,通过 JVM 屏蔽硬件与操作系统差异。

40.2 跨平台与字节码设计思想

字节码是JVM通用中间语言,不绑定任何操作系统;采用操作数栈存储运算数据,指令精简、通用性强;JVM通过读取字节码指令,屏蔽Windows、Linux、Mac系统硬件差异,实现一次编译、随处运行。

40.3 虚拟机架构分类

堆栈式虚拟机(HotSpot):指令简单、开发适配成本低、内存开销偏高;

寄存器式虚拟机:指令执行更快、硬件耦合度高;Java默认采用堆栈式HotSpot虚拟机,平衡兼容性与性能。

41 运行时数据区(内存模型·OOM根源详解)

41.1 五大内存区域权限与特性汇总

线程私有区(线程销毁自动回收):程序计数器、虚拟机栈、本地方法栈;

线程共享区(需GC手动回收):堆、元空间。私有区无GC、内存开销低;共享区存储对象与元数据,是GC、OOM高频区域。

41.2 程序计数器(唯一无OOM区域)

含有:记录当前线程执行字节码行号,唯一不会 OOM的区域。

作用:

记录当前线程字节码执行行号,线程切换后恢复执行位置;

本地方法执行时计数器值为空;

内存极小、固定占用,JVM规范明确该区域不会抛出OOM异常。

41.3 虚拟机栈(栈溢出核心)

作用:存放栈帧、局部变量、方法调用链路;方法执行就入栈,结束立即出栈销毁。

栈帧为最小存储单元,方法调用即入栈、方法结束即出栈;

栈帧包含:局部变量表、操作数栈、动态链接、方法返回地址、附加信息。

局部变量表存放基本类型、对象引用地址;默认栈空间1M,递归过深、方法嵌套过多触发StackOverflowError

41.4 本地方法栈

专门服务Native本地方法,底层调用C/C++编写的操作系统方法;

用途:线程启动、内存操作、硬件调用、CAS底层指令;同样存在栈溢出异常,参数配置同虚拟机栈。

41.5 堆内存(OOM重灾区)

唯一存放Java对象实例、数组的共享内存;GC 垃圾回收主战场

逻辑划分为:新生代、老年代;JDK8彻底废除永久代,元空间移至堆外直接内存。

默认物理内存占比:新生代1/3、老年代2/3;新生代细分Eden区(8/10)、Survivor0(1/10)、Survivor1(1/10)。

41.6 元空间(Metaspace)

存储类元数据、方法字节码、类结构、常量、静态变量、运行时常量池、注解信息;

使用操作系统堆外直接内存,不受JVM堆内存限制;

默认无固定上限,可通过-XX:MaxMetaspaceSize限制大小;

动态生成类过多(动态代理、CGLIB)触发元空间OOM。

41.7 堆内存结构划分

文档标准表述:

1. 新生代(Eden + Survivor0 + Survivor1)

2.老年代默认比例:Eden:S0:S1 = 8:1:1

3.新生代:老年代 = 1:2

42 Java对象内存布局与对象创建流程

42.1 对象创建完整源码流程

  1. 类加载校验:检测类是否已加载、初始化;

  2. 内存分配:优先Eden区,指针碰撞分配空闲内存;

  3. 对象头初始化:MarkWord、类型指针赋值;

  4. 实例数据初始化:成员变量默认赋值;

  5. 执行构造方法:代码自定义赋值;

  6. 返回对象内存地址,栈中引用指向堆对象。

42.2 对象内存布局(64位JVM)

对象头(MarkWord + KlassPointer) + 实例数据 + 对齐填充。

MarkWord:哈希码、GC分代年龄、锁标识、偏向线程ID;

KlassPointer:指向方法区类元数据指针;

对齐填充:满足8字节内存对齐规则,提升CPU读取效率;

-XX:+UseCompressedOops 开启指针压缩,将64位指针压缩为32位,节省堆内存。

42.3 对象访问定位两种方式

直接指针访问:引用直接指向堆对象,访问速度快,HotSpot默认采用;

句柄池访问:引用指向句柄、句柄映射对象地址,内存扩容无需修改引用,稳定性高、开销大。

42.4 对象存活判定算法

    1.引用计数法:对象被引用计数+1,归零判定死亡;

           缺点:无法解决循环引用,JVM废弃;

    2.可达性分析法:以GC Roots为起始链,无引用链连通判定死亡;

    3.GC Roots包含:栈引用对象、静态变量、常量、本地方法栈Native对象。

43 类加载机制与双亲委派模型

43.1 类加载五大阶段

  1. 加载:读取class字节码,生成内存Class对象;

  2. 验证:校验字节码合法性、安全合规,防止恶意代码;

  3. 准备:静态变量分配内存、赋默认值;

  4. 解析:符号引用转为直接内存引用;

  5. 初始化:静态代码块、静态变量主动赋值,执行类构造器。

43.2 三类加载器层级关系

        启动类加载器(Bootstrap):C++编写,加载JDK核心底层类(java.lang.*);

        扩展类加载器(Extension):加载jre/lib/ext扩展包;

        应用程序类加载器(App):加载项目自定义业务类、第三方依赖包。

43.3 双亲委派完整执行流程

  1. 自定义加载器接收类加载请求;

  2. 向上委托父加载器处理,逐层向上;

  3. 顶层启动类加载器判定是否可加载;

  4. 无法加载则向下回传,由子类加载器加载;

  5. 最终完成类加载,全局仅加载一次。

43.4 双亲委派三大核心好处

  • 安全:防止恶意篡改 Java 核心基础类。
  • 复用:核心类只加载一次,节省资源。
  • 隔离:不同加载器实现类版本隔离,避免重复加载,保障java.lang.String等核心类全局唯一,杜绝恶意自定义同名核心类破坏JVM安全。

43.5 打破双亲委派四大场景

SPI服务加载、Tomcat自定义类加载器、热部署、动态代理;

Tomcat自定义类加载器打破委派,实现不同应用包类隔离,避免依赖版本冲突。

44 SPI服务扩展机制(中间件通用底层)

接口定义、实现分离、配置注册、ServiceLoader自动加载;

配置路径:META-INF/services/接口全类名;

应用:JDBC、Spring扩展、Dubbo插件、中间件自定义扩展;优点解耦可扩展,缺点遍历所有实现、不支持按需加载。

生产优化:自定义SPI,实现懒加载、别名匹配、优先级排序。

45 GC垃圾回收机制(架构师调优核心)

45.1 GC核心设计目标 和 垃圾判定算法

    1.  设计目标:

        自动识别死亡对象、回收空闲内存、压缩内存碎片、降低人工内存管控成本;

        生产核心指标:吞吐量、停顿时间、内存占用,三高权衡无最优解,业务按需取舍。

    2. 垃圾判定算法:

  • 废弃:引用计数法,无法解决循环引用
  • 主流:可达性分析算法,从 GC Roots 向下扫描,不可达对象判定为垃圾

45.2 三大基础GC算法原理

标记清除:标记死亡对象、统一回收;实现简单、产生内存碎片;

复制算法:划分对等内存,存活对象迁移、清空原区域;无碎片、内存利用率低;

标记整理:标记存活对象、向一端压缩整理;无碎片、整理耗时高。

新生代用复制算法、老年代用标记清除/标记整理。

45.3 四大引用体系(内存管控关键)

强引用:默认引用,只要存在, GC永不回收,直至程序结束;

软引用:内存不足触发GC回收,适合缓存、图片资源;

弱引用:下次 GC 一定会被回收,ThreadLocalMap底层使用;

虚引用:无引用效果,仅做回收跟踪、监控对象销毁。

45.4 分代回收思想

依据对象存活周期分层回收

        新生代对象存活时间短、死亡率高,采用低成本复制算法;

        老年代对象存活久、存活率高,采用低频率标记整理;

对象晋升规则:新生代存活15次GC晋升老年代,阈值可JVM参数调整。

45.5 GC分类与触发条件

Minor GC:新生代垃圾回收,频率高、速度快;

Major GC:老年代回收,触发伴随一次Minor GC,耗时久;

Full GC:整堆回收(新生代+老年代+元空间),Stop-The-World,生产严禁频繁触发。

触发条件:内存不足、手动调用System.gc、元空间溢出、并发GC失败。

46 主流垃圾收集器(生产选型+参数调优)

46.1 收集器分类与适配场景

Serial:单线程串行回收、停顿时间长,适配客户端、低配置机器;

Parallel:多线程并行回收、高吞吐量,后台计算型业务;

CMS:并发低延迟、有浮动垃圾、内存碎片、适合互联网响应式业务;

G1:分区回收、可预测停顿、兼顾吞吐与延迟,生产主流通用收集器;

ZGC:超低延迟、TB级大堆,JDK17+高端生产环境。

46.2 CMS并发垃圾收集器原理

执行流程:初始标记→并发标记→重新标记→并发清除;

优缺点:并发回收、用户线程不阻塞、响应快;内存碎片多、CPU占用高、无法处理浮动垃圾。

生产淘汰原因:碎片化严重、大堆内存卡顿、JDK14彻底移除。

46.3 G1收集器核心特性

将堆内存划分为多个等大Region分区,单独回收空闲分区;

可设置最大停顿时间-XX:MaxGCPauseMillis;

混合回收新生代+老年代,自动分区优先级;

适配8G以上大堆内存,互联网企业通用默认收集器。

46.4 ZGC超低延迟收集器

染色指针标记对象、读屏障保障并发安全;

停顿时间固定不超过10ms,不受堆内存大小影响;

支持TB级堆内存、动态内存扩容;

适合金融、支付、高实时性业务,JDK17长期支持版本首选。

46.5 收集器生产选型黄金规则

  1. 小堆、单机低配:Serial收集器;

  2. 批量计算、无高响应要求:Parallel;

  3. 中等堆、通用业务:G1(企业主流);

  4. 超大堆、金融高实时:ZGC;

  5. 生产禁止使用废弃收集器CMS。

47 JVM调优、OOM全场景与线上故障排查

47.1 JVM 调优核心三大指标:

  • 吞吐量:用户业务代码执行时间占比
  • 停顿时间:GC 造成业务暂停的时间长短
  • 内存占用:堆大小、元空间、虚拟机栈内存控制

47.2 常用JVM核心调优参数

  1. -Xms:初始堆内存,生产建议与-Xmx相等,避免扩容开销;

  2. -Xmx:最大堆内存,服务器物理内存1/4;

  3. -XX:NewRatio:新生代老年代比例,默认1:2;

  4. -XX:SurvivorRatio:伊甸区与幸存区比例,默认8:1:1;

  5. -XX:+PrintGCDetails:打印详细GC日志,线上排查必备;

  6. -XX:MaxMetaspaceSize:限制元空间上限,防止无限膨胀。

47.3 OOM六大常见类型+触发原因

堆内存溢出:大对象、集合无限累加、内存泄漏;

栈溢出:递归无出口、方法嵌套过深;

元空间溢出:动态生成类、代理类过多;

直接内存溢出:Netty堆外内存未释放;

线程溢出:无限创建线程、无线程池管控;

GC频繁溢出:GC回收效率极低、内存持续占用。

47.4 生产高频内存泄漏源头

ThreadLocal未手动remove、静态集合常驻大对象、长生命周期持有短生命周期对象、

数据库/IO连接池不关闭、Netty直接内存泄漏、匿名内部类持有外部引用。

47.5 线上标准排查流程(架构师通用模板)

  1. 监控查看CPU、内存、GC趋势,定位异常时间段;

  2. jps定位Java进程PID,查看运行状态;

  3. jstack查线程死锁、线程堆积、阻塞链路;

  4. jmap dump堆快照,保留故障现场;

  5. MAT分析大对象、泄漏对象、支配树定位泄漏源;

  6. 优化修复:限制集合容量、手动释放资源、调整JVM参数。

48 JVM编译机制与逃逸分析(性能优化底层)

48.1 解释器与JIT即时编译器

        前端编译器javac:编译源码为字节码;

        运行期解释器:逐行解释字节码,适配冷门代码;

        C1/C2 JIT编译器:高频代码编译为本地机器码,执行效率极高;

        分层编译:代码热度分级,冷门解释、热门编译,兼顾启动速度与运行性能。

48.2 逃逸分析底层优化

JDK6+默认开启,分析对象作用域:未逃逸对象仅在方法内使用;

优化手段:栈上分配、标量替换、同步消除;将堆对象分配至虚拟机栈,方法结束自动回收,减轻GC压力。

48.3 四大经典编译优化手段

        循环展开:减少循环判断次数、提升执行效率;

        指令重排:调整指令顺序、规避CPU空闲;

        常量折叠:编译期计算常量结果、无需运行期运算;

        死代码消除:剔除无效代码、冗余判断,精简字节码。

49 常量池体系(面试高频盲区)

49.1 三类常量池层级划分

        Class常量池:编译期生成,存储字面量、符号引用;

        运行时常量池:类加载后转换,存储直接内存引用;

        字符串常量池:全局共享,缓存字符串对象,减少重复创建。

49.2 字符串常量池底层规则

双引号常量优先入池,new String强制创建堆对象;

JDK8字符串池存放堆内存,复用对象减少内存占用;intern()方法:

常量不存在则入池,存在直接返回引用。

50 JVM生产编码黄金规约

  1. 避免创建超大对象、无限循环集合,防止堆内存溢出;

  2. 递归代码必须设置出口,自定义栈深度防止栈溢出;

  3. 高频小对象优先栈分配,利用逃逸分析优化性能;

  4. 禁止手动调用System.gc(),触发不必要Full GC;

  5. 动态代理、CGLIB生成类需管控数量,防止元空间溢出;

  6. 线上必须开启GC日志,故障可追溯、便于排查;

  7. 堆内存初始化与最大值保持一致,减少内存扩容损耗;

  8. 大内存业务优先使用G1/ZGC,规避CMS内存碎片。

51 SPI服务扩展机制

接口定义、实现分离、配置注册、ServiceLoader自动加载;

配置路径:META-INF/services/接口全类名;

应用:JDBC、Spring扩展、Dubbo插件、中间件自定义扩展;优点解耦可扩展,缺点遍历所有实现、不支持按需加载。

52 OOM全场景 & 线上排查

52.1 OOM常见类型

堆内存溢出、栈溢出、元空间溢出、直接内存溢出、线程数过多溢出、静态集合无限累加溢出。

52.2 内存泄漏常见源头

ThreadLocal未清理、静态集合常驻、长生命周期持有短生命周期对象、连接池不关闭、Netty直接内存泄漏。

52.3 线上标准排查流程

  1. 监控查看CPU、内存、GC趋势;

  2. jps定位进程、jstack查线程死锁与堆积;

  3. jmap dump堆快照;

  4. MAT分析大对象、泄漏对象、支配树;

  5. 定位代码漏洞、修复资源释放、增加定时清理、限制集合容量。

第七部分 IO & NIO & 网络全集(新增URL全过程+三次握手四次挥手极致细节)

48 传统IO

字节流、字符流,采用装饰器模式;

BIO同步阻塞,一连接一线程,高并发下线程爆炸、资源开销大。

49 NIO三大核心

Channel通道、Buffer缓冲区、Selector多路复用器;

同步非阻塞,单线程管理多通道,高并发低资源消耗。

50 零拷贝 &amp; mmap

零拷贝:减少用户态与内核态内存拷贝、减少上下文切换;适合大文件传输、RPC、消息队列。mmap内存映射:文件直接映射到用户内存,省去数据拷贝,读写效率更高。

51 堆内存与直接内存

堆内存:JVM管理、GC回收;

直接内存:操作系统堆外内存、不受JVM堆限制、读写更快、需手动释放;Netty、NIO大量使用,需限制大小防溢出。

52 网络IO模型

BIO同步阻塞、NIO同步非阻塞、AIO异步非阻塞、Reactor反应器、Proactor异步反应器;

Netty主从Reactor多线程模型为工业级标准。

53 粘包半包成因与解决方案

TCP是流式协议无数据边界,内核缓冲区缓存、分片合并导致粘包半包;

解决方案:固定长度、特殊分隔符、长度+报文头、Protobuf序列化;

Netty内置解码器:FixedLength、DelimiterBased、LengthFieldBased 开箱即用。

54 Netty核心架构

核心组件:Bootstrap、EventLoop、Channel、Pipeline、ChannelHandler、ByteBuf;

优势:封装NIO底层、解决空轮询CPU 100%、内置编解码、心跳检测、断线重连、优雅关闭。

55 网络超重点:输入URL到页面展示完整全过程(面试必考)

  1. DNS域名解析:浏览器缓存 → 系统缓存 → hosts文件 → 本地DNS → 根DNS → 顶级DNS → 权威DNS,最终拿到服务器IP。

  2. 建立TCP连接:通过IP+端口,三次握手建立可靠TCP连接。

  3. TLS加密握手(HTTPS):证书校验、密钥协商,建立加密传输通道。

  4. 发送HTTP请求:组装请求行、请求头、请求体发送报文。

  5. 后端服务处理:Nginx反向代理 → 路由分发 → 业务逻辑执行 → 组装响应数据。

  6. 返回HTTP响应:状态码、响应头、HTML/静态资源返回浏览器。

  7. 四次挥手断开TCP连接:传输完成释放连接。

  8. 浏览器渲染页面:解析HTML生成DOM树 → 解析CSS生成CSSOM → 合成渲染树 → 布局Layout → 绘制Paint → 分层合成Composite。

  9. 异步加载资源:JS、图片、接口异步请求重复上述流程。

56 TCP三次握手 极致详细流程+原理

56.1 TCP核心标志位

        SYN:请求建立连接;

        ACK:确认报文;

        FIN:请求断开连接。

56.2 三次握手完整步骤

  1. 第一次握手:客户端 → 服务端(SYN):客户端随机生成序列号seq=x,发送SYN报文,进入SYN_SENT状态。

  2. 第二次握手:服务端 → 客户端(SYN+ACK):服务端确认ACK=x+1;自身生成seq=y,返回SYN+ACK,进入SYN_RCVD状态。

  3. 第三次握手:客户端 → 服务端(ACK):客户端确认ACK=y+1,发送空ACK报文;服务端收到,双方进入ESTABLISHED连接成功状态。

56.3 为什么不能两次握手?

  1. 防止失效延迟报文建立无效连接,浪费服务端资源;

  2. 三次握手可以校验双方发送、接收能力全部正常;两次握手只能保证客户端能收,服务端无法确认客户端接收是否正常。

57 TCP四次挥手 极致详细流程+原理

57.1 核心原理

TCP全双工通信,读写通道独立;必须单独关闭读、写通道,因此挥手需要四次,无法合并。

57.2 四次挥手完整步骤

  1. 第一次挥手:主动方发送FIN:主动关闭方发送FIN报文,关闭自身写入通道,进入FIN_WAIT_1。

  2. 第二次挥手:被动方返回ACK:确认关闭,此时连接半关闭;主动方只能收不能发,进入FIN_WAIT_2。

  3. 第三次挥手:被动方发送FIN:被动方业务数据发送完毕,发送FIN关闭自己写入通道。

  4. 第四次挥手:主动方返回ACK+等待2MSL:确认断开,等待2MSL防止报文残留,双方彻底关闭。

57.3 为什么挥手四次、握手仅需三次?

握手合并原理:连接建立阶段无业务数据,服务端SYN建立连接报文与ACK确认报文可以合并为一次发送,因此三次即可完成双向确认。

挥手不可合并原理:TCP为全双工通道,读写通道相互独立;被动关闭方可能还在传输业务残留数据,不能立刻关闭写入通道,必须先ACK应答、处理完存量数据,再单独发送FIN报文,报文无法合并,强制四次挥手。

57.4 2MSL等待时间(架构必背)

MSL:报文最大生存时间,Linux系统默认30s。主动断开方进入TIME_WAIT状态固定等待2MSL,两大核心作用:

①容错保障:保证被动方一定收到最后一次ACK报文,避免被动方超时重发FIN;

②净化链路:耗尽网络中滞留的失效延迟报文,防止下一次复用端口连接收到错乱旧报文,规避脏数据干扰。

生产坑点:短连接高并发场景大量TIME_WAIT占用端口,需调整内核参数优化。

58 TCP高级核心机制(面试高频重难点)

58.1 滑动窗口(流量控制)

核心目的:解决收发双方处理速率不匹配,防止接收缓冲区溢出导致丢包。

底层原理:接收方每次在ACK报文中携带自身剩余缓冲区大小(窗口大小),发送方严格遵循窗口上限发送数据;窗口为0时发送方立即停止发送,等待窗口更新。

动态伸缩窗口,实现精准流量管控,避免盲目发送造成网络拥堵。

58.2 拥塞控制(全网限流)

核心目的:防止大量报文涌入链路,造成全网网络拥堵、路由器丢包。

四大经典算法:慢启动、拥塞避免、快重传、快恢复

①慢启动:初始拥塞窗口极小,指数增长试探网络承载力;

②拥塞避免:窗口线性平缓增长,规避网络临界点;

③快重传:连续收到3次冗余ACK,判定报文丢失,无需等待超时直接重传;

④快恢复:丢包后不重置初始窗口,降低性能损耗,平衡吞吐量与稳定性。

58.3 超时重传(可靠性基石)

TCP可靠传输的底层核心保障。发送方每发送一份报文,绑定计时器;超时未收到对应ACK应答,判定报文丢失/网络延迟,自动重传报文。超时时间动态自适应网络延迟,网络波动时自动拉长时间,兼顾传输效率与极端环境可靠性。

58.4 粘包半包终极补充解决方案

TCP流式协议无数据边界,内核缓冲区缓存、分片合并导致粘包半包。

生产级优先级方案:

①最优方案:长度头+报文体(Netty LengthFieldBasedFrameDecoder,行业通用);

②次选:特殊分隔符(报文禁止出现分隔符,局限性大);

③大文件:固定分片传输,限定单包最大字节;

④禁止无格式裸流传输,生产绝对禁用。

59 HTTP&HTTPS深度详解

59.1 HTTP三大版本迭代差异

HTTP/1.0:短连接模型,一次请求一次TCP连接,请求完成立即断开;无连接复用、无管道化,频繁握手造成巨大性能开销。

HTTP/1.1:默认长连接Keep-Alive,连接复用;支持管道化请求、协商缓存、断点续传;缺陷:单连接串行请求,存在队头阻塞

HTTP/2.0:二进制帧传输替代文本;单连接多路复用,彻底解决队头阻塞;头部压缩减少冗余报文;支持服务器推送;强制基于HTTPS。

59.2 HTTPS TLS1.2 完整握手流程

  1. 客户端握手:发送TLS版本、加密套件列表、客户端随机数、明文随机字符串;

  2. 服务端应答:选定加密套件、下发CA公钥证书、携带服务端随机数;

  3. 证书校验:客户端校验证书有效期、域名、签名合法性,防止证书伪造;

  4. 密钥加密传输:客户端生成预主密钥,使用服务端公钥加密发送;

  5. 会话密钥同步:双方结合三份随机数,本地演算生成一致对称加密密钥;

  6. 加密通信:后续报文全部通过对称密钥加密传输,会话结束销毁密钥。

59.3 HTTP状态码分类大全(生产必认)

1xx信息类:请求接收,继续处理(101协议切换);

2xx成功:200正常响应、204无返回体、206断点续传;

3xx重定向:301永久重定向、302临时重定向、304协商缓存命中;

4xx客户端错误:400参数非法、401未授权、403权限禁止、404资源不存在;

5xx服务端错误:500代码异常、502网关异常、503服务不可用、504网关超时。

60 IO四大模型极致对比(BIO/NIO/AIO/多路复用)

60.1 四大模型底层定义

BIO同步阻塞:线程发起IO请求后持续阻塞,直至读写完成;一连接一线程,并发上限极低,线程资源爆炸。

NIO同步非阻塞:线程主动轮询IO状态,无连接阻塞;数据就绪后手动读写,线程利用率高。

IO多路复用:单线程监听成千上万个通道事件(select/poll/epoll),事件触发后处理;Java NIO底层依托epoll,工业级高并发方案。

AIO异步非阻塞:操作系统完成全部IO读写后主动回调程序,无需线程轮询;JDK实现差、Windows适配良好,生产极少使用。

60.2 BIO、NIO、AIO核心对照表

IO模型

阻塞状态

同步异步

线程开销

适用场景

BIO

阻塞

同步

极高

低并发、本地简单小程序

NIO

非阻塞

同步

极低

高并发、中间件、网关、网络通信

AIO

非阻塞

异步

极低

大文件异步读写、Windows平台

61 NIO三大组件深度底层原理

61.1 Buffer缓冲区

底层依托数组存储数据,

四大核心指针:position(当前读写位置)、limit(读写边界)、capacity(总容量)、mark(备用标记);

核心方法:flip()读写切换、clear()逻辑清空、rewind()重读数据。

缓冲区分类:堆缓冲区(JVM管控、GC回收、读写慢)、直接缓冲区(堆外内存、零拷贝、无GC、Netty首选)。

61.2 Channel通道

区别于传统IO单向流,通道双向可读可写;支持异步非阻塞读写,不能单独存储数据,必须绑定Buffer传输。

常用实现:FileChannel文件通道、SocketChannel客户端网络通道、ServerSocketChannel服务端监听通道。

61.3 Selector多路复用器

NIO核心调度中枢,单线程监听海量Channel;

监听四大事件:OP_CONNECT连接、OP_ACCEPT接收、OP_READ读、OP_WRITE写。

底层适配:Linux采用epoll、Windows采用select;原生JDK NIO存在空轮询Bug,造成CPU100%占用,Netty底层已修复优化。

62 零拷贝深度详解(面试必考)

62.1 传统IO拷贝流程(低效)

磁盘文件→内核缓冲区→用户缓冲区→内核Socket缓冲区→网卡;

全程4次数据拷贝+4次上下文切换,用户态与内核态频繁切换,冗余拷贝严重、吞吐量低。

62.2 两种工业级零拷贝

mmap内存映射:磁盘文件直接映射至用户内存,省去内核→用户态拷贝;适合中小文件、随机读写场景。

transferTo发送文件:全程内核态传输,无用户态介入,仅需2次拷贝+2次切换;适合超大文件传输,Nginx、RocketMQ、RPC框架底层通用。

62.3 零拷贝生产使用场景

分布式文件传输、消息队列持久化、网关静态资源推送、RPC远程调用、大文件上传下载;

生产严禁使用传统IO传输大文件,必须采用零拷贝优化吞吐量。

63 Netty高频面试补充

63.1 ByteBuf优于JDK Buffer核心优势

摒弃固定指针,采用读写分离双指针

支持自动扩容、内存池复用、堆外直接内存;无需flip()切换读写,规避原生缓冲区Bug;

支持引用计数、自动释放内存,杜绝内存泄漏。

63.2 主从Reactor线程模型

BossGroup主线程池:专门监听端口、接收客户端连接、完成TCP握手、分发连接;

WorkerGroup从线程池:处理读写事件、编解码、业务逻辑、心跳检测;线程池隔离,避免连接阻塞业务,支撑百万并发长连接。

63.3 Netty生产调优配置

开启内存池复用ByteBuf、禁用Java偏向锁、优化epoll边缘触发、自定义心跳检测间隔、优雅关闭释放连接、限制单包最大长度防止拆包攻击、禁用无效空轮询。

64 网络高频冷门面试题(收尾必背)

64.1 TCP与UDP终极对比

  1. TCP面向有连接、UDP无连接,无需握手;

  2. TCP可靠有序、丢包重传、流量管控;UDP不可靠、无序、无重传;

  3. TCP头部20~60字节开销大;UDP头部仅8字节,极简高效;

  4. TCP用于转账、网页、接口、文件传输;UDP用于直播、游戏、语音、DNS。

64.2 生产常用端口汇总

HTTP:80、HTTPS:443、

FTP:21、SSH:22、MySQL:3306、

Redis:6379、Nginx:80、

RabbitMQ:5672;生产防火墙严格管控端口,禁止对外开放高危端口。

64.3 长连接&短连接生产选型

短连接:一次请求一次连接,用完立即断开;适合低频零散请求、临时接口调用。

长连接:TCP连接长期复用,持续传输数据;适合高频接口、数据库连接、消息推送、网关通信;长连接必须配置心跳检测,定时剔除僵尸连接,避免端口占用耗尽。

64.4 epoll/poll/select底层区别

select:数组存储文件描述符,最大监听1024、遍历扫描、效率极低;

poll:链表存储、无上限、仍需遍历;

epoll:事件回调机制、无需遍历、海量连接无性能衰减,Java NIO、Netty底层默认采用epoll。

第八部分 反射 & 注解 & 动态代理 & 序列化(框架底层核心)

65 反射机制底层原理(Spring核心基石)

65.1 反射定义与架构价值

反射:程序运行期间,动态获取类全部信息、动态调用任意方法、修改成员变量;打破封装限制。架构意义:框架底层核心,SpringIOC、注解、MyBatis、RPC全部依赖反射实现无侵入扩展。

65.2 Class类与类加载关系

Class是所有类的元数据模板;类加载阶段将class字节码解析为Class对象,存入元空间;一个类在JVM中永久唯一Class对象

三种获取Class方式:对象.getClass()、类名.class、Class.forName()。

65.3 反射常用API

Class:获取类名、父类、接口;

Constructor:构造器反射创建对象;

Method:反射调用方法;

Field:反射读写成员变量;

Modifier:解析权限修饰符。

65.4 反射优缺点与生产规范

优点:高度解耦、动态扩展、框架通用;

缺点:破坏封装、绕过编译校验、性能偏低、可读性差。

生产规范:业务代码禁止手写大量反射,统一封装工具类;反射方法必须关闭权限检查setAccessible(true),提升执行速度。

66 注解体系底层原理

66.1 注解本质与分类

注解:代码标记元数据,无业务逻辑,仅做标识与配置;底层继承Annotation接口。

三大分类:源码注解、编译注解、运行时注解。

66.2 四大元注解(自定义注解必背)

  1. @Target:限定注解生效位置(类、方法、字段、参数);

  2. @Retention:生命周期(SOURCE源码、CLASS编译、RUNTIME运行);

  3. @Documented:生成Java文档保留注解;

  4. @Inherited:子类自动继承父类注解。

66.3 注解解析流程

运行时注解依靠反射解析;编译期注解依靠APT注解处理器(Lombok底层原理);框架通过注解标记+反射扫描,实现自动装配、路由映射、参数校验。

66.4 生产常用内置注解

@Override重写校验、

@Deprecated废弃标记、

@SuppressWarnings压制警告、

@FunctionalInterface函数式接口;

Spring:@Component、@Autowired、@Configuration。

67 动态代理(SpringAOP核心)

67.1 代理模式设计思想

不修改原目标类代码,通过代理类增强方法逻辑;实现业务与非业务逻辑解耦,通用逻辑抽离(日志、事务、权限、限流)。

67.2 静态代理

手动编写代理类,绑定目标类;代码冗余、拓展性差、一个目标类对应一个代理类,生产淘汰。

67.3 JDK动态代理

底层:字节码动态生成代理类、反射执行方法;强制实现接口、基于接口代理;无接口无法使用;InvocationHandler实现方法拦截。

67.4 CGLIB动态代理

底层:ASM字节码框架,继承目标类生成子类代理;无需接口、通过方法重写拦截;禁止代理final类与final方法;Spring默认优先JDK代理,无接口自动切换CGLIB。

67.5 两种代理生产对比

        JDK代理:基于接口、无需依赖第三方包、速度快;

        CGLIB:基于继承、适配无接口类、启动稍慢、运行高效。

        AOP开发原则:有接口用JDK、无接口用CGLIB。

68 序列化 & 反序列化

68.1 序列化定义

序列化:Java对象转为字节数组,用于网络传输、磁盘持久化;

反序列化:字节数组还原为Java对象。

68.2 JDK原生序列化

实现Serializable空标记接口;

底层native方法;

transient关键字修饰属性不参与序列化;

序列化ID serialVersionUID固定版本,防止类结构修改反序列化失败。

68.3 主流序列化框架对比

JDK序列化:笨重、二进制大、兼容性差;

JSON:可读性强、通用、适合接口传输;

Protobuf:压缩率极高、序列化速度快、RPC中间件专用;

Hessian:分布式通信通用。

68.4 序列化生产坑点

禁止序列化大对象防止OOM;

必须固定serialVersionUID;

敏感字段禁止序列化;

反序列化漏洞高危,禁止解析不可信外部字节流。

69 反射进阶:字节码层面原理 & 执行性能优化

69.1 反射底层字节码执行机制

Java反射并非纯Java代码实现,底层大量依赖Native本地方法。Class对象在类加载完成后存入元空间,反射调用流程:

①校验访问权限

②查找方法元数据

③动态拼装栈帧

④native方法执行底层调用。

普通方法调用为编译期静态绑定、直接寻址;

反射为运行期动态寻址,多出权限校验、参数解包、动态栈帧构建,因此性能远低于普通调用。

69.2 反射性能四大优化手段(生产必用)

  1. 关闭权限检查:setAccessible(true),禁用Java安全校验,节省一半反射耗时;

  2. 缓存反射元数据:将Class、Method、Field全局缓存,避免频繁Class.forName()重复加载;

  3. 使用Fast反射工具:Spring ReflectionUtils、Apache PropertyUtils,规避原生反射异常冗余代码;

  4. 反射膨胀为字节码:CGLIB、ASM动态生成代理类,将反射转为硬编码调用,性能接近原生方法。

69.3 反射破坏单例模式原理

饿汉式、懒汉式单例均可被反射暴力破解;通过反射获取私有构造方法、关闭权限校验、强行newInstance()创建新对象。

枚举单例唯一防反射:枚举构造方法为JVM强制保护,反射无法调用枚举私有构造器,从底层杜绝暴力破解,生产最优单例写法。

70 注解高阶:APT、Spring注解扫描、手写注解实战

70.1 三大注解生命周期深度详解

1.SOURCE源码级:仅存在java源码,编译期直接丢弃;

        作用:代码校验、生成辅助代码;

        典型:@Override、@SuppressWarnings、Lombok注解;依托APT编译期注解处理器执行。

2.CLASS编译级:保留至class字节码,类加载阶段丢弃;

        作用:JVM底层字节码标记、框架预扫描;

        典型:@Transactional、编译期埋点。

3.RUNTIME运行级:全程保留,运行期可反射获取;

        作用:业务标记、动态解析、框架装配;

        典型:@Component、@Autowired、自定义业务注解。

70.2 APT注解处理器(Lombok底层核心)

APT:编译期注解处理技术,无需反射、无运行期开销;编译阶段扫描注解、动态修改AST抽象语法树、自动生成get/set、构造器、日志代码。Lombok、MapStruct、MyBatis Generator全部基于APT实现;

优点:零侵入、无运行期性能损耗、简化模板代码。

70.3 Spring注解扫描完整流程

  1. 启动配置扫描包路径,Spring生成扫描器;

  2. ASM读取class字节码,不加载类、轻量化解析注解;

  3. 识别@Component、@Service等标记注解;

  4. 封装BeanDefinition元数据,存入Bean定义注册表;

  5. 后置Bean实例化、依赖注入、初始化方法执行。

70.4 手写自定义注解+AOP实现日志记录(工程模板)

实现思路:

①自定义运行时注解@OperationLog,标注业务描述、操作类型;

②AOP环绕通知拦截标注该注解的方法;

③反射获取注解属性、请求参数、返回值;

④异步入库保存操作日志。

企业级通用模板,权限日志、操作审计、接口埋点全部复用此架构。

71 动态代理深度:手写JDK&CGLIB、底层字节码、AOP源码原理

71.1 JDK动态代理生成字节码解析

JDK代理运行期使用ProxyGenerator动态生成.class字节码;

代理类继承Proxy类、实现目标接口;

重写接口所有方法,方法内部回调InvocationHandler.invoke();

所有代理方法逻辑统一在invoke增强,无硬编码业务逻辑。

缺点:Java单继承限制,无法继承普通类,强制依赖接口。

71.2 CGLIB ASM字节码生成原理

CGLIB依托ASM字节码操作框架,直接修改二进制字节码;

运行期动态生成目标类子类,重写所有非final方法;

底层采用FastClass机制,建立方法下标映射,避免反射调用,运行速度远超JDK动态代理。

局限:不能代理final类、final方法、私有方法;生产Spring无接口类默认使用CGLIB。

71.3 JDK代理 VS CGLIB 终极生产对比表

对比维度

JDK动态代理

CGLIB动态代理

依赖条件

必须实现接口

无需接口、继承父类

底层技术

动态生成接口实现类+反射

ASM字节码+FastClass

调用性能

运行慢、反射开销大

运行极快、无反射

启动开销

低、初始化快

高、字节码生成耗时

禁止场景

无接口无法使用

final类、final方法禁止代理

71.4 Spring AOP 代理选择规则(面试必考)

  1. Spring Boot 2.0+ 默认强制使用CGLIB,不再自动适配JDK;

  2. 手动配置proxy-target-class=true 强制CGLIB;

  3. 有接口也可强制使用CGLIB,规避接口代理限制;

  4. 生产规范:业务层优先定义接口,兼容两套代理机制。

71.5 动态代理典型工程使用场景

SpringAOP事务控制、日志拦截、权限校验、限流熔断;

MyBatis Mapper接口代理(JDK动态代理);

RPC远程接口调用、Feign、Dubbo接口动态生成;

监控链路追踪、接口耗时统计、异常统一捕获。

72 序列化高阶:漏洞原理、版本兼容、加密序列化、生产规范

72.1 serialVersionUID底层版本控制原理

序列化时写入固定版本ID,反序列化时比对字节码内UID;

若实体类新增字段、删除字段、修改修饰符,UID不变则兼容反序列化,新增字段赋默认值;

未手动定义UID,JDK自动随机生成哈希ID,类结构改动直接反序列化报错。生产强制手动固定serialVersionUID = 1L

72.2 transient关键字深度解析

被transient修饰字段:不参与序列化、不写入字节数组;底层标记为临时字段,JDK序列化算法直接过滤。

适用场景:临时缓存、密码明文、敏感隐私数据、连接会话信息;

注意:transient仅对JDK原生序列化生效,JSON、Protobuf序列化无效。

72.3 JDK序列化漏洞(高危生产漏洞)

漏洞成因:反序列化时无需校验、自动执行readObject(),恶意构造畸形字节流可触发代码执行、远程命令注入;

漏洞载体:Apache Commons Collections、Fastjson、Jackson;

生产防护:

①禁止使用JDK原生序列化传输外网数据;

②升级依赖版本、禁用危险反序列化;

③外网传输统一使用JSON/Protobuf。

72.4 四大序列化框架生产选型黄金标准

  1. 内部RPC、中间件传输:优先Protobuf,压缩极小、二进制高速、跨语言;

  2. HTTP接口、前后端交互:JSON(Jackson),可读性强、通用标准;

  3. 老旧分布式系统:Hessian,兼容Java多版本、简洁二进制;

  4. 本地简单持久化:禁止JDK序列化,改用JSON文件持久化。

72.5 序列化通用生产强制规范

  1. 所有序列化实体类手动声明serialVersionUID,杜绝自动生成;

  2. 敏感数据加密后存储,禁止明文序列化;

  3. 禁止序列化大集合、大对象,限制序列化字节大小防止OOM;

  4. 禁止反序列化外部不可信字节流、报文;

  5. 实体类禁止循环引用,避免序列化栈溢出。

第九部分 面试真题(反射+注解+动态代理+序列化 必考20题)

  1. 反射为什么慢?如何优化?:动态寻址+权限校验+native调用;优化:缓存元数据、关闭权限检查、字节码生成。

  2. 能不能反射修改final常量?:编译期常量无法修改、运行期赋值final字段可暴力修改。

  3. 枚举为什么防反射、防序列化破解?:JVM底层保护构造方法,禁止外部调用。

  4. 注解三种生命周期适用场景?:SOURCE代码生成、CLASS字节码标记、RUNTIME业务解析。

  5. Lombok原理是什么?运行期有无性能损耗?:APT编译期生成代码、零运行开销。

  6. Spring怎么不加载类读取注解?:ASM字节码轻量解析,避免类加载开销。

  7. JDK代理为什么必须接口?底层生成什么类?:继承Proxy类、Java单继承限制,只能接口代理。

  8. CGLIB为什么不能代理final?:final方法不可重写,无法字节码增强。

  9. Spring什么时候用JDK、什么时候CGLIB?:高版本默认CGLIB,历史版本有接口JDK、无接口CGLIB。

  10. 动态代理和静态代理区别?:静态硬编码、动态运行期生成字节码。

  11. AOP底层原理?五大通知执行顺序?:动态代理;前置、环绕、异常、返回、后置。

  12. 序列化为什么要实现Serializable?:JDK序列化算法标记,判定是否允许序列化。

  13. 不写serialVersionUID会怎样?:类改动UID变更,反序列化直接报错。

  14. transient修饰static变量有用吗?:无效,static属于类、不参与对象序列化。

  15. 深拷贝和序列化关系?:序列化反序列化可实现最简单深拷贝。

  16. Fastjson漏洞根本原因?:反序列化自动触发构造方法、恶意代码注入。

  17. Protobuf比JSON优势?:二进制压缩、体积小、解析快、适合内网RPC。

  18. 反射能不能获取私有方法?:可以,setAccessible(true)暴力访问。

  19. 动态代理能不能代理静态方法?:不能,静态方法属于类、无动态绑定。

  20. 注解能不能被继承?:默认不能,必须加@Inherited元注解。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐