【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. 流量洪峰场景
相关推荐
yaoh.wang2 小时前
力扣(LeetCode) 13: 罗马数字转整数 - 解法思路
python·程序人生·算法·leetcode·面试·职场和发展·跳槽
程序员zgh3 小时前
C++ 互斥锁、读写锁、原子操作、条件变量
c语言·开发语言·jvm·c++
青蛙大侠公主4 小时前
java8新特性
jvm
铭哥的编程日记5 小时前
后端面试通关笔记:从真题到思路(五)
面试·职场和发展
前端一小卒7 小时前
一个看似“送分”的需求为何翻车?——前端状态机实战指南
前端·javascript·面试
程序员汤圆7 小时前
软件测试面试题总结【含答案】
测试工具·单元测试·测试用例
xlp666hub7 小时前
C进阶之内存对齐,硬件总线和高并发伪共享的底层原理
面试·代码规范
xhxxx7 小时前
从被追问到被点赞:我靠“哨兵+快慢指针”展示了面试官真正想看的代码思维
javascript·算法·面试
yaoh.wang7 小时前
力扣(LeetCode) 14: 最长公共前缀 - 解法思路
python·程序人生·算法·leetcode·面试·职场和发展·跳槽
努力发光的程序员8 小时前
互联网大厂Java求职面试实录
java·jvm·线程池·多线程·hashmap·juc·arraylist