线上问题定位+JVM核心面试题全解析

在后端开发面试中,"线上问题定位"和"JVM相关"是高频必考考点,也是实际工作中不可或缺的核心能力。很多开发者面对线上接口报错、CPU飙高、OOM等问题时手足无措,对JVM底层原理更是一知半解,导致面试失利或工作中踩坑。本文将结合实际工作场景,详细拆解线上问题定位全流程,同步梳理43道JVM核心面试题(含通俗解析+实操细节),帮你吃透知识点,从容应对面试与工作难题。

第01章:线上问题定位全流程

项目上线后出现问题,核心排查原则是:先定位问题根源,再解决问题,最后验证迭代。根据是否有运维人员,排查流程分为两种情况,重点掌握无运维场景下的实操方法(面试重点考察)。

1.1 有运维人员的排查流程(简单场景)

运维人员配合开发人员,拉取项目运行日志 → 开发人员结合日志与本地源码,分析定位问题 → 修复源码 → 运维人员部署迭代,验证问题是否解决。

1.2 无运维人员的排查流程(核心重点)

开发人员需直接操作生产服务器,根据问题类型(接口报错、RT超时、CPU飙高、OOM、死锁等),采用对应排查方案,不同问题的排查思路差异较大,以下是高频问题的详细实操步骤。

1.2.1 接口报错 + RT超时(request timeout)

最常见的线上问题,核心排查核心是"日志分析",分单机和分布式两种场景:

  1. 单机项目排查:直接进入服务器,排查项目运行日志。优先查看tomcat的logs目录,或项目自身的logs文件夹,找到报错信息(如异常堆栈、接口调用失败详情),结合本地源码定位问题(如参数错误、数据库连接异常、接口逻辑bug),修复后重新部署迭代。

  2. 分布式项目排查:需依赖日志采集系统,核心掌握两种主流架构(面试必问):

    1. Spring Boot Admin 监控架构:微服务集成Spring Boot Admin Client,Client会定时将微服务的CPU使用率、线程信息、内存信息及运行日志,汇报给Spring Boot Admin Server,开发人员通过Server可视化界面,统一查看所有微服务的日志与监控数据,快速定位问题。

    2. ELK 监控架构:ELK是ElasticSearch(日志存储)、Logstash(日志采集)、Kibana(可视化展示)的组合,主流两种架构:

      • 架构一(简单易上手):Logstash部署在各个节点,采集日志并过滤分析,直接发送给ElasticSearch存储,Kibana提供Web界面查询日志、生成报表。优点:搭建简单;缺点:Logstash耗资源高,无消息队列缓存,存在数据丢失风险。

      • 架构二(生产常用):引入Kafka/Redis作为消息队列,Logstash Agent先将日志发送到消息队列,再由Logstash从队列中读取日志、过滤分析,最终发送给ElasticSearch。优点:避免数据丢失(即使Logstash故障,日志先存在队列中);缺点:搭建复杂度略高。

1.2.2 无报错,但程序卡死(CPU飙高、死锁)

此类问题隐蔽性强,核心是"排查线程状态",掌握原生工具和Arthas工具的使用(面试高频实操考点)。

(1)CPU飙高问题排查
方式1:原生工具(jdk自带,无需额外安装)
  1. 通过 top 命令,查看系统资源占用情况,确认CPU占用率是否过高(通常超过80%需重点关注)。

  2. 通过 ps -ef | grep java 命令,查询目标Java进程的PID(进程号)。

  3. 通过 top -H -p PID 命令,查看该进程下所有线程的CPU占用情况,找到占用CPU最高的线程ID(tid)。

  4. (可选)将线程ID从十进制转为十六进制(命令:printf "%x" tid),用于后续定位线程堆栈(非必需,jstack可直接识别十进制tid)。

  5. 通过 jstack -l PID 命令,查看进程的线程堆栈信息,结合线程ID,找到对应的线程逻辑,定位CPU飙高的原因(如死循环、频繁GC)。

注意:开发时需给每个线程分配明确的线程名称,便于快速定位线程对应的业务逻辑。

方式2:Arthas工具(阿尔萨斯,阿里开源,高效排查)

Arthas是线上问题排查的"神器",无需重启项目,可实时查看线程、内存、日志等信息,步骤如下:

  1. 下载安装:官网地址(https://arthas.aliyun.com/doc/download.html),解压后得到arthas-boot.jar。

  2. 启动Arthas:进入解压目录,执行命令 java -jar arthas-boot.jar,会列出当前所有Java进程。

  3. 选择目标进程:输入进程对应的序号(如1),回车,即可附着到该进程。

  4. 排查CPU飙高:执行 thread 命令,查看所有线程的CPU占用情况,找到占用率最高的线程;结合 thread -n 5(查看CPU占用前5的线程),定位线程对应的业务代码,分析问题原因。

(2)死锁问题排查

死锁定义:两个或多个线程互相持有对方所需的资源,导致所有线程处于等待状态,无法继续执行(面试常考定义)。

第一步:死锁演示(理解核心场景)

通过简单代码演示死锁场景,帮助理解排查逻辑:

java 复制代码
// 锁对象接口
public interface MyLock {
    public static final Object R1 = new Object();
    public static final Object R2 = new Object();
}

// 死锁线程
public class DeadThread extends Thread {
    private boolean flag;
    public DeadThread(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            synchronized (MyLock.R1) {
                System.out.println(Thread.currentThread().getName() + "---获取到了R1锁,申请R2锁....");
                synchronized (MyLock.R2) {
                    System.out.println(Thread.currentThread().getName() + "---获取到了R1锁,获取到了R2锁....");
                }
            }
        } else {
            synchronized (MyLock.R2) {
                System.out.println(Thread.currentThread().getName() + "---获取到了R2锁,申请R1锁....");
                synchronized (MyLock.R1) {
                    System.out.println(Thread.currentThread().getName() + "---获取到了R2锁,获取到了R1锁....");
                }
            }
        }
    }
}

// 测试类
public class DeadThreadDemo1 {
    public static void main(String[] args) {
        DeadThread deadThread1 = new DeadThread(true);
        DeadThread deadThread2 = new DeadThread(false);
        deadThread1.start();
        deadThread2.start();
    }
}

控制台输出:Thread-0---获取到了R1锁,申请R2锁....;Thread-1---获取到了R2锁,申请R1锁....,程序卡死,即为死锁。

第二步:死锁排查方法
方式1:原生工具(jstack,最常用)
  1. 通过 jps 命令,获取死锁程序的PID(如8144)。

  2. 执行 jstack -l 8144 命令,查看线程堆栈信息,会明确提示"Found 1 deadlock",并列出每个线程持有的锁和等待的锁,从而定位死锁代码。

方式2:Arthas工具
  1. 启动Arthas,附着到目标进程(步骤同CPU排查)。

  2. 执行 thread 命令,查看线程状态,找到状态为BLOCKED(阻塞)的线程。

  3. 执行 thread -b 命令,直接检测死锁,打印死锁线程的堆栈信息和锁持有情况。

  4. 通过 jad 全类名(如 jad com.atguigu.cloud.demo.DeadThread)反编译代码,定位死锁代码,进行修复(如调整锁的获取顺序)。

1.2.3 OOM异常(内存溢出,面试重中之重)

OOM(java.lang.OutOfMemoryError)是线上致命问题,核心是"内存不足",但不同类型的OOM,排查思路不同,先明确OOM的常见类型,再掌握定位流程。

(1)OOM的4种常见类型(面试必背)
  1. Java heap space:堆内存溢出。Full GC后,堆内存仍无法容纳新对象,无法扩展堆内存时触发(最常见)。

  2. PermGen space:永久代溢出(JDK7及之前)。方法区加载的类过多(如框架动态生成大量类)。

  3. Metaspace:元空间溢出(JDK8及之后)。替代永久代,本质是方法区溢出,原因同永久代。

  4. Direct Memory space:直接内存(堆外内存)溢出。JVM不管理直接内存,需手动释放(如NIO使用不当)。

补充:方法区溢出的核心原因------框架动态生成类过多,比如Mybatis调用Mapper接口、AOP动态代理、Feign接口调用等场景,若配置不当,会生成大量动态类,导致方法区溢出。

(2)OOM问题定位全流程(实操步骤,面试必问)
  1. 第一步:生成OOM异常日志文件 在JVM启动参数中添加配置,让OOM发生时自动生成堆转储文件(.hprof格式),命令:-Xms30m -Xmx30m -XX:+HeapDumpOnOutOfMemoryError。参数说明:-Xms(初始堆大小)、-Xmx(最大堆大小),设置较小的堆大小,便于快速复现OOM;-XX:+HeapDumpOnOutOfMemoryError,OOM时自动生成堆转储文件。

  2. 第二步:分析堆转储文件常用分析工具(二选一即可,面试需说出工具名称):

    1. IDEA直接打开:将.hprof文件拖入IDEA,IDEA会自动解析,展示内存使用情况、大对象、对象引用关系等。

    2. JDK自带工具:jvisualvm(JDK/bin目录下),打开后导入.hprof文件,进行内存分析。

    3. 专业工具:Eclipse Memory Analyzer(MAT)、YourKit Java Profiler,适合复杂场景的内存分析。

  3. 第三步:定位问题并优化分析流程:收集OOM日志 → 导入堆转储文件 → 查看内存占用Top对象 → 排查对象引用关系(是否存在内存泄漏) → 优化代码或调整JVM参数。核心优化方向:减少大对象创建、优化数据结构、调整缓存策略、增加堆内存大小(-Xms和-Xmx)、修复内存泄漏(如清理无用的静态集合引用)。

第02章:JVM核心面试题全解析(43道,覆盖所有高频考点)

以下面试题均为大厂高频考点,解析采用"口语化+核心重点"模式,避免晦涩难懂,便于记忆和面试表达,重点题目标红强调。

一、JVM基础架构(3道)

1. 说一下JVM由哪些部分组成,运行流程是什么?

核心组成(4部分,必背):类加载器、运行时数据区、执行引擎、本地接口。

运行流程(口语化,好记):.java文件 → javac编译 → .class字节码文件 → 类加载器加载字节码到运行时数据区(方法区) → 执行引擎将字节码翻译成机器码 → 本地接口调用native方法(C/C++) → CPU执行机器码。

2. 说一下JVM运行时数据区?

核心:5个区域,3个线程私有,2个线程共享(必背,面试高频)。

  • 线程私有(线程创建则存在,线程结束则销毁):

    • 程序计数器:记录当前线程执行的字节码行号,线程切换时用于恢复执行位置,唯一不会出现OOM的区域

    • Java虚拟机栈:描述Java方法执行过程,每个方法对应一个栈帧(存局部变量、操作数栈等),方法执行完栈帧销毁,递归过深会报StackOverflowError。

    • 本地方法栈:和虚拟机栈功能一致,区别是服务于native方法(C/C++编写)。

  • 线程共享(JVM启动则存在,关闭则销毁):

    • Java堆:最大的内存区域,存所有对象实例和数组,是GC的主要区域(GC堆),可通过-Xms、-Xmx调整大小,满了会报OOM。

    • 方法区:存加载的类信息、静态变量、常量、编译后的代码(非堆),JDK8后用元空间替代永久代,满了会报OOM。

3. 你听过直接内存吗?

听过,直接内存(堆外内存),不是运行时数据区的一部分,JVM不自动管理,需手动释放(如NIO使用后需关闭资源)。

特点:独立于JVM内存,可避免堆内存不足的问题,但使用不当会导致直接内存溢出,且不会被GC自动回收,容易造成内存泄漏。

二、运行时数据区细节(5道,重点)

4. 详细介绍下程序计数器?(重点)

核心:一块极小的内存区域,核心作用是记录当前线程正在执行的字节码指令地址(行号)。

为什么需要?JVM多线程是CPU轮流调度,线程切换时,CPU需要知道上次执行到哪里,程序计数器为每个线程单独存在,互不干扰,切换后可恢复执行位置。

关键考点:唯一不会出现OOM的JVM区域。

5. 详细介绍下Java虚拟机栈?(重点)

线程私有,与线程生命周期一致,核心作用是描述Java方法的执行过程,基本单位是栈帧。

栈帧包含4部分(必背):

  • 局部变量表:存方法内的局部变量(基本类型、对象引用地址)。

  • 操作数栈:用于执行运算(如1+1,先入栈1和1,计算后出栈结果)。

  • 动态链接:存方法引用地址,用于调用其他方法(如service.add())。

  • 方法出口:记录方法执行完后,回到调用者的位置(return或异常退出)。

补充面试题:递归为什么会栈溢出?因为递归会不断创建新栈帧,栈容量有限,栈帧满了就报StackOverflowError。

6. 详细介绍下Java堆?(重点)

线程共享,JVM启动时创建,是内存最大的区域,核心作用是存对象实例和数组(几乎所有对象都在这里分配)。

关键细节:

  • 别名"GC堆":垃圾回收器主要回收这里的内存。

  • 分区:为了高效GC,分为新生代(1/3)和老年代(2/3),新生代又分Eden(8)、From Survivor(1)、To Survivor(1)。

  • 参数调整:-Xms(初始堆)、-Xmx(最大堆),建议两者设为一致,避免频繁扩展堆影响性能。

  • OOM场景:堆内存不足,无法分配新对象,报Java heap space。

7. 解释一下本地方法栈?

和Java虚拟机栈功能完全一致,唯一区别:服务的对象不同。

虚拟机栈服务于Java方法(我们写的Java代码),本地方法栈服务于native关键字修饰的本地方法(C/C++编写,无Java源码),用于存储本地方法的局部变量、操作数等执行信息。

8. 详细解释一下方法区(重点)

线程共享,又称"非堆",核心作用是存"类相关"的信息,不存对象实例(对象在堆里)。

存储内容(必背):加载的类信息(类名、方法、字段)、静态变量(static修饰)、常量(String常量)、即时编译器编译后的代码。

关键考点:JDK8后,方法区由"元空间"替代"永久代",元空间不在JVM内存中,而是使用本地内存,默认无上限(可通过-XX:MaxMetaspaceSize限制)。

三、内存泄漏与OOM(2道,高频)

9. Java会存在内存泄漏吗?请说明为什么?

会存在,虽然Java有GC自动回收垃圾,但GC无法回收"被引用但无用的对象"。

核心原因:长生命周期对象持有短生命周期对象的引用,导致短生命周期对象无用后,仍被引用,GC无法识别为垃圾,长期占用内存,形成内存泄漏。

示例:静态集合(长生命周期)中存入临时对象(短生命周期),临时对象用完后未从集合中移除,集合一直引用,导致临时对象无法被GC回收。

10. 什么是OOM?常见的OOM类型有哪些?(同线上问题定位部分,重点背诵)

OOM:java.lang.OutOfMemoryError,即内存溢出,指JVM无法申请到足够的内存,并非仅指堆溢出。

4种常见类型(必背):Java heap space、PermGen space、Metaspace、Direct Memory space(详细说明见线上问题定位1.2.3)。

四、垃圾回收(GC)相关(12道,重中之重)

11. 简述Java垃圾回收机制

核心:JVM自动回收"无用对象"(垃圾)的过程,无需程序员手动释放内存。

流程:GC线程(低优先级)监控所有对象 → 标记无用对象(不可达对象) → 回收无用对象的内存 → 释放内存供新对象使用。

触发时机:JVM空闲时、堆内存不足时。

12. GC是什么?为什么要GC?

GC:Garbage Collection,垃圾回收,即JVM自动回收无用对象、释放内存的过程。

为什么需要GC?手动管理内存易出错(忘记释放、释放错误),导致内存泄漏、程序崩溃;Java无手动释放内存的方法,依赖GC保证程序稳定运行。

13. 垃圾回收器的原理是什么?有什么办法手动进行垃圾回收?

核心原理:GC监控所有对象的引用关系,用"有向图"记录引用链,区分可达对象(正在使用)和不可达对象(垃圾),触发GC时回收不可达对象。

手动触发GC:调用System.gc()方法,但仅为"通知"GC工作,不强制执行(GC执行时机由JVM决定)。

14. JVM中都有哪些引用类型?(4种,必背)

按强度从强到弱排序,口语化解释,便于记忆:

  1. 强引用:平时new对象(如Object obj = new Object()),只要有强引用,即使内存不足,JVM也不回收,宁愿报OOM。

  2. 软引用:SoftReference实现,内存足够时不回收,内存不足时回收(如浏览器后退按钮缓存)。

  3. 弱引用:WeakReference实现,无论内存是否足够,GC扫描到就回收,生命周期短。

  4. 虚引用:PhantomReference实现,和无引用几乎一致,用于跟踪对象回收过程,几乎不用。

15. 怎么判断对象是否可以被回收?(2种方法,重点)

  1. 引用计数器法(已淘汰):给对象加计数器,引用+1,释放-1,计数器为0则可回收。缺点:无法解决循环引用(A引用B,B引用A,计数器均为1,GC无法识别)。

  2. 可达性分析算法(主流):从GC Roots(线程栈局部变量、静态变量、常量)出发,搜索引用链,无引用链连接的对象,即为不可达对象,可回收,能解决循环引用。

16. JVM垃圾回收算法有哪些?(4种,必背)

  1. 标记-清除算法(基础):标记垃圾 → 一次性清除。优点:简单;缺点:效率低、产生内存碎片。

  2. 复制算法(新生代用):内存分成两块,用一块存对象,GC时复制存活对象到另一块,清空当前块。优点:效率高、无碎片;缺点:内存利用率低(仅用一半)。

  3. 标记-整理算法(老年代用):标记垃圾 → 移动存活对象到内存一端 → 清除另一端垃圾。优点:无碎片;缺点:效率低于复制算法。

  4. 分代算法(生产常用):按对象存活时间分区域(新生代+老年代),新生代用复制算法,老年代用标记-整理算法,兼顾效率和内存利用率。

17. 讲一下新生代、老年代、永久代的区别(重点)

核心:新生代、老年代属于Java堆(存对象);永久代(JDK7)/元空间(JDK8)属于方法区(存类信息)。

  1. 新生代:存新创建的对象,朝生夕死(存活率低),分Eden(8)、From(1)、To(1),GC用复制算法,频繁触发Minor GC。

  2. 老年代:存存活时间长的对象(经过多次Minor GC),存活率高,GC用标记-整理算法,触发Major GC(不频繁)。

  3. 永久代/元空间:存类信息、静态变量,JDK8用元空间替代永久代,使用本地内存,不易溢出。

18. Minor GC、Major GC、Full GC是什么?(重点)

  1. Minor GC:新生代GC,回收Eden+Survivor区,频繁、速度快,用复制算法。

  2. Major GC:老年代GC,回收老年代,速度慢,通常触发前先触发Minor GC。

  3. Full GC:全堆GC,回收新生代+老年代+方法区,速度最慢,会导致程序停顿,尽量避免频繁触发。

19. Minor GC、Major GC、Full GC的触发条件(重点)

  1. Minor GC:Eden区满,或新对象大于Eden剩余空间。

  2. Major GC/Full GC(基本等价):

    1. 晋升到老年代的对象平均大小,超过老年代剩余空间;

    2. Minor GC后,存活对象超过老年代剩余空间;

    3. 方法区(元空间)空间不足;

    4. 手动调用System.gc();

    5. 大对象直接进入老年代,老年代存不下。

20. 为什么新生代要分Eden和两个Survivor区域?(重点)

核心目的:减少进入老年代的对象,避免频繁触发Full GC(Full GC速度慢,影响性能)。

原理:新对象先放Eden,Minor GC后存活对象移到Survivor From,下次Minor GC后,Eden+From的存活对象移到To,循环往复;只有经过15次(默认)Minor GC还存活的对象,才进入老年代,减少老年代压力。

21. Java堆老年代和新生代的默认比例?(必背)

  1. 新生代:老年代 = 1:2(-XX:NewRatio=2修改)。

  2. 新生代内部:Eden:From:To = 8:1:1(-XX:SurvivorRatio=8修改)。

补充:新生代实际可用内存 = Eden + 一个Survivor(90%),另一个Survivor空闲,用于复制存活对象。

22. 为什么要分代?

核心:根据对象存活周期,采用最合适的GC算法,兼顾效率和内存利用率(因材施教)。

新生代对象存活率低,用复制算法(效率高);老年代对象存活率高,用标记-整理算法(无碎片),减少GC对程序性能的影响。

五、垃圾回收器(3道,高频)

23. 说一下JVM有哪些垃圾回收器?(分类记忆)

  1. 新生代回收器(只回收新生代):Serial(串行,单线程)、ParNew(并行,多线程,配合CMS)、Parallel Scavenge(并行,追求高吞吐量)。

  2. 老年代回收器(只回收老年代):Serial Old(串行,单线程)、Parallel Old(并行,追求吞吐量)、CMS(并发,追求短停顿)。

  3. 全堆回收器(回收新生代+老年代):G1(垃圾优先,JDK1.9默认,兼顾吞吐量和停顿)。

24. 新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?

区别核心3点(必背):

  1. 回收区域不同:新生代回收器只回收新生代,老年代只回收老年代。

  2. 算法不同:新生代用复制算法,老年代用标记-清除/标记-整理算法。

  3. 核心目标不同:新生代追求效率(快),老年代追求吞吐量(Parallel Old)或短停顿(CMS)。

25. 简述分代垃圾回收器是怎么工作的?

流程(口语化,好记):

  1. 堆分新生代(1/3)和老年代(2/3),新生代分Eden、From、To。

  2. 新对象优先分配到Eden,Eden满触发Minor GC,存活对象移到From,清空Eden。

  3. 下次Minor GC,Eden+From的存活对象移到To,清空Eden和From,From和To交换身份。

  4. 对象每经历一次Minor GC,年龄+1,达到15(默认)晋升到老年代。

  5. 老年代满触发Major GC/Full GC,用标记-整理算法回收垃圾。

六、内存分配与回收策略(3道)

26. 简述java内存分配与回收策略以及Minor GC和Major GC

内存分配策略(3个核心):

  1. 大部分对象优先分配到新生代Eden区。

  2. 大对象直接分配到老年代(无需经过新生代)。

  3. 长期存活对象(多次Minor GC后)晋升到老年代。

Minor GC/Major GC(简化记):Minor GC(新生代,频繁快)、Major GC(老年代,不频繁慢)、Full GC(全堆,尽量避免)。

27. 对象优先在Eden区分配

核心:new的对象,绝大多数先分配到Eden区;Eden满触发Minor GC,GC后仍无足够空间,启用分配担保,在老年代分配内存。

28. 长期存活对象将进入老年代

JVM给每个对象加年龄计数器:

  1. 对象在Eden出生,Minor GC后存活,进入Survivor,年龄设为1。

  2. 每经历一次Minor GC,存活则年龄+1。

  3. 年龄达到15(默认,-XX:MaxTenuringThreshold修改),晋升到老年代。

七、类加载机制(5道,高频)

29. 简述java类加载机制?

核心:JVM将.class字节码文件加载到内存,经过一系列处理,转换成可使用的Java类型的过程,核心步骤:加载 → 连接 → 初始化。

30. 类加载的机制及过程(重点)

  1. 加载:类加载器找到.class文件(本地、Jar包等),读入内存,在堆生成Class对象(访问方法区的入口),将静态数据转为方法区运行时数据结构。

  2. 连接(3小步):

    1. 验证:检查.class文件合法性、安全性,确保符合JVM规范。

    2. 准备:给类变量(static)分配内存(方法区),设置默认初始值(如int=0,String=null),不执行用户设置的初始值。

    3. 解析:将常量池的符号引用(类名、方法名字符串)替换为直接引用(内存地址)。

  3. 初始化:执行类构造器方法(合并类变量赋值和静态代码块),先初始化父类,再初始化子类。

31. 描述一下JVM加载Class文件的原理机制

核心:类必须被类加载器加载到JVM,才能运行;类加载器负责读取.class字节流,转换为JVM可识别的格式。

加载方式(2种):

  1. 隐式装载:new对象、调用静态方法时,JVM自动加载类(如new User())。

  2. 显式装载:手动加载(如Class.forName("全类名"),常用于反射、JDBC驱动加载)。

补充:类加载是动态的,用到时才加载,节省内存。

32. 什么是类加载器,类加载器有哪些?(重点)

类加载器:通过类的全限定名,获取.class字节流的工具,负责加载类。

4种类加载器(自上而下,父到子):

  1. 引导类加载器(Bootstrap):C++实现,加载JRE/lib核心类库(如java.lang.String),无法被Java代码访问。

  2. 扩展类加载器(Extension):加载JRE/lib/ext目录下的扩展Jar包,父加载器是引导类加载器。

  3. 应用程序类加载器(Application):系统类加载器,加载ClassPath下的自定义类,父加载器是扩展类加载器,平时使用的就是这个。

  4. 自定义类加载器:继承ClassLoader,加载自定义路径下的类(如加密的.class文件)。

33. 什么是双亲委派模型,怎么打破双亲委派模型?(重点,面试高频)

双亲委派模型:类加载器加载类时的规则,"双亲"指父加载器和子加载器(非继承关系)。

规则(必背):子加载器收到加载请求,不自己加载,先交给父加载器,依次向上传递到引导类加载器;父加载器能加载则加载,不能加载则子加载器自己尝试加载。

目的:保证类的唯一性(如java.lang.String只能由引导类加载器加载),避免核心类被篡改,保证安全。

打破方式:继承ClassLoader类,重写loadClass方法和findClass方法(双亲委派逻辑在loadClass中,重写后改变委派规则)。

示例:Tomcat的类加载器,打破双亲委派,用于加载不同Web应用的类,避免类冲突。

八、JVM调优与实操(5道,重点)

34. 常用的JVM调优的参数都有哪些?(必背,实操考点)

  1. 堆内存相关(最常用):

    1. -Xms:初始堆大小(如-Xms2g),建议与-Xmx一致。

    2. -Xmx:最大堆大小(如-Xmx4g)。

    3. -Xmn:新生代大小(如-Xmn1g)。

    4. -XX:NewRatio=n:新生代:老年代比例(如-XX:NewRatio=3 → 1:3)。

    5. -XX:SurvivorRatio=n:Eden:Survivor比例(默认8 → 8:1:1)。

  2. 栈内存相关:

    1. -Xss:每个线程栈大小(默认1M,如-Xss512k,减小可创建更多线程)。
  3. 方法区相关:

    1. JDK7:-XX:PermSize、-XX:MaxPermSize(永久代大小)。

    2. JDK8:-XX:MetaspaceSize、-XX:MaxMetaspaceSize(元空间大小)。

  4. 其他常用:

    1. -XX:MaxTenuringThreshold=n:对象晋升老年代的年龄(默认15)。

    2. -XX:+PrintGC:打印GC日志。

    3. -XX:+PrintGCDetails:打印详细GC日志(排查问题用)。

35. JVM的GC收集器设置(实操)

通过"-XX:+UseXXXGC"指定GC收集器,常用:

  • -XX:+UseSerialGC:串行收集器(新生代,单线程,小型程序)。

  • -XX:+UseParNewGC:ParNew收集器(新生代,多线程,配合CMS)。

  • -XX:+UseParallelGC:Parallel Scavenge(新生代,追求高吞吐量)。

  • -XX:+UseParallelOldGC:Parallel Old(老年代,追求吞吐量)。

  • -XX:+UseConcMarkSweepGC:CMS(老年代,追求短停顿,交互类程序)。

  • -XX:+UseG1GC:G1收集器(全堆,JDK1.9默认,兼顾吞吐量和停顿)。

36. JVM内存模型的相关知识了解多少?(主内存、工作内存、重排序、内存屏障、happen-before)

  1. 主内存和工作内存(核心):

    1. 主内存:存所有变量(静态、实例变量),线程共享。

    2. 工作内存:每个线程私有,存主内存变量的副本,线程操作变量必须在工作内存中进行,线程间通信需通过主内存。

  2. 指令重排序:CPU为提高效率,打乱代码执行顺序(单线程不影响结果,多线程可能出错)。

  3. 内存屏障:禁止重排序,保证内存可见性(4种,无需记细节,知道作用即可)。

  4. happen-before原则:先行发生,A happen-before B → A的结果能被B看到,JVM保证可见性,常用5个:

    1. 单线程:前操作happen-before后操作。

    2. 锁:解锁happen-before加锁。

    3. volatile:写操作happen-before所有读操作。

    4. 传递性:A→B,B→C,则A→C。

    5. 线程启动:start() happen-before线程内操作。

37. 怎么打出线程栈信息?(实操,面试必问)

  1. 获取Java进程PID:执行jps命令,列出所有Java进程,前面的数字是PID。

  2. (可选)查看线程CPU占用:top -Hp PID,找到占用CPU高的线程ID。

  3. 打印线程栈:jstack PID,控制台直接显示线程栈信息。

  4. 保存到文件:jstack -l PID > /tmp/thread.txt,便于后续分析。

补充:可通过fastthread.io等在线工具,上传线程栈文件,自动分析死锁、线程阻塞等问题。

38. 堆栈的区别是什么?(必背,高频)

这里的堆=Java堆,栈=Java虚拟机栈,核心4点区别:

  1. 共享性:堆线程共享,栈线程私有。

  2. 存储内容:堆存对象实例和数组,栈存方法栈帧(局部变量、操作数等)。

  3. 生命周期:堆随JVM启动/关闭,栈随线程启动/结束。

  4. 内存管理:堆是GC主要区域(自动回收),栈帧随方法执行完自动销毁(无需GC)。

补充:静态变量在方法区,不在堆和栈;静态对象引用在方法区,对象本身在堆。

第03章:总结与核心技巧

本文整合了线上问题定位全流程(接口报错、CPU飙高、死锁、OOM)和43道JVM核心面试题,覆盖高频考点,技巧:

  1. 线上问题定位:重点掌握"日志分析"和"工具使用"(jstack、Arthas),回答时要体现"实操步骤",不要只说理论。

  2. JVM面试:核心背诵"运行时数据区、GC算法、分代模型、类加载机制",回答时用口语化表述,结合实际场景(如OOM排查、GC调优),体现实战能力。

  3. 高频考点:OOM类型及排查、GC算法、分代模型、双亲委派模型、JVM调优参数、线程栈打印,这些是必背内容,务必熟练掌握。

相关推荐
爱丽_4 小时前
JVM 堆参数怎么设:先建立内存基线,再谈性能优化
java·jvm·性能优化
qq_334903154 小时前
使用Flask快速搭建轻量级Web应用
jvm·数据库·python
小小怪7505 小时前
将Python Web应用部署到服务器(Docker + Nginx)
jvm·数据库·python
2401_844221325 小时前
使用PictureBox实现图片缩放与显示的深入探讨
jvm·数据库·python·算法
NGC_66115 小时前
CMS收集器详解
java·开发语言·jvm
左左右右左右摇晃5 小时前
JVM 整理(二) 类加载器
jvm·笔记
左左右右左右摇晃6 小时前
JVM 整理(五) 垃圾回收(GC)
jvm·笔记
闻哥7 小时前
深入理解 MySQL InnoDB Buffer Pool 的 LRU 冷热数据机制
android·java·jvm·spring boot·mysql·adb·面试
左左右右左右摇晃7 小时前
JVM 整理(三) 方法区+虚拟机栈
jvm·笔记