查看: 169|回复: 0

java.lang.OutOfMemoryError: Java heap space堆内存不足的解决办法

[复制链接]

11

主题

0

回帖

73

积分

管理员

积分
73
发表于 2026-1-9 11:01:02 | 显示全部楼层 |阅读模式

ps:如果不是不能短暂停机的生产服务器,我认为最快的解决办法还是重启服务器。先恢复生产再往下找你能用到的解决方案。


一、临时缓解:调整 JVM 堆内存参数(最快见效)


堆内存不足的直接原因是 JVM 分配的堆空间不够,优先通过调整启动参数扩大堆内存,Windows 下需注意系统和 JDK 的位数限制:

1. 核心参数配置(以 jar 包运行为例)

# Windows命令行/批处理脚本(.bat)中运行java -Xms2g -Xmx4g -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\heapdump.hprof -jar your-app.jar





参数详解:

[td]
参数
作用
-Xms2g堆初始内存(建议设为 - Xmx 的 50%,减少 JVM 动态扩容的开销)
-Xmx4g堆最大内存(核心参数,直接提升堆上限)
-XX:+UseG1GC使用 G1 垃圾收集器(JDK9 + 默认,JDK8 需手动指定,更高效的内存回收)
-XX:+HeapDumpOnOutOfMemoryErrorOOM 时自动生成堆转储文件(关键,用于后续分析)
-XX:HeapDumpPath堆转储文件保存路径(Windows 路径用\或/,需确保目录有写入权限)



2. Windows 系统注意事项


  • 64 位 JDK:可设置较大堆内存(如 4g/8g/16g,根据服务器 / 电脑物理内存调整,建议不超过物理内存的 70%);
  • 32 位 JDK:堆内存最大只能到 1.2~1.5g,必须换成 64 位 JDK 才能突破此限制;
  • 物理内存不足:若 Windows 主机物理内存本身小(如 4g),不要把 - Xmx 设为 4g(系统本身需要内存),建议设为 2g,同时确保 Windows 虚拟内存(页面文件)≥堆最大内存。

二、定位根因:排查内存泄漏(调整参数后仍报错必做)


如果扩大堆内存后,程序运行一段时间仍报堆不足,90% 是内存泄漏(无用对象被持续引用,GC 无法回收,堆积在堆中),需通过工具定位泄漏点:

步骤 1:获取堆转储文件(hprof)


  • 方式 1:用上述参数,程序 OOM 时自动生成heapdump.hprof;
  • 方式 2:程序运行中手动导出(需安装 JDK):bash







    # 1. 打开Windows命令行,查看Java进程ID(找到你的应用进程)jps -l# 2. 导出堆转储文件(替换1234为你的进程ID)jmap -dump:format=b,file=D:\heapdump.hprof 1234

步骤 2:分析堆转储文件(Windows 推荐工具)


工具 1:Eclipse Memory Analyzer (MAT)(免费、最常用)


  • 下载地址:https://www.eclipse.org/mat/(选择 Windows 版本);
  • 打开 MAT,导入heapdump.hprof文件;
  • 选择 “Leak Suspects Report”(泄漏嫌疑报告),MAT 会自动分析:
    • 哪些对象占用了大部分堆内存(如 ArrayList/HashMap 里堆积了百万级对象);
    • 这些对象的引用链(谁在持有它们,导致 GC 无法回收)。



工具 2:JVisualVM(JDK 自带,无需额外安装)


  • 打开 Windows 命令行,输入jvisualvm启动工具;
  • 左侧找到你的 Java 进程,右键选择 “堆转储”;
  • 切换到 “类” 标签,按 “实例数” 排序,看哪些类的实例数量异常多(如自定义的Order类有 10 万个实例,远超正常业务量)。

三、代码层面修复(常见堆内存泄漏场景)


根据工具分析结果,针对性修复代码中的泄漏点,这是解决堆不足的根本方案:

场景 1:静态集合无限添加对象(最常见)


问题代码:静态 List/Map 持有对象引用,只加不减,堆积成海量对象:

java


运行







// 错误示例:静态集合永不清理public class DataHolder {    public static List<Order> orderList = new ArrayList<>();        // 业务方法:持续添加订单,无清理逻辑    public void addOrder(Order order) {        orderList.add(order);    }}





修复方案:

  • 按需清理集合:在合适的时机调用orderList.clear()或orderList.remove();
  • 改用有限容量的集合:如LinkedBlockingQueue设置最大容量,满了自动阻塞;
  • 弱引用集合:若对象无需强引用,用WeakHashMap(GC 时自动回收无其他引用的对象)。

场景 2:未关闭的资源导致对象无法回收


问题代码:数据库连接、IO 流、网络连接未关闭,持有大量内存资源:


// 错误示例:IO流未关闭,占用内存且无法回收public void readFile() {    FileInputStream fis = null;    try {        fis = new FileInputStream("large-file.txt");        // 读取文件逻辑    } catch (IOException e) {        e.printStackTrace();    }    // 未关闭fis,流对象一直占用堆内存}





修复方案:使用 try-with-resources 语法(自动关闭资源):



// 正确示例:try-with-resources自动关闭资源public void readFile() {    try (FileInputStream fis = new FileInputStream("large-file.txt")) {        // 读取文件逻辑    } catch (IOException e) {        e.printStackTrace();    }}





场景 3:大对象频繁创建且未及时回收


问题代码:循环中频繁创建大对象(如大字符串、大数组),新生代 GC 来不及回收,进入老年代堆积:




// 错误示例:循环创建大字符串,占用堆内存public void processData() {    for (int i = 0; i < 100000; i++) {        // 每次循环创建新的大字符串,老对象未及时回收        String largeStr = new String(new char[1024 * 1024]); // 1MB的字符串        // 业务逻辑(使用后未释放引用)    }}





修复方案:

  • 复用对象:将大对象提取到循环外,复用而非每次创建;
  • 及时置空引用:使用完大对象后,手动置为null,让 GC 能识别为可回收:java



    public void processData() {    String largeStr = null;    for (int i = 0; i < 100000; i++) {        largeStr = new String(new char[1024 * 1024]);        // 业务逻辑        largeStr = null; // 置空,让GC回收    }}

场景 4:缓存无过期 / 淘汰策略


问题代码:本地缓存无限存储数据,堆内存被占满:


// 错误示例:缓存无上限,一直添加public class LocalCache {    private static Map<String, Object> cache = new HashMap<>();        public static void put(String key, Object value) {        cache.put(key, value);    }}





修复方案:使用带过期 / 淘汰策略的缓存框架(如 Guava Cache、Caffeine):


// 正确示例:Guava Cache设置过期时间和最大容量import com.google.common.cache.Cache;import com.google.common.cache.CacheBuilder;import java.util.concurrent.TimeUnit;public class LocalCache {    private static Cache<String, Object> cache = CacheBuilder.newBuilder()            .maximumSize(1000) // 最大缓存数            .expireAfterWrite(30, TimeUnit.MINUTES) // 写入30分钟后过期            .build();        public static void put(String key, Object value) {        cache.put(key, value);    }        public static Object get(String key) {        return cache.getIfPresent(key);    }}





总结


  • 临时解决:通过-Xms/-Xmx调整 JVM 堆内存上限(64 位 JDK 可设更大),添加堆转储参数便于后续分析;
  • 根因定位:用 MAT/JVisualVM 分析堆转储文件,找到占用内存最多的对象和引用链;
  • 代码修复:重点解决静态集合堆积、资源未关闭、大对象频繁创建、缓存无淘汰策略等问题,让无用对象能被 GC 正常回收。



您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关注公众号

相关侵权、举报、投诉及建议等,请发 E-mail:admin@discuz.vip

Powered by Discuz! X5.0 © 2001-2025 Discuz! Team.|蜀ICP备2025161238号-5

在本版发帖
关注公众号
QQ客服返回顶部
快速回复 返回顶部 返回列表