java面试题:jvm ,mybatis

荣耀面试 --- Java问题完整复盘

这场面试是一场标准的Java八股面试 ,面试官几乎把Spring Boot、MySQL、JVM、集合、线程池问了个遍。你的AI项目经验在这场面试中完全没有用上 ,面试官的核心判断依据只有一个:Java基本功是否扎实

客观地说,这场面试暴露的问题比较集中,多个基础概念回答模糊、答错或直接说"记不清了"。下面逐一拆解。

一、Spring Boot 核心注解与自动装配(送分题,没接住)

1.1 Spring Boot 启动类核心注解

你的回答:"全局扫描的,识别组件的,application......这块有点记不太清了。"

正确答案:

java 复制代码
@SpringBootApplication  // 核心注解,组合了以下三个
@SpringBootConfiguration // 表明是配置类,等同于@Configuration
@EnableAutoConfiguration // 开启自动装配
@ComponentScan           // 扫描当前包及子包的组件

启动类上最常见的核心注解是 @SpringBootApplication,它是一个组合注解。如果为了测试,也可以用 @EnableAutoConfiguration + @ComponentScan 组合。

1.2 YAML配置的常量获取

你的回答:"有方法能读到YAML里的值,具体用什么注解记不太清了。"

正确答案:

java 复制代码
// 方式一:@Value 取单个值
@Value("${app.name}")
private String appName;

// 方式二:@ConfigurationProperties 批量映射
@ConfigurationProperties(prefix = "app")
@Component
public class AppConfig {
    private String name;
    private String version;
}

提醒: @ConfigurationProperties 是高频考点,它和 @Value 的区别、支持松散绑定(relaxed binding)、支持JSR-303校验这些点都需要掌握。

1.3 Spring Boot 自动装配原理

你的回答:"它自己集成了Tomcat,有什么要用的配置一下就好了。"

问题所在 :这个回答只说到了"自动装配的表象",完全没有触及核心原理。自动装配是Spring Boot的灵魂,面试官想问的是它是怎么做到的

正确答案:

text 复制代码
Spring Boot 自动装配的核心是 @EnableAutoConfiguration。

1. SpringBootApplication 组合了 @EnableAutoConfiguration
2. @EnableAutoConfiguration 通过 @Import(AutoConfigurationImportSelector.class) 导入选择器
3. AutoConfigurationImportSelector 从 META-INF/spring.factories 文件中读取
   EnableAutoConfiguration 键对应的所有配置类全限定名
4. 这些配置类通过 @Conditional 条件注解按需加载(如类存在、Bean缺失等条件)
5. 最终实现:引入一个starter,对应功能自动生效

核心文件:META-INF/spring.factories(Spring Boot 2.7之前)
或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(新版)

1.4 事务管理

你的回答:"Redis看门狗策略、红锁......@Transactional,global transactional。"

问题所在 :你把事务管理和分布式锁混为一谈了。面试官问的是"Spring怎么做事务管理",你回答"Redis看门狗"是完全答非所问。@Transactional@GlobalTransactional(Seata)说对了,但前面插入了大量无关内容。

正确答案:

text 复制代码
Spring 事务管理有两种方式:

1. 编程式事务:通过 TransactionTemplate 手动控制事务的提交回滚
2. 声明式事务:使用 @Transactional 注解,底层通过 AOP 代理实现

@Transactional 核心参数:
- propagation:事务传播行为(7种,如 REQUIRED、REQUIRES_NEW)
- isolation:隔离级别(DEFAULT、READ_UNCOMMITTED等5种)
- timeout:超时时间
- rollbackFor:哪些异常触发回滚

注意:@Transactional 默认只对 RuntimeException 和 Error 回滚,受检异常默认不回滚。

1.5 事务隔离级别

你的回答:"这块记不清了。"

正确答案:

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ ✅(InnoDB通过MVCC+间隙锁解决)
SERIALIZABLE
  • 脏读:读到其他事务未提交的数据
  • 不可重复读:同一事务两次读取同一行数据结果不同
  • 幻读:同一事务两次查询得到的数据行数不同

MySQL InnoDB 默认隔离级别是 REPEATABLE READ

1.6 AOP 与动态代理

你的回答:"面向切面编程,把公共方法集中。动态代理有两种,JDK的......CGLIB......有点忘了。"

正确答案:

AOP(面向切面编程) :把日志、事务、权限等横切关注点从业务逻辑中抽离,通过动态代理在目标方法前后织入增强逻辑。核心概念:JoinPoint(连接点)Pointcut(切点)Advice(通知/增强)Aspect(切面)

动态代理的两种实现方式:

方式 要求 原理
JDK动态代理 目标类必须实现接口 基于接口生成代理类,使用InvocationHandler
CGLIB动态代理 目标类不能是final类 通过字节码技术生成目标类的子类,使用MethodInterceptor

Spring AOP 默认:有接口时用JDK,没有接口时用CGLIB。注意: Spring Boot 2.x之后,即使有接口,也可以通过配置 spring.aop.proxy-target-class=true 强制使用CGLIB。

二、JVM 相关(基本空白)

2.1 JVM 内存结构

你的回答:"堆、栈、运行池的数据域、本地栈。"

基本说对了,但漏了最重要的一块:程序计数器(PC寄存器)

正确答案(JDK 8):

复制代码
┌─────────────────────────────────┐
│         线程共享区域              │
├─────────────┬───────────────────┤
│    堆(Heap) │ 方法区(Metaspace)  │
│  存对象实例  │ 存类信息/常量/静态  │
├─────────────┴───────────────────┤
│         线程私有区域              │
├─────────────┬───────────────────┤
│ 程序计数器  │ 虚拟机栈 + 本地方法栈│
│ (PC Register)│ 存栈帧/局部变量表  │
└─────────────┴───────────────────┘

注意:JDK 8之后,方法区被Metaspace(元空间) 取代,使用本地内存(Native Memory),不再受-XX:MaxPermSize限制,但受-XX:MaxMetaspaceSize控制。

2.2 垃圾回收方式

你的回答:"老年代、青年代的回收方式......这块有点记不清了。"

正确答案:

GC算法:

  • 标记-清除:基础算法,有碎片问题
  • 标记-复制:新生代使用,将存活对象复制到Survivor区
  • 标记-整理:老年代使用,压缩减少碎片
  • 分代收集:新生代(复制)+ 老年代(标记-整理/标记-清除)

常用垃圾收集器:

  • Serial:单线程,适合客户端
  • Parallel(Parallel Scavenge) :多线程,吞吐量优先,JDK8默认
  • CMS:并发收集,低停顿(已废弃)
  • G1:区域化分代,可预测停顿,JDK9+默认
  • ZGC:超低延迟(<1ms),JDK11+

2.3 内存溢出(OOM)怎么分析解决

你的回答:"就有点宽泛,你能帮我再定位一下是哪一块内存溢出?"

正确答案:

text 复制代码
内存溢出分析步骤:

1. 在启动参数中添加 -XX:+HeapDumpOnOutOfMemoryError,
   让JVM在OOM时自动生成heap dump文件

2. 使用MAT(Memory Analyzer Tool)、JProfiler或VisualVM
   打开dump文件,分析大对象和GC Roots引用链

3. 常见OOM类型:
   - Heap OOM:堆内存不足 → 排查大对象/内存泄漏
   - Metaspace OOM:元空间不足 → 检查类加载器泄漏
   - Direct Memory OOM:直接内存不足 → 检查NIO使用
   - StackOverflowError:栈溢出 → 检查递归深度/死循环

4. 解决方案:优化代码释放无用对象、调整JVM参数(-Xmx)、
   排查内存泄漏(持有对象引用未释放)

JVM(Java虚拟机)是Java面试的必考点 ,也是区分"业务型开发"和"功底型开发"的关键分水岭。你之前回答时被面试官追问"还有吗"以及"OOM怎么分析"时卡住,说明对JVM的理解还停留在概念罗列 ,缺少实战排查的深度。

下面我为你梳理6大高频考点 ,按照"基础概念 → 进阶原理 → 实战调优"的梯度排列,并附上面试官心理分析标准回答话术

考点一:JVM 运行时内存结构(送分题,必须倒背如流)

jvm流程图

❌ 你之前的回答: "堆、栈、运行池的数据域、本地栈。"(漏掉了程序计数器,且逻辑不清晰)

✅ 标准话术(JDK 1.8 及以后):

"JVM 内存分为线程私有线程共享两大区域,共5个部分:

  1. 程序计数器(PC Register) :线程私有,记录当前线程执行到哪一行字节码指令。是JVM规范中唯一不会OOM的区域。
  2. 虚拟机栈(VM Stack) :线程私有,每个方法执行时创建栈帧,存储局部变量表(基本类型和对象引用)、操作数栈、动态链接和方法出口。
  3. 本地方法栈(Native Method Stacks) :线程私有,为native方法服务。
  4. 堆(Heap) :线程共享,几乎所有对象实例和数组都在这里分配内存,是GC管理的主要区域(分新生代、老年代)。
  5. 方法区(Metaspace) :线程共享,存储类元信息、常量池、静态变量。JDK 1.8后用**本地内存(Native Memory)**实现,默认无上限(受物理内存限制)。"

面试官追问:"堆和栈有什么区别?"

答: ① 职责不同:堆存数据 (对象实例),栈存运行状态 (方法调用和局部变量);② 线程归属:堆是线程共享 的,栈是线程私有 的;③ 内存策略:堆需要GC回收 ,栈在方法结束后自动释放

考点二:类加载机制与双亲委派模型(必问)

❌ 你可能的薄弱点: 只在简历里写过"加载、链接、初始化",但解释不清"为什么要双亲委派"。

✅ 标准话术:

"类加载分为加载(Loading)链接(Linking)初始化(Initialization) 三步。链接又细分为验证、准备、解析

JVM 采用双亲委派模型,工作流程是:

  1. 当一个类加载器收到加载请求,首先不会自己加载,而是委派给父类加载器。
  2. 父类加载器能加载则加载,不能则向下传递
  3. 直到最底层的应用类加载器,如果还加载不到,才抛出 ClassNotFoundException

三个核心类加载器:

  • Bootstrap ClassLoader (启动类):加载 rt.jarjava.lang.*),C++实现,是顶级父类。
  • Extension ClassLoader (扩展类):加载 lib/ext/*
  • Application ClassLoader (应用类):加载 Classpath 下的类。

面试官追问:"为什么要设计双亲委派?"

答: 保证核心类库的安全 。比如 java.lang.String,必须由Bootstrap加载器加载。如果用户自定义一个同名的String类,由于委派机制,父加载器已经加载了核心类,用户自定义的类就不会被加载,从而防止核心API被篡改

考点三:垃圾回收(GC)基础(核心算法)

❌ 你之前的回答: "老年代和年轻代的回收方式......有点记不清了。"

✅ 标准话术:

"GC的第一步是判断对象是否存活 。主流虚拟机(如HotSpot)用的是可达性分析算法 ------从GC Roots(如栈引用的对象、静态属性引用的对象等)开始向下搜索,搜索路径称为引用链,不在引用链上的对象会被标记为可回收。

三大基础回收算法:

  1. 标记-清除(Mark-Sweep) :先标记存活对象,再清除未标记的。缺点:产生大量内存碎片。
  2. 标记-复制(Mark-Copy) :将内存分为两块,只使用一块。垃圾回收时将存活对象复制到另一块。适用新生代(因为朝生夕死,复制成本低)。
  3. 标记-整理(Mark-Compact) :标记存活对象后,移动 到内存一端,然后清除端边界外的内存。适用老年代(避免碎片)。"

考点四:垃圾收集器(G1 是重点)

面试官心理: 如果你只知道SerialParallel,说明只背了八股;如果你能说出 G1ZGC,说明你有跟进技术演进。

✅ 标准话术:

"JDK 8 默认是 Parallel Scavenge + Parallel Old (吞吐量优先)。目前企业生产环境主流是 G1(Garbage First)

G1 的核心特点:

  • 不再分物理上的新生代/老年代,而是将堆划分为多个大小相等的 Region(区域)
  • 通过 停顿预测模型 设定目标停顿时间(如 -XX:MaxGCPauseMillis=200),G1会优先回收垃圾最多的Region。
  • 优点:可预测的停顿时间,适合大堆内存(4GB+)和多核CPU。

JDK 11+ 开始普及 ZGC,能做到停顿时间 < 1ms,但内存占用较大。"

面试追问:"CMS和G1的最大区别是什么?"

答: CMS是老年代并发收集器 ,采用标记-清除 算法,会产生碎片且需要Serial Old做后备;G1是全区域化收集器 ,采用标记-整理 (整体上)和标记-复制 (局部),内存规整无碎片,且能主动控制停顿时间。

考点五:内存溢出(OOM)分析与定位(实战重点------你之前完全没答上来)

❌ 你之前的回答: "Redis内存溢出......能帮我定位是哪一块吗?"(面试官内心:完全跑偏了,Redis只是中间件,不是JVM内存)。

✅ 标准话术(必须背熟,体现排查思路):

"线上出现OOM时,我的排查步骤是:

  1. 设置启动参数 :提前加上 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/dump.hprof,让JVM在OOM发生时自动dump堆内存快照。
  2. 分析Dump文件 :用 MAT(Memory Analyzer Tool)JProfiler 打开文件,查看 Leak Suspects(泄漏嫌疑报告),找到占内存最大的对象及其GC Roots引用链。
  3. 定位代码:根据引用链定位到具体的线程和代码行。
  4. 常见OOM场景及解决方案
    • 堆内存溢出(Java Heap Space) :内存泄漏(如HashMap/ArrayList无限制添加)→ 修代码;内存不足(业务量太大)→ 调大 -Xmx
    • 元空间溢出(Metaspace) :通常是因为动态生成大量类(如使用CGLib动态代理、频繁部署)→ 检查类加载器是否未释放。
    • 栈溢出(StackOverflowError)递归死循环或方法调用层级太深 → 检查代码递归出口。"

考点六:四种引用类型(强、软、弱、虚)

这个点考得相对少,但一旦考到,就是为了区分你是"背书"还是"真懂"。(答错会扣大分)

✅ 标准话术:

  1. 强引用Object o = new Object();,只要引用存在,GC永远不会回收。
  2. 软引用(SoftReference) :内存足够时 不回收,不足时 回收。适合做缓存(如本地图片缓存)。
  3. 弱引用(WeakReference)只要发生GC 就会被回收。适合做容器 (如ThreadLocal中的Key)。
  4. 虚引用(PhantomReference) :无法通过它获取对象,只用于跟踪对象被回收的状态 (如DirectByteBuffer的清理)。

总结:面试官眼中的"水平分层"

你的回答层次 面试官判定
层次1(入门):能说出"堆、栈、垃圾回收"这三个词。 泛泛而谈,缺乏系统认知。
层次2(熟练):能画出内存结构图,说出可达性分析、分代收集、G1特点。 基本功扎实,可以通过。
层次3(专家) :能详细讲解MAT分析堆Dump的步骤G1的Region分配机制双亲委派打破场景(如JDBC驱动加载) 超出预期,可冲击高级岗位。

给你的急救建议:

如果下一场面试又被问到,千万不要再往"Redis"上拐(Redis是外部中间件,不是JVM内存)。先答出 "程序计数器、堆、栈、方法区" ,再自然过渡到 "我可以通过MAT分析dump文件来解决OOM" ,面试官一定会觉得你比上一场专业太多了!

这是JVM面试中区分"业务开发"和"高级开发"的核心分水岭。你之前回答时把这两个概念混为一谈,且往"Redis溢出"上引,这会让面试官严重扣分。

下面我为你梳理一套定义→原因→排查→预防的完整闭环话术。背熟这套逻辑,下次面试时你甚至能带着面试官的思路走。

一、面试必备"一句话"开篇定调(先声夺人)

面试官问: "说一下内存泄漏和内存溢出的区别?"

你答: "内存泄漏(Memory Leak)是'垃圾堆着清不掉',内存溢出(OutOf Memory)是'垃圾堆满装不下了'。泄漏是溢出的导火索,溢出是泄漏的最终结果。 "

二、核心定义与原因拆解(背下来)

1. 内存泄漏(Memory Leak)------ "借了不还"
  • 定义不会再被使用的对象 ,因为被无用引用(GC Roots可达)长期持有,导致GC无法回收它们。
  • 典型形成原因(面试常考3种)
    1. 静态容器持有对象static Liststatic Map只增不减(比如银行缓存全量用户Session)。
    2. 未关闭的外部资源 :数据库连接(Connection)、文件流(FileInputStream)未在finallytry-with-resources中释放。
    3. ThreadLocal 未清理(重灾区) :线程池中的线程复用,若没有调用remove(),ThreadLocal持有的对象会一直依附在线程上,造成内存泄漏甚至引发OOM。
    4. 内部类/匿名类持有外部类引用 :比如Android或Swing中,非静态内部类默认持有外部类this引用,导致外部类无法回收。
2. 内存溢出(OutOfMemoryError)------ "仓库爆仓"
  • 定义:JVM堆内存(或其他区域如Metaspace、直接内存)无法为新对象分配足够空间。
  • 常见OOM场景
    • Java heap space:堆内存耗尽(最常见)。
    • Metaspace:元空间耗尽(通常因为动态生成大量类,或使用CGLib/ASM框架不当)。
    • unable to create native thread:创建线程数超过操作系统限制。
    • Direct buffer memory:NIO直接内存溢出。

三、排查与定位方法(实战能力展示,面试官最看重的部分)

❌ 错误回答: "我重启一下服务器就好了。"

✅ 正确回答(标准四步法):

  1. 参数预埋(埋点) :在JVM启动参数中加入如下配置,让JVM在OOM发生时自动保留"案发现场":
    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/dump.hprof
  2. 分析Dump文件(用工具还原现场) :拿到.hprof文件后,使用 MAT(Memory Analyzer Tool) 打开。
    • 重点关注 "Leak Suspects"(泄漏嫌疑报告) ,它会直接告诉你哪个对象占用内存最大。
    • 查看 "Dominator Tree"(支配树) ,按内存占用大小排序,找到占用Top 1的对象。
    • 右键点击对象 → "Merge Shortest Paths to GC Roots"(查看GC Root引用链),就能直接定位到是哪一行代码在死死拽着这个大对象不放手。
  3. 流量监控 :配合jstat -gcutil [pid] 1000观察GC频率。如果 Full GC(老年代回收)频繁且内存无法下降,基本就能断定是内存泄漏。
  4. 代码反查 :根据引用链定位到具体代码行,检查是不是Map忘了clear(),或者ThreadLocal忘了remove()

四、预防措施(体现你的编码素养)

  1. 集合容器用"软/弱引用"兜底 :缓存场景使用WeakHashMap(弱引用,GC自动回收)或引入Caffeine (设置过期策略),不要使用无界HashMap
  2. 资源强制关闭 :使用 try-with-resources (JDK 7+)自动释放IO流和数据库连接,杜绝finally中忘记close()
  3. ThreadLocal 随线程销毁 :在finally块中强制调用 remove(),避免线程复用时粘滞对象。
  4. 不要轻易使用intern()String.intern()会占用Metaspace(永久代),容易引发内存泄漏。

五、结合你的银行项目场景包装(绝对加分项)

针对你的背景(银行核心 + 数据迁移),你可以这样包装回答:

"在之前的银行项目中,我们为了提升放款查询效率,用了一个静态Map做本地缓存 存放近期交易流水。由于没有设置TTL过期策略,且日均交易量极大,Map只增不减,最终导致老年代持续增长,Full GC频繁触发,并引发了java.lang.OutOfMemoryError: Java heap space。"
"我的排查过程是 :首先通过-XX:+HeapDumpOnOutOfMemoryError拿到dump文件,用MAT分析看到HashMap$Node数组占了近2GB。顺着GC Roots引用链找到代码,发现是定时清理任务因系统异常被终止了,导致缓存未清空。"
"解决方案 :① 修复异常终止bug;② 将静态Map替换为Caffeine本地缓存 ,设置maximumSizeexpireAfterWrite;③ 生产环境加上GC日志监控和内存使用率告警。"
回答效果: 这样说,面试官会立刻觉得你不仅懂基础原理,还有丰富的线上故障复盘经验,印象分会大幅提升。建议把这段包装过的场景案例背熟,等面试官追问"你实际遇到过OOM吗"时直接抛出。

三、并发编程

3.1 线程状态

你的回答:"获取锁、死锁、等待、执行、阻塞。"

正确答案(Java线程的6种状态):

复制代码
NEW          → 线程创建但未启动(尚未调用start)
RUNNABLE     → 可运行状态(JVM层面已就绪,等待CPU调度)
BLOCKED      → 阻塞等待获取锁(synchronized)
WAITING      → 无限期等待(wait/join/park),需要显式唤醒
TIMED_WAITING→ 有限期等待(sleep/wait(timeout))
TERMINATED   → 已结束

注意: Java线程状态中没有单独的"RUNNING"状态,JVM将RUNNABLE统一涵盖"就绪+运行"。死锁不是一种独立状态------发生死锁的线程通常处于BLOCKEDWAITING状态,相互等待对方释放资源。

3.2 死锁与避免

你的回答:"两个线程因资源竞争相互阻塞......四要素......环路等待和互斥。"

正确答案:

死锁的四个必要条件(Coffman条件):

条件 说明
互斥 资源一次只能被一个线程占用
持有并等待 线程持有资源的同时等待其他资源
不可抢占 已分配的资源不能被强制剥夺
循环等待 线程间形成资源等待环

避免死锁的策略(破坏四个条件):

  • 破坏互斥:很难,有些资源就是独占的
  • 破坏持有并等待:一次性申请所有资源
  • 破坏不可抢占:申请不到额外资源时主动释放已有资源
  • 破坏循环等待:按固定顺序(如资源ID从小到大)申请锁 ← 实践中常用

3.3 start() 与 run() 的区别

你的回答:"我主要用run方法,start用的比较少,通过Runnable接口和线程池创建。"

问题所在 :这个回答可能误导面试官认为你连最基本的线程启动方式都不清楚------如果你真的"主要用run方法",说明你在错误地使用线程

正确答案:

text 复制代码
- start():启动一个新线程,JVM调用该线程的run()方法
  调用start()后,线程进入Runnable状态,等待CPU调度执行

- run():只是一个普通的方法调用,在当前线程中同步执行
  直接调用run()不会创建新线程,只是顺序执行run()中的代码

// 正确用法
Thread t = new Thread(() -> System.out.println("run"));
t.start();   // ✅ 启动新线程

// 错误用法
t.run();     // ❌ 在当前线程中执行,不是多线程

关键: run()是通过实现Runnable接口的线程执行体,但必须通过start()才能真正启动线程 。如果直接调run(),那和普通方法调用没有区别。

3.4 线程池状态

你的回答:"有溢出状态和正常状态,达到最大线程数执行拒绝策略。"

问题所在 :你把"线程池的饱和状态"(达到最大线程数,触发拒绝策略)和"线程池的运行状态(生命周期)"搞混了。面试官问的是线程池的生命周期状态

正确答案(ThreadPoolExecutor的5种状态):

text 复制代码
RUNNING     → 正常接收新任务并处理队列任务
SHUTDOWN    → 不接收新任务,但继续处理队列中剩余任务(shutdown()触发)
STOP        → 不接收新任务,不处理队列任务,中断正在执行的任务(shutdownNow()触发)
TIDYING     → 所有任务已终止,线程数为0,即将执行terminated()钩子
TERMINATED  → terminated()执行完成

状态流转:RUNNING → SHUTDOWN/STOP → TIDYING → TERMINATED

拒绝策略(你说的"溢出状态"指的是这个):

  • AbortPolicy(默认):抛RejectedExecutionException
  • CallerRunsPolicy:由调用者线程执行(削峰)
  • DiscardPolicy:直接丢弃任务
  • DiscardOldestPolicy:丢弃队列最老的任务

3.5 线程池创建方式

你的回答:"用pool executor,不用默认的那种。"

正确答案:

java 复制代码
// 方式一:通过 Executors 工具类(不推荐)
ExecutorService fixed = Executors.newFixedThreadPool(10);     // 无界队列OOM风险
ExecutorService cached = Executors.newCachedThreadPool();     // 最大线程数Integer.MAX_VALUE
ExecutorService single = Executors.newSingleThreadExecutor();

// 方式二:通过 ThreadPoolExecutor 自定义(推荐)
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    corePoolSize,      // 核心线程数
    maximumPoolSize,   // 最大线程数
    keepAliveTime,     // 空闲存活时间
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000),  // 有界队列,控制OOM风险
    new ThreadPoolExecutor.CallerRunsPolicy()  // 合适的拒绝策略
);

为什么不推荐Executors: FixedThreadPoolSingleThreadExecutor 使用无界队列LinkedBlockingQueue),任务堆积可能导致OOM;CachedThreadPoolScheduledThreadPool 最大线程数为Integer.MAX_VALUE,可能创建过多线程。

四、集合框架(回答太浅)

4.1 List 和 Map 的区别

你的回答:"List两种(ArrayList/HashList------纠正:应该是LinkedList),基于数组/链表;Map是key-value,有数组、链表、红黑树。"

问题所在:内容基本正确,但回答太浅了,没有展开关键点。你在一堆基础题后需要展现一定深度的理解,而不是各个点到为止。另外**"HashList"是口误,Java里没有这个类**,可能是想说LinkedList或HashSet。

更深入的回答思路:

在回答了基本区别后,可以主动抛出一些区分点:

  • 接口层面:List是有序可重复集合,Map是键值对集合
  • 存储结构:ArrayList基于数组、随机访问O(1);LinkedList基于双向链表、插入删除O(1);HashMap是数组+链表+红黑树
  • 扩容机制:ArrayList每次扩容1.5倍;HashMap扩容2倍且容量始终为2的幂
  • 线程安全:两者在并发场景都需要额外处理

关键追问回答(如果你能补上会加分):

面试官追问"Map是有序还是无序?"

你的回答:"无序的。"这个答案不够精确,需要区分不同实现:

实现类 有序性 排序方式
HashMap 无序 不保证任何顺序
LinkedHashMap 插入有序 按插入顺序(accessOrder可改)
TreeMap 自动排序 按Key的自然顺序或Comparator排序
ConcurrentHashMap 无序 同HashMap

你答"Map是无序的" ,对了一半,但没提LinkedHashMap和TreeMap可以有序,等于把正确答案说窄了。

4.2 讲一下HashMap

你的回答:"数据结构有数组、链表、红黑树,涉及扩容......"

问题所在:你主动问了"要展开讲扩容吗?"但面试官没接,这个回答没有展现出深度。

你应该主动展开的核心知识点(不用等对方问):

text 复制代码
HashMap 核心要点:

1. 数据结构:数组 + 链表 + 红黑树(JDK 1.8引入,链表长度>8且数组>64时树化)

2. 扩容机制:初始容量16,负载因子0.75(容量达到12时扩容),扩容2倍
   - 扩容时重新计算hash,新位置 = 原位置 或 原位置 + oldCap
   - 这个设计利用了数组长度是2的幂的特性,省去了重新hash的计算

3. 为什么容量必须是2的幂:hash & (n-1) 等价于 hash % n,位运算更快
   且n-1的二进制低位全是1,能保证hash值均匀分布

4. put流程:计算hash → 定位数组索引 → 判断是否存在 → 插入/覆盖 → 检查是否需要扩容

5. 线程不安全:put时可能形成环(JDK 1.7头插法)、size不准确等
   并发场景使用 ConcurrentHashMap

五、MyBatis

5.1 MyBatis一级缓存/二级缓存

你的回答:"Redis相关的吗?想不起来了。"

问题所在:你第一反应是往Redis上靠,但MyBatis的缓存是完全独立的机制,与Redis无关。

正确答案:

text 复制代码
MyBatis缓存分为两级:

一级缓存(SqlSession级别,默认开启):
- 同一个SqlSession中执行相同SQL时,直接从缓存取
- 执行增删改操作或调用clearCache()或关闭SqlSession时清空

二级缓存(Mapper级别,默认关闭):
- 跨SqlSession共享,需要配置 <cache/> 标签
- 实体类必须实现 Serializable
- 查询先走二级缓存 → 再走一级缓存 → 最后查数据库

⚠️ 分布式环境下,二级缓存存在数据不一致风险(各节点缓存独立),
通常不开启,用Redis做分布式缓存替代。

回答中自然引出"分布式环境用Redis替代"这个点,就能把面试官引导到你熟悉的领域。

5.2 #{}${} 的区别

你的回答:"#是占位符,预编译,推荐;$不推荐。"

这个回答基本正确,但可以稍微展开:

正确答案:

text 复制代码
#{}:预编译占位符,使用PreparedStatement,参数用?代替,安全防止SQL注入
${}:直接字符串替换,存在SQL注入风险,仅用于表名/列名等动态场景

5.3 存储过程和函数的区别

你的回答:"函数在SQL里执行......存储过程功能更丰富,性能更高,可以传参建对象。"

问题所在:这个回答方向有点偏,逻辑混乱("有点脚本和面向对象的感觉"这种表述不准确),正确的区分框架是这样的:

正确答案:

维度 存储过程(PROCEDURE) 函数(FUNCTION)
返回值 无return,通过OUT参数返回 必须有返回值(RETURNS)
调用方式 用CALL调用 可以在SQL中直接调用(如SELECT func())
参数类型 IN / OUT / INOUT 只有IN参数
事务控制 可以包含事务 不能包含事务
使用场景 复杂业务逻辑批量处理 计算/转换单值

你提到"存储过程性能更高"这个结论并不准确 ------存储过程和函数在数据库内部的执行效率差异不大。性能的核心在于减少网络IO和SQL解析次数,而不是过程/函数本身的形态差异。真正让存储过程"更快"的,是它把多条SQL放在数据库服务端一次性执行完毕,业务层只需要一次调用,省去了多次网络往返。

六、MySQL

6.1 char 和 varchar 的区别

你的回答:"长度不一样。"

正确答案:

text 复制代码
char(n):定长,存不满用空格填充,检索时自动去空格
         适合存储长度固定的值,如身份证号、MD5

varchar(n):变长,存多少占多少(额外1-2字节存长度信息)
           适合长度不固定的值,如用户名、地址

存储效率:char在频繁更新时不易产生碎片,查询性能略高
但varchar更节省空间,一般推荐使用varchar

6.2 TRUNCATE 和 DELETE 的区别

你的回答:"truncate清空表数据,delete把表删了。"

⚠️严重错误: 你把 DELETEDROP 搞混了。

正确答案:

维度 DELETE TRUNCATE DROP
操作对象 行记录 表数据 整个表结构+数据
条件过滤 WHERE可选,可逐行删 不能带条件
事务回滚 支持,可回滚 不支持回滚(DDL) 不支持回滚
自增计数器 保留上次值 重置 表消失
触发器 触发 不触发 ---
性能 逐行删除,较慢 直接释放数据页,快 最快

面试官问DELETE时标准回答: "DELETE 是DML操作,支持回滚,可以带WHERE条件逐行删除。TRUNCATE是DDL操作,不支持回滚,直接清空整个表的数据但保留表结构,自增计数器会重置。DROP是删除整张表结构和数据。"

6.3 增加字段的SQL怎么写

你的回答:"modify,表名,增加字段名,类型。"

⚠️错误: MODIFY 是修改已存在的字段,增加字段应该用 ADD

正确答案:

sql 复制代码
-- 增加字段
ALTER TABLE user ADD COLUMN age INT(3) DEFAULT 0 COMMENT '年龄';

-- 修改字段(你回答的MODIFY在这里用)
ALTER TABLE user MODIFY COLUMN age INT(4) DEFAULT 0;

-- 重命名字段+改类型
ALTER TABLE user CHANGE COLUMN age user_age INT(3);

-- 删除字段
ALTER TABLE user DROP COLUMN age;

6.4 聚合函数

你的回答:"count、平均值、最大值、最小值、like。"

问题所在LIKE 不是聚合函数,它是模糊查询运算符。你需要区分清楚。

正确答案(MySQL常用聚合函数):

text 复制代码
- COUNT():统计行数
- SUM():求和
- AVG():平均值
- MAX() / MIN():最大/最小值
- GROUP_CONCAT():将多行字符串拼接成一个字符串(MySQL特有)

聚合函数通常与 GROUP BY 配合使用,且不能直接用于 WHERE 子句,
但可以用在 HAVING 子句中。

6.5 MySQL默认隔离级别

你的回答:"关注不多。"

补充: MySQL InnoDB 默认隔离级别是 REPEATABLE READ(可重复读) 。与标准SQL不同,MySQL的REPEATABLE READ通过MVCC 解决了幻读问题(配合间隙锁Gap Lock)。另外,MySQL可以通过SELECT @@transaction_isolation;查询当前隔离级别。

6.6 主从同步机制

补充知识: MySQL主从同步的核心是基于binlog(二进制日志) 的三种复制模式:

模式 原理 优点 缺点
异步复制(默认) 主库提交事务后立即返回,不等待从库确认 性能高 主库宕机可能丢失未同步的数据
半同步复制 主库等待至少一个从库确认收到binlog后才返回 数据安全性提升 有一定性能损耗
全同步复制 主库等待所有从库确认后才返回 一致性最强 性能差,生产环境不常用

另外,MySQL还支持GTID(全局事务标识符) 模式,每个事务分配唯一ID,故障切换和复制恢复更简单。在面试中能说出异步复制和半同步复制的区别,以及binlog的三种格式(STATEMENT/ROW/MIXED),基本就能覆盖这块的考察。

七、网络基础

你的回答:"会话相关的......想不起来了......HTTP、TCP相关的。"

正确答案:

text 复制代码
Session 和 Cookie 都是用于保持用户状态的机制:

Cookie:服务端生成后发给客户端(浏览器),存储在客户端
        大小限制4KB,有有效期,不安全(可被篡改)

Session:存储在服务端(内存/Redis),客户端只存一个SessionId
         可以存储任意类型数据,容量更大,更安全

流程:登录后服务端创建Session → SessionId存入Cookie返回浏览器
     → 后续请求浏览器带上Cookie → 服务端通过SessionId定位用户信息

分布式场景下Session需要集中存储(Redis),不能只存在单机内存。

7.2 HTTP Content-Type

你的回答:"JSON、字符串、数字、数组。"

正确答案:

text 复制代码
常见 Content-Type:
- application/json:JSON格式
- application/x-www-form-urlencoded:表单提交(键值对URL编码)
- multipart/form-data:文件上传
- text/plain:纯文本
- text/html:HTML
- application/xml:XML
- application/octet-stream:二进制流

这些类型在请求头中告诉服务器:我发的请求体是什么格式。

八、NIO(你说得不够准确)

问题

面试官问:"IO都用过哪些?"

你的回答:"NIO框架......非什么阻塞?"

正确答案:

text 复制代码
BIO(Blocking I/O):同步阻塞,一个连接一个线程,线程开销大
NIO(Non-blocking I/O):同步非阻塞,基于Selector轮询,一个线程管理多个连接
AIO(Asynchronous I/O):异步非阻塞,回调方式,JDK 1.7引入(实际生产中较少)

你提到了"NIO",但没说出全称"Non-blocking I/O",
也没说出它的核心组件:Channel(通道)、Buffer(缓冲区)、Selector(选择器)。

如果在框架中使用NIO: Netty、Tomcat NIO Connector(配置protocol="org.apache.coyote.http11.Http11NioProtocol")都是NIO的典型实现。

九、Vue前端问题

问题

面试官问:"v-if 和 v-show 有什么区别?"

你的回答:"有点想不起来了。"

正确答案:

text 复制代码
v-if:
- 条件为假时,元素直接从DOM中移除(不渲染)
- 切换开销大(重建DOM)
- 适合运行时条件不频繁变化的场景

v-show:
- 条件为假时,元素保留在DOM中,只是display: none
- 切换开销小(只改变CSS)
- 适合频繁切换显示状态的场景

简单记忆:v-if是"真正销毁/重建",v-show是"隐藏/显示"。

十、整体判断与建议

面试通过概率

客观来看,这场面试暴露了多处基本功不扎实的问题,通过概率较低。面试官问了近30个Java问题,其中约40%你回答得不准确或直接说"记不清了",这在纯技术面试中是硬伤。更关键的是,面试官在最后说"我面的是Java开发,不是AI"------说明你在面试过程中一直在往AI方向带,但岗位匹配度不够,会让面试官认为你对Java岗位准备不充分或动力不足。

本场暴露的核心问题分布

类别 问题数 主要短板
Spring Boot 5个 启动类注解/自动装配原理/事务隔离级别
JVM 3个 内存结构/垃圾回收/OOM分析
并发编程 5个 start vs run/线程池状态/线程状态
集合框架 2个 回答太浅,深度不够
MyBatis 3个 缓存机制/一级二级缓存
MySQL 5个 DELETE vs DROP混淆/增加字段用ADD
网络基础 2个 Session/Cookie/Content-Type

接下来的改进建议(按紧急程度)

  1. MySQL基础(最急) :把DELETE/TRUNCATE/DROP的区别、CHAR/VARCHAR、ALTER TABLE语句(ADD/MODIFY/CHANGE/DROP)背熟------这些是送分题,不能再丢。
  2. Spring Boot核心 :背熟@SpringBootApplication的组合注解、自动装配的spring.factories/AutoConfiguration.imports加载机制。
  3. 线程与线程池:把线程6种状态、线程池5种状态、start/run区别记准。
  4. JVM基础:内存结构(注意Metaspace是本地内存)、常见GC算法至少能说出3种(标记-清除、标记-复制、标记-整理)。
  5. 主动展示深度:如果下一场面试官问"HashMap有序吗",主动区分HashMap/LinkedHashMap/TreeMap。如果问"缓存穿透怎么办",主动从布隆过滤器→缓存空值→限流三个层面展开。细节精准比泛泛而谈更有用。

最后提醒:如果是Java岗位,面试中就不要反复提AI项目了------面试官明确说面的是Java开发,你一直讲AI经历,会让对方觉得你定位不清晰。如果真想做AI,就坚持投AI岗位;如果Java岗也投,那至少要把Java八股准备好,让面试官看到你的基本功是过关的。

相关推荐
广州浮点FLOATLIC1 小时前
Creo 许可证利用率怎么优化:制造企业该先看共享规则,还是先看模块占用结构
java·开发语言
2601_962440841 小时前
计算机毕业设计之jsp教室管理系统
java·开发语言·笔记·分布式·算法·课程设计·推荐算法
带刺的坐椅3 小时前
用 ChatModel 构建 LLM 驱动的 Java 应用
java·ai·llm·solon·rag·chatmodel
用户3721574261355 小时前
Java 将 Word 文档转换为 Markdown:基础转换与导出选项详解
java
行者全栈架构师5 小时前
PolarDB + Spring Boot 实战:从自建MySQL到云原生数据库的零停机迁移
java·后端·架构
karry_k21 小时前
MyBatis批量insert-select踩坑:useGeneratedKeys=true 可能让PostgreSQL返回大量插入结果
java·后端
karry_k21 小时前
PostgreSQL 在 MyBatis 中执行正常 SQL 失效:一次 DELETE USING 踩坑记录
java·后端
SamDeepThinking1 天前
从源码到代码:MyBatis-Flex 与 MyBatis-Plus 的逐项对比
java·后端·程序员
她的男孩1 天前
Spring Boot 接 Flowable 工作流:用 3 个注解搭一个请假审批流程
java·后端·架构