JVM 内存调优
内存调优
内存泄漏(Memory Leak)和内存溢出(Memory Overflow)是两种常见的内存管理问题,它们都可能导致程序执行不正常或系统性能下降,但它们的原因和表现有所不同。
内存泄漏
内存泄漏(Memory Leak)指的是程序在动态分配内存后,未能正确释放或回收已经不再使用的内存,导致这部分内存无法再被程序使用,同时也不能被操作系统重新分配。每次内存泄漏都会使得系统的可用内存减少,长时间运行的程序可能会耗尽可用的内存资源,最终导致系统性能下降甚至崩溃。
在 Java 中如果不再使用一个对象,但是该对象依然在 GC ROOT 的引用链上,这个对象则不会被垃圾回收器回收,这种情况就是内存泄漏。如果对象之间存在循环引用并且没有适当地断开引用,这些对象可能也不会被垃圾回收器正确地释放。
内存泄漏大多数情况都是由于堆内存泄漏引起的。
内存溢出
内存溢出指的是程序试图分配超过其可用内存的空间。当程序尝试向已经被其他程序或操作系统占用的内存地址空间分配内存时,会导致内存溢出。通常会导致程序崩溃或异常终止。
程序在运行时请求了大量的内存,超过了系统当前可用的物理内存或虚拟内存。或者是递归函数未能正确地终止,导致了堆栈的溢出。
当然,内存泄漏也会导致内存溢出,例如在一些大型的 Java 后端应用中,在处理用户请求之后未能及时将用户的数据删掉,随着用户请求数量越来越多,持续内存泄漏的对象占满整个堆内存最终导致了内存溢出。但是产生内存溢出的并不仅仅只有内存泄漏这一种原因。
解决内存溢出的四个步骤:

在 Linux 环境下,可以使用top命令查看系统的进程信息,它提供了实时系统资源的使用情况,进程使用的内存为 RES(常驻内存) - SHR(共享内存)。但是该命令只能查看最基础的进程信息,无法查看每个部分(如堆、方法区、堆外等)的内存占用,也无法查看内存变化的趋势图。
这个时候可以使用另一个工具:VisualVM
VisualVM是多功能合一的Java故障排除工具并且他是一款可视化工具,整合了命令行 JDK 工具和轻量级分析功能,功能非常强大。
这款软件在0racle JDK6~8中发布,但是在 0racle JDK9 之后不在JDK安装目录下需要单独下载。下载地址:https://visualvm.github.io
JDK 8及以下版本可以在 JDK 的 bin 目录下找到jvisualvm.exe程序,双击打开即可。而 8 以上的版本下载解压完成之后,在解压后的bin目录下找到visualvm.exe程序双击打开。
在 IDEA 中快速启动 VisualVM,直接在 IDEA 中下载 VisualVM Launcher 插件,然后在设置中的其他设置里面配置之前下载好的visualvm.exe程序路径。

运行是选择用 VisualVM 运行即可

注意:VisualVM 仅限于测试时本地使用,禁止访问生产环境下的进程程序,其中的一些功能(如手动 Full GC )会导致生产环境下的用户进程暂停。
VisualVM 虽然可以实现实时监控系统的详细数据,但是对大量集群化部署的 Java 进程需要手动进行管理,非常麻烦。

产生原因
equals() 与 hashCode() 导致的内存泄漏
在定义新类时没有重写正确的equals()和hashCode()方法。在使用HashMap的场景下,如果使用这个类对象作为key,HashMap在判断key是否已经存在时会使用这些方法,如果重写方式不正确,会导致相同的数据被保存多份。
public class UserTest{public static long count = 0;public static Map<User,Long> userMap = new HashMap<>();public static void main(String[] args) throws InterruptedException {while(true) {if(count++ % 100 == 0) {Thread.sleep(100);}userMap.put(new User(1L, "xiaoming"),5L);}}
}class User {private Long id;private String username;private byte[] bytes;public User(Long id, String username) {this.id = id;this.username = username;this.bytes = new byte[1024];}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}
}
因为 hashCode 方法未实现,会导致相同id的用户对象计算出来的hash值并不同,会被分配到不同的槽中。而 equals 方法未实现,会导致 key 在比对的时候,即使用户对象的id是相同的,也会被认定为不同的key。
所以在阿里巴巴java开发手册中就有这样的规定,其目的也是为了防止编码的不规范而出现内存溢出:

这种问题的解决方案有以下三点:
- 在定义新实体时,始终重写 equals() 和 hashCode() 方法。
- 重写时一定要确定使用了唯一标识去区分不同的对象,比如用户的 id 等。
- Hashmap 使用时尽量使用编号 id 等数据作为 key,不要将整个实体类对象作为 key 存放。
内部类引用外部类
非静态的内部类默认会持有外部类,尽管代码上不再使用外部类,所以如果有地方引用了这个非静态内部类,会导致外部类也被引用,垃圾回收时无法回收这个外部类。
匿名内部类对象如果在非静态方法中被创建,会持有调用者对象,垃圾回收时无法回收调用者。
/*** 外部类** @author HeXin* @date 2024/07/09*/
public class Outer {private byte[] bytes = new byte[1024 * 1024];private static String name = "外部类";public List<String> newList(){List<String> list = new ArrayList<>();list.add("1");list.add("2");list.add("3");return list;}/*** 内部类** @author HeXin* @date 2024/07/09*/class Inner{private String name;public Inner(){this.name = Outer.name + "==> 内部类";System.out.println(this.name);}}public static void main(String[] args) {int count = 0;List<Inner> inners = new ArrayList<>();List<Object> objects = new ArrayList<>();// 非静态的内部类默认会持有外部类,会导致外部类也被引用,垃圾回收时无法回收这个外部类。while(true) {System.out.println(++count);inners.add(new Outer().new Inner());}// 匿名内部类对象如果在非静态方法中被创建,会持有调用者对象,垃圾回收时无法回收调用者。while(true) {System.out.println(++count);objects.add(new Outer().newList());}}
}
解决上述内存溢出问题的办法是:
- 若不想持有外部类对象,应该使用静态内部类,而使用内部类的原因是可以直接获取外部类中的成员变量值。
- 使用静态方法,避免匿名内部类持有调用者对象。
ThreadLocal 使用后未及时清理
若仅仅只是使用手动创建的线程,即便没有调用 ThreadLocal 的 remove 方法清理数据,也不会产生内存泄漏问题。因为线程被回收时,ThreadLocal 也同样会被回收,但若使用线程池则不一样,有可能会出现内存泄漏问题。
// 未发送内存泄漏
public class ThreadLocalTest {public static ThreadLocal<Object> threadLocal = new ThreadLocal<>();public static void main(String[] args) throws InterruptedException {while(true) {new Thread(()->{threadLocal.set(new byte[1024 * 1024 * 100]);}).start();Thread.sleep(100);}}
}
public class ThreadLocalTest {public static ThreadLocal<Object> threadLocal = new ThreadLocal<>();public static void main(String[] args) throws InterruptedException {ThreadPoolExecutor executor = new ThreadPoolExecutor(Integer.MAX_VALUE,Integer.MAX_VALUE,0, TimeUnit.DAYS,new SynchronousQueue<>());int count = 0;while(true) {System.out.println(executor.getPoolSize());executor.execute(()-> threadLocal.set(new byte[1024 * 1024 * 1024]));Thread.sleep(100);}}
}
当线程方法执行完毕后,一定要调用 ThreadLocal 中的 remove 方法清理对象。
String 的 intern 方法
在 JDK6 中字符串常量池位于堆内存中的Perm Gen永久代中,如果不同字符串的 intern 方法被大量调用,字符串常量池会不停的变大超过永久代内存上限之后就会产生内存溢出问题。
解决办法:
- 注意代码逻辑,尽量不要将随机生成的字符串加入字符串常量池。
- 增大永久代空间的大小,根据实际的测试/估算结果进行设置JVM参数
-XX:MaxPermSize=512M
通过静态字段保存对象
如果大量数据在静态变量中被长期引用,数据就不会得到释放。若这些数据将永久不被使用,则这些数据也成为了内存泄漏。
- 尽量减少将对象长时间的保存在静态变量中,如果不再使用,必须将对象删除(比如在集合中)或
者将静态变量设置为 null。 - 使用单例模式时,尽量使用懒加载( @Lazy 懒加载注解 ),而不是立即加载。
- Spring 的 Bean 中不要长期存放大对象,如果是缓存用于提升性能,尽量设置过期时间定期失效。
并发请求问题
并发请求问题指用户通过发送请求向 Java 程序获取数据,正常情况下 Java 后端程序将数据返回之后,这部分数据就可以在内存中被释放掉。但是由于用户的并发请求量有可能很大,同时处理数据的时间很长,导致大量的数据存在于内存中,最终超过了内存的上限,导致内存溢出。这类问题的处理思路和内存泄漏类似,首先要定位到对象产生的根源。
快速定位
当堆内存溢出时,需要在堆内存溢出时将整个堆内存保存下来,生成内存快照(Heap Profile)文件。
使用MAT打开hprof文件,并选择内存泄漏检测功能,MAT会自行根据内存快照中保存的数据分析内存泄漏的根源。
如果想要生成内存快照则需要添加以下的 JVM 参数:
-XX:+HeapDumpOnOutOfMemoryError:当程序发生内存溢出(Out of Memory Error)错误时,会自动生成 hprof 内存快照文件。
-XX:HeapDumpPath=<path>:指定 hprof 文件的输出路径。
如果需要导出运行中系统的内存快照,有两种方式,注意只需要导出标记为存活的对象
-
通过JDK自带的jmap命令导出,格式为:
jmap -dump:live,format=b,file=文件路径和文件名 进程ID -
通过arthas的heapdump命令导出,格式为:
heapdump --live 文件路径和文件名
在开发使用的机器内存范围内的快照文件,可以直接使用 MAT 打开分析。但是可能经常遇到服务器上的程序占用的内存达到 10G 之上的,开发环境无法正常打开内存快照的情况,并且还需要将其快照文件下载转移到开发环境,也是一个较为耗时的操作。此时需要下载服务器操作系统对应的MAT(https://eclipse.dev/mat/downloads.php)。
然后在服务器上执行下面的 MAT 中的脚本生成分析报告(几个静态页面),将其转移到开发环境中查看分析。
# suspects->内存泄漏检测报告 overview->总览图 top_components->组件图
./ParseHeapDump.sh 快照文件路径 org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components
注意:默认MAT分析时只使用了1G的堆内存,如果快照文件超过1G,需要修改MAT目录下的 MemoryAnalyzer.ini配置文件调整最大堆内存。最好修改为快照文件大小的 1.5 倍左右
MAT 内存泄漏检测原理
MAT提供了称为支配树(Dominator Tree)的对象图。支配树展示的是对象实例间的支配关系。在对象引用图中,所有指向对象B的路径都经过对象A,则认为对象A支配对象B。
支配树中对象本身占用的空间称之为浅堆(Shallow Heap)
支配树中对象的子树就是所有被该对象支配的内容,这些内容组成了对象的深堆(Retained Heap),也称之为保留集(Retained Set )。深堆的大小表示该对象如果可以被回收,能释放多大的内存空间。
MAT就是根据支配树,从叶子节点向根节点“收缩”遍历,若发现深堆的大小超过整个堆内存的一定比例阈值,则会将其标记为内存泄露的可疑对象。
修复问题
造成内存溢出问题的原因有以下三点:
- 编写代码时代码规范意识不强,导致其产生了代码中的内存泄漏。
- 由于参数设置不合理,并发场景下引起内存溢出,例如堆内存设置过小,会导致并发量增加之后超过堆内存的上限。
- 系统的设计方案不合理,例如直接使用*查询数据库,获取大量无用数据;线程池的设计不合理(线程池参数设置不当,会导致大量线程的创建或者队列中保存了大量的数据)等。
相关文章:
JVM 内存调优
内存调优 内存泄漏(Memory Leak)和内存溢出(Memory Overflow)是两种常见的内存管理问题,它们都可能导致程序执行不正常或系统性能下降,但它们的原因和表现有所不同。 内存泄漏 内存泄漏(Memo…...
Shell脚本提交Spark任务简单案例
一、IDEA打包SparkETL模块,上传值HDFS的/tqdt/job目录 二、创建ods_ETL.sh脚本 mkdir -p /var/tq/sh/dwd vim /var/tq/sh/dwd/ods_ETL.sh chmod 754 /var/tq/sh/dwd/ods——ETL.sh #脚本内容如下 #!/bin/bash cur_date$(date %Y-%m-%d) /opt/bigdata/spark-3.3.2/b…...
国标GB28181视频平台EasyCVR视频汇聚系统,打造别墅居民区智能监控体系
一、现状背景 随着国家经济的快速增长,生活水平逐渐提高,私人别墅在城市、乡镇和农村的普及率也在逐年增加。然而,由于别墅区业主经济条件较好,各类不法事件也日益增多,主要集中在以下几个方面: 1&#x…...
BGP分解实验·23——BGP选路原则之路由器标识
在选路原则需要用到Router-ID做选路决策时,其对等体Router-ID较小的路由将被优选;其中,当路由被反射时,包含起源器ID属性时,该属性将代替router-id做比较。 实验拓扑如下: 实验通过调整路由器R1和R2的rout…...
机器学习(5)——支持向量机
1. 支持向量机(SVM)是什么? 支持向量机(SVM,Support Vector Machine)是一种监督学习算法,广泛应用于分类和回归问题,尤其适用于高维数据的分类。其核心思想是寻找最优分类超平面&am…...
访问不到服务器上启动的llamafactory-cli webui
采用SSH端口转发有效,在Windows上面进行访问 在服务器上启动 llamafactory-cli webui 后,访问方式需根据服务器类型和网络环境选择以下方案: 一、本地服务器(物理机/虚拟机) 1. 直接访问 若服务器与操作设备处于同一…...
【玩泰山派】MISC(杂项)- 使用vscode远程连接泰山派进行开发
文章目录 前言流程1、安装、启动sshd2、配置一下允许root登录3、vscode中配置1、安装remote插件2、登录 **注意** 前言 有时候要在开发板中写一写代码,直接在终端中使用vim这种工具有时候也不是很方便。这里准备使用vscode去通过ssh远程连接泰山派去操作࿰…...
量子纠缠物理本质、技术实现、应用场景及前沿研究
以下是关于 量子纠缠(Quantum Entanglement) 的深度解析,涵盖物理本质、技术实现、应用场景及前沿研究,以技术视角展开: 一、量子纠缠的物理本质 1. 核心定义 量子纠缠是多个量子系统(如粒子)间的一种关联状态,表现为: 非局域性:纠缠态粒子无论相距多远,测量其中一…...
Spring Boot中接入DeepSeek的流式输出
第一步,添加依赖: <!-- WebFlux 响应式支持 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId> </dependency> 第二步,配置We…...
同步/异步日志系统
同步/异步日志系统 项目演示基础测试性能测试测试环境:同步日志器单线程同步日志器多线程异步日志器单线程异步日志器多线程 工具类(util.hpp)日志等级level.hpp 日志消息message.hpp 日志消息格式化formatter.hpp 日志消息落地sink.hpp 日志…...
typescript html input无法输入解决办法
input里加上这个: onkeydown:(e: KeyboardEvent) > {e.stopPropagation();...
游戏引擎学习第224天
回顾游戏运行并指出一个明显的图像问题。 回顾一下之前那个算法 我们今天要做一点预加载的处理。上周刚完成了游戏序章部分的所有剪辑内容。在运行这一部分时,如果观察得足够仔细,就会注意到一个问题。虽然因为视频流压缩质量较低,很难清楚…...
SAP-ABAP:SAP HANA高可用与灾备——存储镜像与系统复制的核心技术
SAP HANA作为企业关键业务的核心数据库,其高可用性(High Availability, HA)与灾备(Disaster Recovery, DR)能力直接影响业务连续性。HANA通过存储镜像、系统复制及集群集成三大核心技术,实现秒级故障切换与…...
工厂能耗系统智能化解决方案 —— 安科瑞企业能源管控平台
安科瑞顾强 政策背景与“双碳”战略驱动 2025年《政府工作报告》明确提出“单位国内生产总值能耗降低3%左右”的目标,要求通过产业结构升级(如高耗能行业技术革新或转型)、能源结构优化(提高非化石能源占比)及数字化…...
【pytorch图像视觉】lesson17深度视觉应用(上)构建自己的深度视觉项目
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、 数据1、认识经典数据1.1入门数据:MNIST、其他数字与字母识别(1)数据下载(2)查看数据的特征和标…...
java中的Future的设计模式 手写一个简易的Future
案例 例如:今天是小妹的生日,需要一个蛋糕有点仪式感,于是去蛋糕店预定,预定完之后,店老板说蛋糕做好了,到时电话通知你,不可能在这傻傻的等着吧,还有其他事情要做啊,于…...
USB(TYPE-C)转串口(TTL)模块设计讲解
目录 一 、引言 二、方案设计 三、USB TYPE-C介绍 1、TYPE-C接口定义 1、24P全引脚描述 2、Type C 接口 VBUS/GND 作用 3、Type C 接口 D/D- 作用 1、数据传输: 2、设备识别: 3、充电协议协商: 4、Type C 接口 CC1/CC2 作用 1、主从设备区…...
JavaScript | ajax实现原理
在早期,web应用,更多采用mvc框架,通过后端输出整个页面的内容,然后再用浏览器进行渲染,这样效率不高,对于事件绑定来说比较麻烦,于是提出了ajax,其最大的特点就是能实现局部更新。通…...
PyTorch张量操作指南:cat、stack、split与chunk的实战拆解
本文深入探讨PyTorch中用于调整张量结构的四个核心函数——torch.cat、torch.stack、torch.split和torch.chunk。通过实际应用场景分析和代码演示,帮助读者掌握它们的功能差异及适用条件,提升模型开发的灵活性与效率。 在深度学习实践中,张量…...
YOLO涨点技巧之分层扩展路径聚合网络 (HEPAN)
一、应用场景与问题背景 1.1 无人机图像检测挑战 https://ai-studio-static-online.cdn.bcebos.com/3d4f7e8c4d8d4d2d8a4c8e4b4e8c4d8d 场景特点:无人机航拍视角下的小目标检测(如行人、车辆、农作物病害等)核心难点: 目标尺寸小(<3232像素)复杂背景干扰(如城市…...
SQLite、MySQL、SQL Server、Oracle 和 PostgreSQL 五种数据库的区别
以下是 SQLite、MySQL、SQL Server、Oracle 和 PostgreSQL 五种主流关系型数据库管理系统(RDBMS)的区别,从多个维度进行对比: 1. 架构与部署 SQLite(Structured Query Language Lite): 嵌入式数据库,无服务器架构。数据库存储在一个单一的磁盘文件中。部署简单,适合轻量…...
git在分支上会退到某个指定的commit
1、在idea上先备份好分支(基于现有分支new branch) 2、在gitlab管理端删除现有分支 3、在idea中大卡terminal,执行 git log 查看commit log ,找到要会退到的commit唯一码,然后执行git reset 唯一码 4、查看本地代码状态 git st…...
玩机进阶教程----MTK芯片设备刷机导致的死砖修复实例解析 连电脑毫无反应 非硬件问题
在高通芯片机型中,我们可以通过短接主板测试点来激活高通芯片特有的9008底层端口来刷写救砖固件。但通常MTK芯片类的设备联机电脑即可触发深刷模式。但有些例外的情况会导致链接电脑毫无反应。遇到类似故障的友友可以参阅此博文尝试解决。 通过博文了解 1💝💝💝-----实…...
MIPI协议介绍
MIPI协议介绍 mipi 协议分为 CSI 和DSI,两者的区别在于 CSI用于接收sensor数据流 DSI用于连接显示屏 csi分类 csi 分为 csi2 和 csi3 csi2根据物理层分为 c-phy 和 d-phy, csi-3采用的是m-phy 一般采用csi2 c-phy 和 d-phy的区别 d-phy的时钟线和数据线是分开的,2根线一对…...
MySQL 中 `${}` 和 `#{}` 占位符详解及面试高频考点
文章目录 一、概述二、#{} 和 ${} 的核心区别1. 底层机制代码示例 2. 核心区别总结 三、为什么表名只能用 ${}?1. 预编译机制的限制2. 动态表名的实现 四、安全性注意事项1. ${} 的风险场景2. 安全实践 五、面试高频考点1. 基础原理类问题**问题 1**:**问…...
AI应用开发平台 和 通用自动化工作流工具 的详细对比,涵盖定义、核心功能、典型工具、适用场景及优缺点分析
以下是 AI应用开发平台 和 通用自动化工作流工具 的详细对比,涵盖定义、核心功能、典型工具、适用场景及优缺点分析: 1. AI应用开发平台 vs 通用自动化工作流工具 (1) 定义与目标 类型AI应用开发平台通用自动化工作流工具定义用于快速构建、训练、部署…...
GitHub 趋势日报 (2025年04月12日)
本日报由 TrendForge 系统生成 https://trendforge.devlive.org/ 📈 今日整体趋势 Top 10 排名项目名称项目描述今日获星总星数语言1yeongpin/cursor-free-vip[Support 0.48.x](Reset Cursor AI MachineID & Auto Sign Up / In & Bypass Higher…...
asm汇编源代码之-字库转换程序
将标准的16x16点阵汉字库(下载16x16汉字库)转换成适合VGA文本模式下显示的点阵汉字库 本程序需要调用file.asm中的子程序,所以连接时需要把file连接进来,如下 C:\> tlink chghzk file 调用参数描述如下 C:\> chghzk ; 无调用参数,转换标准库文件(SRC16.FNT)为适合VGA…...
VMware Ubuntu挂载Windows机器的共享文件
https://www.dong-blog.fun/post/2029 在VMware Ubuntu中访问Windows共享文件夹:完整指南 在使用VMware运行Ubuntu虚拟机时,访问Windows主机上的文件是常见需求。本文将详细介绍如何通过网络共享方式,让Ubuntu虚拟机直接访问Windows主机的文…...
智慧社区数据可视化中枢平台——Axure全场景交互式大屏解决方案
在数字化治理的时代浪潮中,社区管理正面临数据碎片化、响应滞后、决策盲区等核心挑战。如何将分散的安防、环境、能源、民生服务等数据整合为可操作的智慧洞察?如何让冰冷的数字转化为社区管理者手中的决策利器?Axure智慧社区可视化大屏原型模…...
