十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JVM与MySQL面试全链路:类加载、GC、事务与索引一次讲透

JVM与MySQL面试全链路:类加载、GC、事务与索引一次讲透 很多准备 Java 面试的同学都有一份 JVM 面试题清单比如“类加载过程有几步”“GC Roots 有哪些”“CMS 和 G1 有什么区别”背得滚瓜烂熟。但真到了面试现场面试官换一种问法比如“你线上 JVM 老年代占用过高怎么排查”“这个对象到底什么时候能被回收”很多人就卡住了。原因不是背得不够多而是没有把知识点串成链路。JVM 面试真正的考察重点不是让你报菜名式地列出机制名称而是看你能不能把“类加载 — 内存模型 — GC 判定 — 垃圾回收器 — 性能调优”这条技术链路讲通。MySQL 面试也是同理事务、索引、日志、锁、MVCC 是一套互相咬合的逻辑而不是五个孤立的知识点。这篇文章会把 JVM 面试和 MySQL 面试中的高频考点按照“先建立链路、再逐点击破”的方式梳理一遍。文章里的示例代码和排查命令你可以直接拿到本地环境验证。目标是让你读完以后不再靠零散记忆应对面试而是能用自己的话把整套机制讲清楚。1. 先把“JVM 是什么”这件事说清楚面试官问“JVM 是什么”并不是在考定义而是在确认你有没有把 Java 的运行机制放在整个 JDK 体系里理解。JVM 的全称是 Java Virtual Machine也就是 Java 虚拟机。它是一个可以执行 Java 字节码的虚拟机进程。我们写的.java源文件经过javac编译之后得到.class字节码文件JVM 负责把字节码解释或编译成当前操作系统能识别的机器指令然后在真实硬件上运行。和 C/C 这类直接编译成平台相关机器码的语言不同Java 通过 JVM 实现了“一次编写、到处运行”。同一个.class文件放到 Windows、Linux、macOS 上的 JVM 里都能运行前提是每个平台都安装了对应该平台的 JVM。这里顺带把三个高频概念区分开JDKJava Development Kit是 Java 开发工具包包含 JRE、编译器、调试工具等。JREJava Runtime Environment是 Java 运行环境包含 JVM 和核心类库。JVMJava 虚拟机是 JRE 的一部分负责执行字节码。用一句话概括三者的关系JDK 是给开发者用的JRE 是给运行 Java 程序的人用的JVM 是真正干活的那个进程。在面试中你还可以补充一个细节JVM 不是只有 HotSpot 一种实现。除了 Oracle 的 HotSpot还有 Eclipse OpenJ9、GraalVM 等实现。目前绝大多数线上 Java 应用使用的还是 HotSpot所以后面聊到的内存模型和 GC 机制默认都以 HotSpot 为背景。从材料看很多初学者会把 JVM、JRE、JDK 混为一谈甚至在配置环境变量时因为JAVA_HOME指错了层级导致命令行工具找不到java或javac。这个坑在后面的“常见问题排查”部分会专门讲。如果你能在一个面试题的回答里主动把 JDK、JRE、JVM 三者的边界画清楚就已经比只会背定义的人高出一截。2. 类加载机制从字节码到 Class 对象面试必追的三个问题类加载机制是 JVM 面试的第一个深水区。面试官通常会从一个简单问题切入一个 Java 类从被加载到能被使用经历了什么完整的答案是一个类从字节码文件到被 JVM 使用需要经历加载、验证、准备、解析、初始化五个阶段后面还有使用和卸载。其中解析阶段在部分情况下可以推迟到初始化之后再开始这是为了支持 Java 的动态绑定。2.1 五个阶段分别做了什么加载阶段类加载器根据类的全限定名读取.class文件的二进制字节流把字节流中的静态存储结构转换为方法区或元空间中的运行时数据结构并在堆中生成一个代表该类的Class对象作为访问方法区数据结构的入口。验证阶段JVM 检查字节码是否符合 Java 规范。比如class文件的魔数是否是0xCAFEBABE常量池中的类型是否正确字节码指令是否安全。验证是 JVM 自我保护的关键环节防止恶意或损坏的字节码破坏 JVM。准备阶段JVM 为类的静态变量分配内存并设置默认零值。比如private static int count 10;在这个阶段count的值是 0而不是 10。真正把 10 赋给count要等到初始化阶段执行clinit方法时才会发生。解析阶段JVM 将常量池中的符号引用替换为直接引用。简单说符号引用是“一个字符串形式的类名或方法名”直接引用是指向真实内存地址的指针或偏移量。初始化阶段JVM 执行类构造器clinit方法静态变量被赋予程序员指定的初始值静态代码块被执行。2.2 双亲委派模型为什么重要类加载器分为四层Bootstrap ClassLoader负责加载 JVM 自身需要的核心类比如rt.jar或 JDK 9 的java.base模块。Platform ClassLoaderJDK 8 中叫 Extension ClassLoader负责加载扩展类。Application ClassLoader也叫系统类加载器负责加载 classpath 下的用户类。自定义 ClassLoader用户通过继承ClassLoader实现的加载器常用于热部署、框架隔离等场景。双亲委派模型的工作机制是当一个类加载器收到加载请求时它不会自己先去加载而是把请求委派给父加载器每一层都是如此因此所有加载请求最终都会传到顶层 Bootstrap ClassLoader。只有父加载器反馈无法完成加载时子加载器才会自己尝试加载。用一段伪代码理解// 文件路径src/main/java/com/example/classloader/SimpleClassLoader.java public class SimpleClassLoader extends ClassLoader { Override protected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 1. 检查类是否已经加载 Class? c findLoadedClass(name); if (c null) { try { // 2. 如果父加载器不为空先委派给父加载器 if (getParent() ! null) { c getParent().loadClass(name); } else { c getBootstrapClassLoader().loadClass(name); } } catch (ClassNotFoundException e) { // 父加载器无法加载时由自己加载 } if (c null) { c findClass(name); } } if (resolve) { resolveClass(c); } return c; } } }这段代码的核心逻辑就是“先父后子”保证相同的类在 JVM 中只会被加载一次并且优先使用 JDK 核心类。双亲委派解决的问题是防止 Java 核心 API 被篡改。比如有人自定义了一个java.lang.String如果没有双亲委派模型他可能把自己写的错误版本先加载进来有了双亲委派java.lang.String会由 Bootstrap ClassLoader 加载用户自定义的同名类永远不会被加载。面试中经常追问的“如何打破双亲委派”可以从三个场景回答SPI 机制JDBC 等场景下由 Bootstrap ClassLoader 加载的代码需要调用 classpath 下的实现类但父加载器不知道子加载器路径下的类于是引入了线程上下文类加载器来打破委派。Tomcat每个 Web 应用需要隔离自己的类库所以 Tomcat 使用独立的 WebAppClassLoader 优先加载应用目录下的类。热部署OSGi、Arthas 等工具通过自定义类加载器实现同一个类的多次重新加载。2.3 一个常见追问这个类什么时候才初始化面试官会问下面这段代码Value类会不会被初始化// 文件路径src/main/java/com/example/classloader/PasiveReferenceDemo.java public class PasiveReferenceDemo { static class Parent { static int value 42; static { System.out.println(Parent init); } } static class Child extends Parent { static { System.out.println(Child init); } } public static void main(String[] args) { System.out.println(Child.value); } }运行结果是Parent init 42原因是通过子类访问父类的静态字段只会触发父类的初始化不会触发子类的初始化。这就是“被动引用不触发初始化”的典型例子。类似地通过数组定义来引用类也不会触发初始化引用编译期常量也不会触发初始化因为常量在编译期就已经被写入常量池了。这一小节的关键结论是类加载机制面试不要按五个阶段机械背诵而是要能解释每个阶段解决了什么问题双亲委派保护了什么以及哪些写法会触发或阻止初始化。3. JVM 运行时内存模型别把 JMM 和内存结构搞混JVM 面试里最容易被混淆的两个概念一个是“JVM 运行时数据区”另一个是“Java 内存模型JMM”。前者是 JVM 在运行 Java 程序时把内存划分成几个区域后者是一套跨线程共享变量的规范解决的是多线程并发时的可见性、有序性和原子性问题。面试时如果能把这两个概念主动分清本身就是加分项。3.1 运行时数据区HotSpot JVM 的运行时数据区主要分为以下几块堆存放对象实例是 GC 的主要区域。从内存回收角度堆分为新生代和老年代。虚拟机栈线程私有每个方法调用对应一个栈帧栈帧里存放局部变量表、操作数栈、动态链接、方法出口。本地方法栈线程私有为 native 方法服务。方法区存放类元数据、常量、静态变量等。JDK 8 之后用元空间实现直接使用本地内存。程序计数器线程私有记录当前线程正在执行的字节码指令地址。可以用一句话记忆堆和方法区是线程共享的虚拟机栈、本地方法栈、程序计数器是线程私有的。对象创建后的内存布局也是面试常考。一个 Java 对象在内存中分为三块对象头包含 Mark Word 和 Klass Pointer。Mark Word 存储对象的哈希码、GC 分代年龄、锁状态等信息。实例数据对象真正存储的字段内容。对齐填充仅起占位作用因为 HotSpot 要求对象大小是 8 字节的整数倍。3.2 Java 内存模型JMMJMM 定义了一个抽象关系每个线程都有自己的工作内存线程对共享变量的操作必须先在工作内存中进行然后再同步回主内存。这里的工作内存是 CPU 缓存和寄存器等的抽象并不是真实内存区域。这个模型带来的三个核心问题可见性一个线程修改了变量另一个线程不一定能马上看到因为修改可能还在工作内存中。原子性复合操作比如count不是原子的。有序性编译器和 CPU 可能对指令重排。Java 通过volatile、synchronized、final以及 Happens-Before 规则来保证并发安全。比如volatile的两个语义保证可见性禁止指令重排。面试中一个高频例子是// 文件路径src/main/java/com/example/jmm/SingletonDemo.java public class SingletonDemo { private static volatile SingletonDemo instance; private SingletonDemo() { } public static SingletonDemo getInstance() { if (instance null) { synchronized (SingletonDemo.class) { if (instance null) { instance new SingletonDemo(); } } } return instance; } }这里instance为什么必须加volatile因为new SingletonDemo()不是一个原子操作它分为“分配内存、初始化对象、把引用指向内存”三步。编译器可能重排为“分配内存、把引用指向内存、初始化对象”。如果重排发生另一个线程在判断instance ! null后直接返回拿到的可能是一个尚未初始化完成的对象。volatile禁止了这个重排从而避免拿到半初始化对象。如果把 JVM 面试题列一个高频清单内存模型这一节的重点就是能画出运行时数据区能解释对象创建路径能用volatile单例解释可见性和有序性能区分 JMM 与运行时内存结构。4. 垃圾回收判定引用计数、可达性分析与三色标记GC 相关问题是 JVM 面试中的“分水岭”。前几年面试官喜欢问“怎么判断对象已死”现在更爱问“并发标记阶段如何解决漏标问题”。后者就是三色标记的考点。4.1 对象存活判定算法引用计数法给对象维护一个计数器每被一个地方引用就加 1引用失效就减 1计数器为 0 时判断为可回收。这种方法实现简单但解决不了循环引用问题。比如 A 引用 B、B 引用 A但二者都不再被外部引用此时计数器仍然不为 0对象无法被回收。HotSpot 并没有采用引用计数法。可达性分析从一组称为 GC Roots 的根对象出发通过引用关系遍历能被遍历到的对象是存活对象不能被遍历到的对象是可回收对象。GC Roots 包括虚拟机栈中引用的对象局部变量、参数等。方法区中静态变量引用的对象。方法区中常量引用的对象。本地方法栈中 JNI 引用的对象。活跃线程、锁对象等。4.2 三色标记法在并发标记阶段GC 线程和应用线程同时运行为了不暂停业务线程太久JVM 采用三色标记来跟踪对象状态白色尚未访问到的对象或者访问结束后仍然不可达的对象是潜在的回收目标。灰色当前对象已经被访问到但它引用的其他对象还没有全部被扫描完。黑色当前对象和它引用的所有对象都已经被扫描完。标记过程从 GC Roots 开始把根引用对象标记为灰色然后依次扫描灰色对象把它的引用对象标记为灰色自己标记为黑色直到没有灰色对象为止。三色标记的问题在于并发标记时业务线程会改变引用关系可能出现“漏标”也就是一个本来存活的对象被错误标记成白色最后被回收。漏标需要同时满足两个条件一个黑色对象重新引用了白色对象。所有从灰色对象到该白色对象的引用被破坏。为了解决漏标JVM 有两种思路增量更新CMS 使用的方法。当一个黑色对象新增了一个指向白色对象的引用时把这个黑色对象重新标记为灰色。这样下次扫描时它的引用关系会被重新检查。SATB 快照G1 使用的方法。在并发标记开始时为当时的对象图保存一个快照。当灰色对象到白色对象的引用被删除时把这个引用记录下来保证标记结束时能扫描到这些历史引用指向的对象。面试官如果继续追问“为什么 CMS 用增量更新而 G1 用 SATB”可以回答增量更新思路是“记录新增引用”适合标记-清除算法SATB 思路是“记录被破坏的引用”可以避免维护“黑色对象重新变灰”所带来的后续重新扫描成本对 G1 这种基于 Region 的收集器更友好。与此相关的还有强引用、软引用、弱引用、虚引用四种引用类型。软引用在内存不足时回收弱引用在下一次 GC 时回收虚引用主要用于跟踪对象被回收的状态。这一节的核心结论是判断一个对象是否可回收最底层的逻辑是可达性分析三色标记则是在不停止业务线程的前提下如何进行可达性分析的工程化方案。理解了这个链路GC 题怎么追问都绕不开。5. GC 算法与垃圾回收器从 Serial 到 G1 再到 ZGC如果说三色标记考的是“并发 GC 的正确性”那垃圾回收器考的就是“如何在不同场景下平衡停顿时间和吞吐量”。5.1 三种基础 GC 算法标记-清除先标记出可回收对象然后统一回收。缺点是会产生大量内存碎片后续分配大对象时可能因找不到连续空间而提前触发 GC。标记-复制把内存分成两块只使用其中一块。GC 时把存活对象复制到另一块然后清空原来那块。优点是实现简单、不会产生碎片缺点是浪费一半内存。标记-整理标记存活对象后把所有存活对象向内存一端移动然后清理边界以外的内存。优点是内存连续缺点是移动对象需要更新引用地址停顿时间更长。5.2 常见垃圾回收器Serial单线程收集器GC 时会暂停所有业务线程适合客户端或小内存应用。Parallel多线程并行回收注重高吞吐量。JDK 8 默认的新生代和老年代收集器组合是 Parallel Scavenge Parallel Old。CMSConcurrent Mark Sweep以最小停顿为目标。它的工作流程是初始标记只标记 GC Roots 直接引用的对象需要短暂 STW。并发标记从已标记对象出发进行三色标记在此期间业务线程运行。重新标记修正并发标记期间因业务线程运行而产生变化的标记结果需要 STW。并发清除同时运行业务线程并清除垃圾。CMS 的缺点是内存碎片化严重可能导致并发模式失败Concurrent Mode Failure这时会退化为 Serial Old 的 Full GC停顿时间变长。G1把堆划分为多个大小相等的 Region逻辑上仍然保留新生代和老年代。G1 能做到可预测的停顿时间通过-XX:MaxGCPauseMillis指定期望目标。它使用 SATB 解决并发标记漏标问题通过 Remembered Set 来维护 Region 之间的引用关系避免全堆扫描。ZGC面向大堆低延迟场景JDK 11 引入JDK 15 转正。ZGC 的核心特点是把标记、对象重定位等阶段与业务线程并发执行通过染色指针和读屏障实现极短的 STW 时间且停顿时间不随堆大小线性增长。面试时可以用一张表来组织回答收集器执行方式目标适用场景Serial单线程 STW简单可靠客户端应用、小堆Parallel多线程 STW高吞吐后台计算任务CMS并发标记低停顿互联网业务系统G1Region 化并发可预测停顿JDK 9 的主流服务端选择ZGC并发更多阶段超大堆低延迟大内存、微服务场景一个常见的追问是JDK 8 默认 GC 是什么JDK 8 中默认是 Parallel Scavenge Parallel Old。JDK 9 之后G1 成为默认收集器。如果你在面试时说“我的项目用的 JDK 8想用 G1需要显式加-XX:UseG1GC”这个细节能证明你关注过 JVM 版本差异。6. JVM 性能调优别一上来就改堆大小JVM 调优是面试中的压轴级问题。很多人一听到“调优”就觉得是调大堆内存。实际上JVM 调优的目标取决于业务指标优先降低停顿时间还是优先提高吞吐量或者是解决内存泄漏问题。面试官要的不是你背几个参数而是你有没有一套排查思路。6.1 调优标准流程定位问题先确认现象是 CPU 飙升、接口变慢、频繁 Full GC、还是直接 OOM。收集数据通过 jps 查看 Java 进程jstat 查看 GC 情况jmap 导出堆 dumpjstack 查看线程栈。分析原因结合 GC 日志、堆 dump、线程栈确定是垃圾回收参数不合理、存在大对象、内存泄漏、还是锁竞争导致的问题。调整参数只改一个变量不要同时调多个参数。验证效果压测观察指标确认是否达到目标。一个最基础的 JVM 状态查看命令组合# 查看 Java 进程 jps -l # 查看进程 1234 的 GC 情况每 1 秒输出一次一共输出 5 次 jstat -gc 1234 1000 5 # 查看堆内存使用情况 jmap -heap 1234 # 导出堆 dump生产环境慎用会暂停应用 jmap -dump:live,formatb,fileheap.hprof 1234 # 查看线程栈 jstack 1234 thread_dump.txt # 查看 JVM 参数 jinfo -flags 1234注意jmap 在部分版本中可能不建议在线上随意执行。更稳妥的方案是使用 Arthas 等在线诊断工具或者评估后申请专用窗口操作。6.2 查看 GC 日志JDK 8 及之前常用java -Xloggc:/path/to/gc.log -XX:PrintGCDetails -XX:PrintGCDateStamps -jar app.jarJDK 9 之后统一日志系统推荐这样写java -Xlog:gc*:file/path/to/gc.log:time,uptime,level,tags -jar app.jarGC 日志里需要关注几个指标Young GC 的频率和耗时。Full GC 的频率和耗时。每次 GC 后新生代、老年代的大小变化。晋升失败是否频繁出现。如果发现 Full GC 很频繁通常的排查顺序是先看老年代占用是否持续上涨再用 jmap dump 分析哪些对象占用了大量内存最后定位到具体的业务代码比如未关闭的连接、过大的缓存、静态集合持续增长等。6.3 几个高频 JVM 调优参数-Xms和-Xmx设置堆初始大小和最大值生产环境一般建议设置成相同值避免堆大小动态收缩带来的性能抖动。-XX:NewRatio设置新生代与老年代比例默认 1:2。-XX:SurvivorRatio设置 Eden 区与 Survivor 区比例默认 8:1:1。-XX:MaxMetaspaceSize设置元空间最大值防止元空间无限制增长。-XX:HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath发生 OOM 时自动导出堆 dump对排查内存泄漏非常有用。关于-XX:CompileThreshold它和 JIT 编译相关表示方法被调用多少次后会触发 C1/C2 编译。它在热词中出现是因为部分面试官喜欢问“热点代码是怎么被识别的”。默认情况下C1 编译阈值是 1500C2 编译阈值是 10000。但这个参数不建议随意改小因为过度编译会增加编译线程负担可能得不偿失。6.4 一个调优示例假设一个服务在高峰期每 10 秒发生一次 Full GC每次耗时 2 秒。第一步不是改参数而是先通过jstat -gc确认老年代为什么在短时间内被填满。如果老年代对象数量并不大但每次都在 Full GC就要检查是否有人显式调用了System.gc()。某些框架为了防止堆碎片会主动调用这时可以通过-XX:DisableExplicitGC屏蔽。但要小心如果你用的框架确实依赖显式 GC 来清理堆外内存这个参数反而会造成堆外内存溢出。如果老年代持续增长说明有对象在快速进入老年代。可以看是否创建了大量生命周期长的大对象或者 Survivor 区容纳不下导致对象提前晋升。此时需要调整-Xmn新生代大小或-XX:MaxTenuringThreshold晋升年龄阈值。核心观点是JVM 调优不是堆参数调优而是基于数据的根因定位。面试时你能说出“我先看 GC 日志再看堆 dump最后才考虑改参数”比背出十个-XX参数更有说服力。7. MySQL 高频面试事务、索引、日志与锁一套逻辑打通MySQL 相关内容在 Java 面试中几乎必考。从材料中的热点词来看问题集中在事务、锁表、索引、连接报错、安装配置等方向。MySQL 面试题和 JVM 面试题一样如果只背孤立概念很难应对追问。7.1 事务 ACID 与隔离级别事务的四个特性原子性、一致性、隔离性、持久性。面试官更常问的是MySQL 默认隔离级别是什么为什么MySQL InnoDB 默认隔离级别是“可重复读”。但要注意MySQL 的可重复读通过 MVCC 解决了普通的快照读幻读问题通过间隙锁解决了当前读下的幻读问题所以它实际达到了接近串行化的部分效果。四个隔离级别读未提交一个事务能读到另一个事务未提交的数据存在脏读。读已提交只能读到已提交的数据解决了脏读但可能存在不可重复读。可重复读同一个事务内多次读取相同范围的数据结果一致解决了不可重复读。串行化所有事务串行执行隔离级别最高但并发性能最低。7.2 InnoDB 索引为什么用 B 树B 树的非叶子节点只存储索引值不存储数据所以每个节点可以容纳更多索引项树的高度更低减少磁盘 IO。叶子节点通过双向链表连接非常适合范围查询和排序。聚簇索引与二级索引聚簇索引InnoDB 的主键索引叶子节点直接存储整行数据。二级索引叶子节点存储主键值查询时如果索引列不满足覆盖索引条件会先查到主键再回表查完整行。覆盖索引如果查询所需字段都在索引中就不需要回表。比如SELECT name FROM user WHERE age 20如果建立了(age, name)联合索引这条查询直接走索引就能返回结果。面试中一个非常容易踩坑的点是MySQL 的OR能去重吗答案是不能。OR是逻辑或不会自动去重。如果用了OR查询结果可能包含重复行需要显式使用DISTINCT或改写为UNION。7.3 redo log、undo log 与 binlog这是 MySQL 面试的经典三件套。redo logInnoDB 的物理日志记录的是页层面的修改操作主要用于崩溃恢复保证事务持久性。undo logInnoDB 的逻辑日志记录的是数据修改前的状态用于事务回滚也是 MVCC 版本链的基础。binlogMySQL Server 层的归档日志记录逻辑 SQL用于主从复制和数据恢复。面试高频题为什么需要 redo log 和 binlog 两份日志为什么 redo log 采用两阶段提交这是因为 redo log 属于 InnoDB 引擎层binlog 属于 Server 层两者记录内容不同。如果写了一份后崩溃另一份没写就可能导致主从数据不一致或崩溃恢复后丢失数据。两阶段提交是在写 binlog 前后给 redo log 打两个状态标记确保两份日志达到一致性。7.4 锁与 MVCCInnoDB 锁分为共享锁和排他锁。从锁粒度上分为行锁和表锁。InnoDB 支持行锁但行锁是通过索引实现的如果查询条件没有走索引行锁会退化为表锁这就是线上经常出现“锁表”的原因之一。间隙锁和临键锁在可重复读隔离级别下InnoDB 使用间隙锁Gap Lock和临键锁Next-Key Lock防止幻读。间隙锁锁定的是一个范围临键锁锁定的是记录和它前面的间隙。它们是保证当前读场景下没有幻读的关键机制。MVCC多版本并发控制核心是 undo log 版本链和 ReadView。一个事务启动时生成 ReadView记录当前活跃事务列表。读取数据时根据版本链判断该行对当前事务是否可见。读已提交和可重复读的差异在于 ReadView 的生成时机不同读已提交每次查询都生成新的 ReadView可重复读只在事务第一次查询时生成 ReadView。用一段 SQL 演示事务和隔离级别的基本操作-- 创建演示表 CREATE TABLE account ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(32) DEFAULT NULL, balance DECIMAL(10,2) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB; -- 插入测试数据 INSERT INTO account (name, balance) VALUES (zhangsan, 1000.00); -- 事务 A查询并更新 START TRANSACTION; SELECT balance FROM account WHERE id 1; UPDATE account SET balance balance - 100 WHERE id 1; COMMIT; -- 事务 B观察隔离级别 SELECT transaction_isolation;在实际项目中更新语句如果不带 WHERE 条件或者 WHERE 条件没有走索引就会锁住全表。这也是材料中提到的“mysql update 语法”容易引发事故的原因。生产环境执行 UPDATE 之前必须先用EXPLAIN确认执行计划是否走索引必要时开启事务并做好备份。8. 常见报错与排查思路无论是 JVM 还是 MySQL日常开发和面试中都会遇到一些典型报错。这里把高频问题汇总成一张排查表。问题现象可能原因排查方式解决方案运行 Java 命令提示 no jvm could be found on your systemJDK 未安装或JAVA_HOME未配置执行java -version检查环境变量安装对应版本 JDK配置JAVA_HOME并追加 PATHEclipse 报“找不到或无法加载主类 org.apache.catalina.startup.Bootstrap”Tomcat 运行环境配置错误或 lib 缺失检查项目的运行配置、Tomcat 安装目录和 classpath重新关联本地 Tomcat确认catalina.jar在 classpath 中Java 进程异常重启找不到崩溃原因可能发生 JVM 本地崩溃或 OOM查找hs_err_pid*.log和 GC 日志分析 crash log修复代码或调整 JVM 参数容器中 Java 程序异常重启JVM 日志不明确容器被 OOMKilled或 JVM 参数未指定日志输出dmesg查看内核日志检查 stdout/stderr增加-Xlog:gc*和-XX:HeapDumpOnOutOfMemoryErrorFull GC 频繁接口停顿明显老年代对象持续增长或大对象过多jstat 查看 GC 频率jmap dump 分析对象定位内存泄漏代码调整堆参数和晋升阈值MySQL 启动服务报错端口被占用、数据目录权限不对、配置文件错误查看 MySQL error log检查 3306 端口释放端口调整权限修正配置文件MySQL 锁表业务 SQL 全部等待UPDATE/DELETE 未走索引导致行锁升级表锁SHOW PROCESSLIST查看等待会话通过EXPLAIN优化 SQL让条件走索引JDBC 连接 MySQL 报认证协议不受支持客户端驱动版本与服务端加密规则不兼容查看驱动版本和 MySQL 版本升级 MySQL Connector/J 或调整认证插件线上排查 JVM 时最容易忽略的一步是先确认中间件和容器本身的状态。如果应用跑在 Docker 中JVM 看到的可用 CPU 和内存可能和宿主机不一致尤其要注意容器内存限制与 JVM 堆参数的关系。此时应该使用容器感知的 JVM 参数或者让堆参数略小于容器内存限制避免被 OOM Killer 杀掉。9. 面试答题的底层方法从背题到讲链路最后想给准备面试的人一个建议不要抱着 45 问清单逐条背而是把知识点按链路组织起来。JVM 链路的组织方式是类加载阶段字节码如何变成 Class 对象。内存模型阶段Class 对象和实例对象被放在哪里。GC 判定阶段JVM 如何判断对象要回收。垃圾回收器阶段用什么方式回收停顿多少。调优阶段线上怎么定位和解决 GC 问题。MySQL 链路的组织方式是事务ACID 是什么MySQL 默认隔离级别是什么。日志redo log、undo log、binlog 各解决什么问题。锁与 MVCC并发事务如何保证隔离性。索引SQL 为什么快为什么慢回表怎么避免。故障定位锁表了怎么看慢查询怎么改。回答问题时建议使用“先结论再展开再给场景”的模式。比如面试官问“Full GC 频繁怎么排查”先给结论“我会先通过 jstat 确认 Full GC 频率再结合 GC 日志和堆 dump 分析原因最后调整参数。”然后展开每一步的具体命令和判断标准最后补一个自己实际操作过的典型场景。JVM 面试和 MySQL 面试都不需要所谓的“标准答案”面试官真正想看到的是你能不能把一个现象关联到底层机制再给出可落地的排查路径。把所有知识点串成链路以后45 个问题其实只是这条链路在 45 个入口上的一次次提问。建议先把文中的代码示例和命令在本地环境跑一遍再把这条链路图自己在纸上画一遍。画不出来说明还有薄弱点画得出来面试时心里就有底。建议收藏备用。
返回列表