【P0】JVM面试篇

Java运行时一个类是什么时候被加载的?

《JVM 虚拟机规范》未强制规定类的加载时机,由虚拟机自行实现,如 HotSpot 虚拟机采用按需加载方式,在需要使用类时才进行加载。

JVM一个类的加载过程?

一个类从加载到JVM内存,到JVM内存卸载,他的整个生命周期会经历7个阶段:

  1. 加载(Loading):将类的字节码加载到 JVM 内存
  2. 验证(Verification):校验字节码合法性,确保符合 JVM 规范
  3. 准备(Preparation):为类静态变量分配内存并设置默认初始值
  4. 解析(Resolution):将符号引用转换为直接引用(如类、方法的内存地址)
  5. 初始化(Initialization) :执行类构造器<font style="color:#000000;"><clinit>()</font>方法,初始化静态变量与静态代码块
  6. 使用(Using):类实例化、调用类方法 / 变量,进入实际使用阶段
  7. 卸载(Unloading):类不再被引用,由垃圾收集器从 JVM 内存中移除

一个类被初始化的过程?

  • 初始化阶段 :Java 虚拟机真正执行类中编写的 Java 程序代码,完成类的最终初始化(如执行静态代码块、为静态变量赋程序定义的初始值)。
  • 准备阶段 :仅为类的静态变量分配内存,并赋予系统默认零值(非程序定义值),不执行任何 Java 代码。

继承时父子类的初始化顺序是怎样的?

核心规律:静态代码(变量 + 初始化块)优先于实例代码,父类代码优先于子类代码,且同类中静态变量与静态块、实例变量与实例块的执行顺序与代码定义顺序一致。

  • 父类--静态变量
  • 父类--静态初始化块
  • 子类--静态变量
  • 子类--静态初始化块
  • 父类--变量
  • 父类--初始化块
  • 父类--构造器
  • 子类--变量
  • 子类--初始化块
  • 子类--构造器

什么是类加载器?

在类 "加载" 阶段,负责通过类的全限定名获取描述该类的二进制字节流的代码,称为类加载器。

JVM有哪些类加载器?

虚拟机视角分类

  • 启动类加载器(Bootstrap ClassLoader):C++ 实现,属于虚拟机自身部分。
  • 其他类加载器 :均由 Java 语言实现,独立于虚拟机外部,且全部继承抽象类<font style="color:rgb(0, 0, 0);">java.lang.ClassLoader</font>

开发者视角三层架构(JDK) :遵循 "双亲委派模型" 的层级关系:
<font style="color:rgba(0, 0, 0, 0.85);">BootstrapClassLoader(启动类加载器) <- ExtClassLoader(扩展类加载器) <- AppClassLoader(应用程序类加载器) <- 自定义类加载器</font>

JVM中不同的类加载器加载哪些文件?

  1. 启动类加载器(Bootstrap ClassLoader)
    1. 加载<font style="color:rgba(0, 0, 0, 0.85) !important;"><JAVA_HOME>\jre\lib</font>下的核心类库(如 rt.jar、resources.jar、charsets.jar),
    2. <font style="color:rgba(0, 0, 0, 0.85) !important;">-Xbootclasspath</font>参数指定路径的类库。
  2. 扩展类加载器(Extension ClassLoader)
    1. 实现类为<font style="color:rgba(0, 0, 0, 0.85) !important;">sun.misc.Laucher$ExtClassLoader</font>,加载<font style="color:rgba(0, 0, 0, 0.85) !important;"><JAVA_HOME>\jre\lib\ext</font>目录及<font style="color:rgba(0, 0, 0, 0.85) !important;">java.ext.dirs</font>系统变量指定路径的类库。
  3. 应用程序类加载器(Application ClassLoader)
    1. 实现类为<font style="color:rgba(0, 0, 0, 0.85) !important;">sun.misc.Launcher$AppClassLoader</font>
    2. 加载用户类路径(ClassPath)上的所有类库。

JVM三层类加载器之间的关系是继承关系?

三者之间不是继承关系。只是父类那一代 并不是真正的父亲或者母亲。

你了解JVM类加载的双亲委派模型吗?

双亲委派模型的核心逻辑是「先委派父加载器,再尝试自身加载」,具体流程如下:

  1. 当类加载器收到类加载请求时,不会先自行加载,而是将请求委派给其上层父类加载器;
  2. 该委派逻辑逐层传递,所有加载请求最终会传至最顶层的启动类加载器;
  3. 若上层父类加载器在自身搜索范围内未找到目标类(即无法完成加载),则反馈给下层类加载器;
  4. 当下层类加载器收到父类的 "加载失败" 反馈后,才会尝试自行加载目标类。

JDK为什么要设计双亲委派模型,有什么好处?

  • 安全保障:防止核心类库被篡改,确保 Java 核心 API 的完整性
  • 效率优化:避免类的重复加载,减少资源消耗
  • 唯一性保证:确保同一类在 JVM 中唯一存在,维护程序稳定性

可以打破JVM双亲委派模型吗?如何打破JVM双亲委派模型?

可以打破。通过自定义类加载器,重写其<font style="color:#000000;">loadClass</font>方法,去除双亲双亲委派逻辑即可实现。

如何自定义自己的类加载器?

  • 基础步骤 :继承<font style="color:#000000;">ClassLoader</font>类。
  • 方法选择
    • 覆盖<font style="color:#000000;">findClass(String name)</font>:遵循双亲委派模型,仅实现自定义加载逻辑。
    • 覆盖<font style="color:#000000;">loadClass()</font>:可修改委派逻辑,从而打破双亲委派模型。

ClassLoader 中的loadClass() 、findClass()、defineClass() 区别?

  • 核心方法作用
    • <font style="color:#000000;">loadClass()</font>:实现类加载主逻辑,包含默认双亲委派机制。
    • <font style="color:#000000;">findClass(String name)</font>:按名称 / 位置加载.class 字节码,无默认实现(JDK1.2 新增)。
    • <font style="color:#000000;">defineClass()</font>:将字节码转换为 Class 对象。
  • 自定义类加载器策略
    • 需打破双亲委派:重写<font style="color:#000000;">loadClass()</font>方法,修改委派逻辑。
    • 需遵守双亲委派:继承<font style="color:#000000;">ClassLoader</font>后重写<font style="color:#000000;">findClass()</font>,实现自定义加载逻辑(JDK1.2 后推荐方式,无需改动委派机制)。

加载一个类采用Class.forName()和 ClassLoader 有什么区别?

  • 类完整生命周期:字节码 → 类加载 → 链接(验证→准备→解析)→ 初始化 → 使用 → 卸载;其中类加载会将类信息存入元空间(方法区)。
  • 类获取方式差异
    • <font style="color:rgb(0, 0, 0);">Class.forName()</font>:获取的 Class 对象已完成初始化。
    • <font style="color:rgb(0, 0, 0);">ClassLoader.loadClass()</font>:获取的 Class 对象尚未初始化。

你了解Tomcat 的类加载机制嘛?

  • 扩展体系:在 Java 原生类加载机制基础上,Tomcat 新增 3 个基础类加载器、每个 Web 应用的类加载器及 JSP 类加载器。
  • 核心特性 :自定义<font style="color:rgb(0, 0, 0);">WebAppClassLoader</font>类加载器,打破双亲委派机制 ------ 收到类加载请求时,先尝试自身加载,找不到再委托父加载器,目的是优先加载 Web 应用自身定义的类(区别于<font style="color:rgb(0, 0, 0);">ClassLoader</font>默认的双亲优先委派模型)。

为什么Tomcat要破坏双亲委派模型?

Tomcat 破坏破坏双亲委派模型主要为满足四类核心需求:

  • Web 应用间类隔离:同一 Tomcat 上的不同 Web 应用类库相互隔离,避免冲突
  • 类库共享能力:支持不同 Web 应用共享通用类库,减少资源消耗
  • 服务器安全防护:保障 Tomcat 自身类库不受部署的 Web 应用影响
  • JSP 热部署 / 热加载:实现 JSP 页面修改后无需重启即可生效

有没有同时过热加载和热部署,如何自己实现一个热加载?

维度 热加载 热部署
核心定义 不重启服务,仅重新加载修改的 Class 文件使代码生效 不重启服务,重新部署整个项目(如 Tomcat 重新解压 War 包)
实现逻辑 后台线程轮询检测 Class 文件是否变更,变更则重新加载 删除旧项目解压目录,重新处理新项目资源(如 War 包)
耗时 耗时短,仅加载变更类 耗时长,需处理整个项目
应用场景 开发 / 调试阶段,提升效率 需整体更新项目的场景
安全性 存在不安全性,一般不用于正式环境 -
底层依赖 基于 Java 类加载器实现 基于服务器部署机制(如 Tomcat)

热加载实现步骤

  1. 自定义类加载器,用于加载需热加载的类;
  2. 通过自定义类加载器加载目标类;
  3. 轮询监测目标类的 Class 文件是否更新,若更新则触发重新加载;

Java代码到底是如何运行起来的?

  • 核心本质:运行Java程序即开启一个JVM进程,JVM作为"运行字节码的软件"是字节码执行的载体
  • 常见运行流程
    • 流程 1:<font style="color:rgb(0, 0, 0);">Mall.java</font><font style="color:rgb(0, 0, 0);">javac</font>编译→<font style="color:rgb(0, 0, 0);">Mall.class</font><font style="color:rgb(0, 0, 0);">java Mall</font>命令启动 JVM 进程
    • 流程 2:<font style="color:rgb(0, 0, 0);">Mall.java</font><font style="color:rgb(0, 0, 0);">javac</font>编译→<font style="color:rgb(0, 0, 0);">Mall.jar</font><font style="color:rgb(0, 0, 0);">java -jar Mall.jar</font>命令启动 JVM 进程
    • 流程 3:<font style="color:rgb(0, 0, 0);">Mall.java</font><font style="color:rgb(0, 0, 0);">javac</font>编译→<font style="color:rgb(0, 0, 0);">Mall.class</font><font style="color:rgb(0, 0, 0);">Mall.war</font>→部署到 Tomcat→<font style="color:rgb(0, 0, 0);">startup.sh</font>启动→触发<font style="color:rgb(0, 0, 0);">org.apache.catalina.startup.Bootstrap</font>类启动 JVM 进程

JVM运行原理图?

JVM的内存结构划分?

JVM运行时数据区 程序计数器 的特点及作用?

  • 内存规模:空间极小,可忽略不计。
  • 核心作用:作为当前线程执行字节码的行号指示器,记录下一条要执行的字节码指令地址。
  • 线程隔离性:线程私有内存区域,每条线程有独立程序计数器,互不影响(适配多线程切换需求)。
  • 异常特性 :不存在<font style="color:rgb(0, 0, 0);">OutOfMemoryError</font>(仅更新指令地址,无需申请新内存)。
  • GC 特性:无需垃圾回收(GC),内存无回收需求。

JVM运行时数据区 虚拟机栈 的特点及作用?

  • 线程属性:线程私有内存区域,随线程创建而生成、线程结束而销毁。
  • 核心结构与操作:方法执行时创建栈帧(存储局部变量表等信息),方法入栈执行、执行完毕出栈,遵循 "先进后出" 规则。
  • 存储内容:存放方法的局部变量名,变量名指向的常量值、对象值等实际数据存储在堆中。
  • 内存配置 :默认大小通常为 1M,可通过<font style="color:rgb(0, 0, 0);">-Xss</font>参数(如<font style="color:rgb(0, 0, 0);">-Xss1M</font>)设置,自身占用内存较小。
  • 异常类型
    • 栈深度超过虚拟机允许范围时,抛出<font style="color:rgb(0, 0, 0);">StackOverflowError</font>
    • 栈扩展时无法申请到足够内存,抛出<font style="color:rgb(0, 0, 0);">OutOfMemoryError</font>(较为少见,HotSpot 虚拟机中不常见)。
  • GC 特性:无需垃圾回收(GC),随线程生命周期自动回收。

JVM运行时数据区 本地方法栈 的特点及作用?

  • 共性与定位:与虚拟机栈特性基本相似,同为线程私有区域,随线程创建而生、线程结束而死,且 GC 不会回收该区域。
  • 核心差异:虚拟机栈为 Java 方法服务,本地方法栈则专门为 Native 方法服务。
  • HotSpot 虚拟机特殊处理:将虚拟机栈与本地方法栈合并实现。
  • 异常类型 :运行中可能抛出<font style="color:rgb(0, 0, 0);">StackOverflowError</font>(栈深度超限)和<font style="color:rgb(0, 0, 0);">OutOfMemoryError</font>(栈扩展内存申请失败)。

JVM运行时数据 Java堆 的特点及作用?

  • 基础属性:线程共享区域,虚拟机启动时创建,是 JVM 管理的最大内存块。
  • 存储内容:存放所有实例对象与数组。
  • GC 关联:垃圾收集器(GC)的核心管理区域。
  • 内存划分
    • 整体分为新生代、老年代;
    • 新生代细分为 Eden、From Survivor、To Survivor,三者比例通常为 8:1:1。
  • 配置与优化
    • 堆大小可通过<font style="color:rgb(0, 0, 0);">-Xmx</font>(最大堆)、<font style="color:rgb(0, 0, 0);">-Xms</font>(初始堆)调节;
    • 共享堆中可划分线程私有分配缓存区(TLAB),提升对象分配效率。
  • 异常类型 :堆无法扩展时,抛出<font style="color:rgb(0, 0, 0);">java.lang.OutOfMemoryError:Java heap space</font>

JVM中对象如何在堆内存分配?

核心分配算法(按堆内存规整性选择)

  • 指针碰撞(Bump The Pointer)
    • 适用场景:堆内存规整时(内存无碎片)。
    • 对应收集器:使用 Serial、ParNew 等带空间压缩(Compact)能力的收集器时采用,算法简单高效。
  • 空闲列表(Free List)
    • 适用场景:堆内存不规整时(存在内存碎片)。
    • 对应收集器:使用 CMS 等仅带清除(Sweep)算法的收集器时采用,逻辑较复杂。

并发安全优化:本地线程分配缓存(TLAB)

  • 问题背景:对象创建频繁,并发下直接分配内存可能导致 "内存争抢"(如 A 未修改指针,B 误用旧内存地址)。
  • 解决方案
    • 同步锁定:JVM 用 CAS + 失败重试保证内存更新的原子性。
    • 线程隔离(TLAB):每个线程在堆中预先分配一小块私有内存,优先从 TLAB 分配;仅当 TLAB 用完时,再申请新内存(减少并发竞争)。

JVM堆内中的对象布局?

Hotspot 虚拟机中,对象存储结构分三部分:

  • 对象头(Header)
    • Mark Word:存储对象运行数据(哈希码、GC 分代年龄、锁状态等),32 位占 32bit,64 位占 64bit。
    • 类型指针:指向类元数据的指针,确定对象所属类;数组对象额外包含数组长度信息(普通对象可通过元数据确定大小)。
  • 实例数据(Instance Data)
    • 存储程序定义的所有成员变量(含父类继承和子类定义的字段内容)。
  • 对齐填充(Padding)
    • 非必需,仅用于占位,确保对象大小为 8 字节的整数倍(HotSpot 虚拟机要求)。

JVM什么情况下会发生堆内存溢出?

  • 溢出原因:Java 堆存储对象,若持续创建对象且通过 GC Roots 保持可达路径(避免被回收),当对象总量达最大堆容量限制时,即产生内存溢出。
  • 排查方式:借助 MAT 工具分析 xxx.hprof 文件,定位溢出原因。

JVM如何判断对象可以被回收?

  • 核心目的:GC 回收堆内存前,通过该算法区分对象 "存活" 或 "死去"。
  • 算法逻辑:以 "GC Roots" 根对象为起点,沿引用关系向下搜索(路径为 "引用链");若对象到 GC Roots 无引用链(不可达),则判定为可回收对象。
  • GC Roots 包含的对象类型
    • 虚拟机栈(栈帧本地变量表)中引用的对象(如方法参数、局部变量);
    • 方法区 / 元空间中类静态属性、常量引用的对象;
    • 本地方法栈中 JNI(Native 方法)引用的对象;
    • JVM 内部引用对象(如基本类型 Class 对象、常驻异常对象、系统类加载器);
    • <font style="color:rgb(0, 0, 0);">synchronized</font>锁持有的对象;
    • JMXBean、JVMTI 回调、本地代码缓存等反映 JVM 内部情况的对象。

谈谈Java中不同的引用类型?

Java 有四种引用类型,特性如下:

  1. 强引用 :如<font style="color:rgba(0, 0, 0, 0.85) !important;">Object object = new Object()</font>,最常见的引用方式,只要存在就不会被 GC 回收。
  2. 软引用(SoftReference):内存充足时不回收,内存不足时触发回收,适合缓存场景。
  3. 弱引用(WeakReference):无论内存是否充足,GC 运行时都会回收,生命周期更短。
  4. 虚引用(PhantomReference):极少使用,形同虚设,仅在对象被 GC 回收时触发通知或后续处理。

JVM堆中新生代垃圾回收过程?

  • GC 回收范围 :针对新生代、老年代、元空间 / 方法区(永久代);虚拟机栈无 GC------ 方法执行完栈帧出栈,局部变量直接清理。
  • 堆中对象存活规律
    • 短期存活对象 :创建后迅速用完,在新生代分配,易被 GC 回收;
    • 长期存活对象 :需持续使用,在新生代的 From Survivor(S0)、To Survivor(S1)间经历 15 次 GC 后(对象年龄达 15),进入老年代

JVM对象动态年龄判断是怎么回事?

虚拟机并非仅要求对象年龄达<font style="color:#000000;">MaxTenuringThreshold=15</font>才晋升老年代,还存在动态年龄判断规则

对 Survivor 区对象按年龄从小到大累加,当累加到某年龄 X 时,总和超过 Survivor 区空间的 50%(默认值,可通过<font style="color:#000000;">-XX:TargetSurvivorRatio</font>调整),则所有年龄大于 X 的对象直接晋升老年代。

什么是老年代空间分配担保机制?

场景前提

新生代初始配置:

  • Eden(800m→Minor GC 后剩 300m)、S0(100m)、S1(100m);
  • 老年代(1000m,剩余 350m/200m);

核心问题:Minor GC 后存活对象超 Survivor 容量需转入老年代,若老年代空间不足如何处理?

老年代空间分配担保流程(Minor GC 前判断):

  1. 首次判断:老年代可用空间 ≥ 新生代所有对象总和
    • 若满足:直接执行 Minor GC。即使 Minor GC 后所有对象存活(需转入老年代),老年代也能容纳,无风险。
  2. 二次判断:老年代可用空间 < 新生代所有对象总和,但 ≥ 历次 Minor GC 后转入老年代的对象平均大小
    • 若满足:冒险执行 Minor GC(目的是避免频繁 Full GC)。但存在风险:若 Minor GC 后存活对象超老年代可用空间,会触发 Full GC。
  3. 最终处理:Full GC 后仍空间不足
    • 若 Full GC 后,老年代仍无法容纳 Minor GC 后的存活对象,直接触发 OOM(内存溢出)

核心目的:通过分层判断,优先避免不必要的 Full GC,仅在必要时触发,平衡 GC 效率与内存安全性。

什么情况下对象会进入老年代?

  • 年龄阈值机制
    对象躲过 15 次 GC 后自动进入老年代,可通过 JVM 参数<font style="color:rgba(0, 0, 0, 0.85) !important;">-XX:MaxTenuringThreshold</font>调整阈值(默认 15)。
  • 动态年龄判断机制
    Survivor 区对象按年龄从小到大累加,当累加到某年龄 X 的总和超过 Survivor 区空间阈值(默认 50%,可通过<font style="color:rgba(0, 0, 0, 0.85) !important;">-XX:TargetSurvivorRatio</font>调整),则年龄大于 X 的对象直接进入老年代。
  • 老年代空间担保机制
    Minor GC 前,若老年代可用空间无法容纳新生代所有对象,但满足 "可用空间≥历次 Minor GC 后转入老年代的对象平均大小",则冒险执行 Minor GC;若 Minor GC 后存活对象超老年代容量,触发 Full GC;Full GC 后仍不足则 OOM。
  • 大对象直接进入机制
    需大量连续内存空间的对象(如长字符串、大数组),会跳过新生代直接进入老年代,避免新生代频繁 GC 导致内存碎片。

JVM 运行时数据区 元空间 的特点及作用?

  • 版本与名称演变:JDK 1.8 起引入 "元空间" 概念,替代之前的 "方法区 / 永久代"。
  • 内存属性:与 Java 堆类似,为线程共享的内存区域。
  • 存储内容:存放被加载的类信息、字段、静态变量、常量池、即时编译的代码等数据。
  • 内存特性:基于本地内存实现,可随本地内存剩余空间扩展,也支持手动设置大小。
  • GC 特性:垃圾收集极少发生,因回收条件苛刻且可回收信息有限。

JVM 本机 直接内存 的特点及作用?

  • 内存归属:不属于 JVM 运行时数据区,而是直接使用本机物理内存。
  • 核心用途与优势 :JDK 1.4 引入的 NIO 类(基于通道与缓冲区的 IO 方式),可通过 Native 函数库直接分配堆外内存;借助 Java 堆中的<font style="color:rgb(0, 0, 0);">DirectByteBuffer</font>对象引用操作该内存,避免 Java 堆与 Native 堆间的数据复制,在部分场景中显著提升性能。
  • 异常风险 :若直接内存分配超出本机物理内存限制,可能触发<font style="color:rgb(0, 0, 0);">OutOfMemoryError</font>异常。

JVM本机直接内存溢出问题?

  • 容量配置 :可通过<font style="color:rgb(0, 0, 0);">-XX:MaxDirectMemorySize</font>参数指定大小(单位字节),用于限制 NIO 直接缓冲区的最大总分配量;默认值为 0,此时 JVM 自动决定 NIO 直接缓冲区的分配大小。
  • 溢出特性:直接内存导致的内存溢出不会生成 Heap Dump 文件;若程序使用 NIO 技术,出现内存溢出时应优先排查直接内存相关原因。

如何计算一个对象的大小?

lucene 提供的一个工具方法可以计算

堆为什么要分成年轻代和老年代?

JVM 将堆分为新生代和老年代并采用不同回收算法,核心依据是对象存活特性的差异:

  • 对象存活周期分化明显
    • 绝大多数对象短期存活,集中放在新生代,回收时只需关注少量存活对象,以低代价回收大量空间。
    • 少数对象长期存活,放入老年代,需适配其 "熬过多次 GC 后更难回收" 的特性。
  • 分代设计的核心价值
    • 针对不同存活特性的对象采用针对性回收策略,提升整体 GC 效率:新生代侧重快速回收大量短期对象,老年代侧重高效管理长期存活对象。

Eden区与Survivor区的空间大小比值默认为8:1:1?

  • 区域构成:包含 1 个 Eden 区(对象诞生地)和 2 个 Survivor 区(S0、S1)。
  • Survivor 区作用
    • 一个用于保存上次新生代 GC 后存活的对象;
    • 另一个为空,新生代 GC 时,将 Eden 区和非空 Survivor 区中存活的对象复制到该空 Survivor 区。
  • 对象存活特性:统计显示 90% 的对象 "朝生夕死",每次 GC 会回收 90% 的对象,剩余 10% 的存活对象由 Survivor 区预留空间保存。

请介绍下JVM中的垃圾回收算法?

  • 标记 - 清除算法 :先标记可回收对象,再统一清除。缺点是产生内存碎片
  • 标记 - 复制算法:将内存分为两块,每次使用一块,回收时将存活对象复制到另一块。适用于新生代(如 Eden 与 Survivor 区)。
  • 标记 - 整理算法 :标记后将存活对象向一端移动,再清除边界外内存。适用于老年代,避免碎片
  • 分代收集算法 :结合上述算法,新生代用复制算法老年代用标记 - 清除或标记 - 整理算法,按需回收。

JVM中的垃圾回收器?

  • 新生代收集器 :专注回收短期存活对象,适配新生代特性
    • Serial:单线程收集,简单高效,适用于客户端场景
    • ParNew:Serial 的多线程版本,常与 CMS 配合使用
    • Parallel Scavenge:多线程收集,侧重控制吞吐量
  • 老年代收集器 :针对长期存活对象,优化回收效率
    • CMS:并发收集,低停顿,优先保证响应速度
    • Serial Old:Serial 的老年代版本,单线程,作 CMS 降级预案
    • Parallel Old:Parallel Scavenge 的老年代版本,多线程,匹配吞吐量需求
  • 整堆收集器 :覆盖新生代与老年代,全局优化
    • G1:分区回收,可预测停顿,兼顾吞吐量与响应速度
  • 前沿成果 (OpenJDK):
    • ZGC、Shenandoah:超低延迟收集器,大幅降低停顿时间,适配高并发场景

什么是内存泄漏?什么是内存溢出?

  • 内存溢出(OutOfMemory):无空闲内存,即使经 GC 回收后仍无法提供足够内存空间。
  • 内存泄漏(Memory Leak):程序运行中未释放已无用的内存,单次泄漏影响小,长期堆积会引发内存溢出。

常见内存泄漏场景

  • 单例对象引用泄漏:单例生命周期与应用一致,若单例持有外部对象引用,该外部对象无法被 GC 回收,导致泄漏。
  • 未关闭资源泄漏 :数据库连接、Socket 网络连接、IO 流等资源,若未在<font style="color:rgb(0, 0, 0);">finally</font>中手动关闭,其占用内存无法释放,造成泄漏。

线上环境的JVM都设置多大?

  • 虚拟机栈:单个线程栈大小设为 512K(-Xss512k),300 个线程约占用 300M 内存。
  • 堆内存 :分配 4G(机器内存的一半),按收集器不同划分:
    • CMS 收集器:新生代 1/3(约 1.3G),老年代 2/3(约 2.7G)
    • G1 收集器:新生代与老年代比例 6:4
  • 元空间:配置 512M,满足大多数场景需求。

线上Java项目CPU飙到100%怎么排查?

  1. 定位进程 :使用<font style="color:rgba(0, 0, 0, 0.85) !important;">top</font>命令找到 CPU 占用最高的 Java 进程(PID)
  2. 定位线程 :执行<font style="color:rgba(0, 0, 0, 0.85) !important;">top -Hp PID</font>查看该进程内 CPU 最高的线程(TID)
  3. 转换线程 ID :将线程 ID 转为十六进制<font style="color:rgba(0, 0, 0, 0.85) !important;">printf "%x\n" TID</font>
  4. 查看线程栈<font style="color:rgba(0, 0, 0, 0.85) !important;">jstack PID | grep 十六进制TID -A 30</font>,分析对应的代码栈
  5. 分析代码:根据栈信息定位具体类和方法,检查是否存在死循环、频繁 GC、锁竞争等问题
  6. 辅助工具 :结合<font style="color:rgba(0, 0, 0, 0.85) !important;">jstat -gcutil PID 1000</font>观察 GC 情况,<font style="color:rgba(0, 0, 0, 0.85) !important;">jmap -histo PID</font>查看对象分布,进一步确认原因

JVM堆溢出后,其他线程是否可以继续工作?

  • 线程影响 :OOM 是否影响其他线程需结合场景分析,一般情况下,发生 OOM 的线程会终结,其持有的堆内存对象会被 GC 回收,释放空间。
  • 全局影响:即便其他线程暂能工作,OOM 发生前会触发频繁 GC,导致系统资源占用飙升,严重影响整体性能与响应效率。

亿级流量的订单系统JVM优化?

  1. 系统预估

每个用户访问20次/天,500万用户,流量 = 500万 * 20 = 10000万 = 1 亿;

购买率 15%,每人1单,每天订单量 = 500万 * 15% = 75万订单/天;

二八原则,下单集中在一天4小时内,洪峰下单量 = 75 万 / 4 小时 = 18.75万单 / 小时 = 18.75 万单 / 60 分 / 60 秒 = 52 单 / 秒

50 k * 20 * 10 = 52 * 200 = 10400 = 10MB/秒,每秒52单基本上JVM没有压力

  1. 流量洪峰场景
相关推荐
Lee川2 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Derek_Smart4 小时前
从一次 OOM 事故说起:打造生产级的 JVM 健康检查组件
java·jvm·spring boot
Lee川6 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i8 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有8 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有8 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫9 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫9 小时前
Handler基本概念
面试
Wect10 小时前
浏览器缓存机制
前端·面试·浏览器
大道至简Edward10 小时前
深入 JVM 核心:一文读懂 Class 文件结构(附 Hex 实战解析)
jvm