java heap space 与 GC overhead limit exceeded 内存溢出相关问题分析处理

前言

最近写了个复杂的数据处理程序,测试的时候在小数据量的场景下一点问题都没有,当部署到生产环境,发现执行不了,报了 java heap space 和 GC overhead limit exceeded 的错误,由于jvm的内存我们都是给最大的,而且明明内存都没满,怎么就报错了,只能从代码入手看是什么问题。

问题介绍

  • java heap space:内存溢出
  • GC overhead limit exceeded:JVM花费了98%的时间进行垃圾回收,而只得到2%可用的内存

问题重现

我使用的电脑配置不是很好,所以如果你的电脑比我的好,我下面的程序你就不会出现异常。
在这里插入图片描述

1、建立实体

package com.example.elasticsearch.entity;

import lombok.Builder;
import lombok.Data;

import java.math.BigDecimal;

@Data
@Builder
public class Item {
    private Long id;
    private String title; //标题
    private String category;// 分类
    private String brand; // 品牌
    private BigDecimal price; // 价格
    private String images; // 图片地址
}

2、建立测试程序

package com.example.elasticsearch.test;

import com.example.elasticsearch.entity.Item;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class JvmTest {

    @Test
    public void testJvm() {
        List<Item> items = new ArrayList<>();
        for(int i = 0; i < 3000000; i++) {
            items.add(Item.builder()
                    .id((long) i)
                    .title("手机" + i)
                    .category("手机")
                    .brand("大米")
                    .price(BigDecimal.valueOf(i))
                    .images("http://www.image/" + i + ".png")
                    .build());
        }
        log.info("执行1完成");

        List<Item> items2 = new ArrayList<>();
        for(int i = 0; i < 3000000; i++) {
            items2.add(Item.builder()
                    .id((long) i)
                    .title("手机" + i)
                    .category("手机")
                    .brand("大米")
                    .price(BigDecimal.valueOf(i))
                    .images("http://www.image/" + i + ".png")
                    .build());
        }
        log.info("执行2完成");

        List<Item> items3 = new ArrayList<>();
        for(int i = 0; i < 3000000; i++) {
            items3.add(Item.builder()
                    .id((long) i)
                    .title("手机" + i)
                    .category("手机")
                    .brand("大米")
                    .price(BigDecimal.valueOf(i))
                    .images("http://www.image/" + i + ".png")
                    .build());
        }
        log.info("执行3完成");
    }

}

3、执行结果
在这里插入图片描述
可以看到控制台输出中报错了,然后我们打开JDK下的内存监听程序(jdk/bin/jvisualvm.exe),这是jdk自带的,再次执行程序,找到你程序的tomcat,如下图:
在这里插入图片描述
可以看到堆内存一直在增长,直到内存溢出。

分析解决办法

从问题中不难看出,由于内存一直在增长,最终导致内存溢出,那有没有办法把没用的内存释放掉呢?重新检查代码,这个例子中的items和items2集合都是没用的,却在占着内存,可以从这里入手。
于是,将上面的建立测试程序修改成如下:

package com.example.elasticsearch.test;

import com.example.elasticsearch.entity.Item;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class JvmTest {

    @Test
    public void testJvm() {
        List<Item> items = new ArrayList<>();
        for(int i = 0; i < 3000000; i++) {
            items.add(Item.builder()
                    .id((long) i)
                    .title("手机" + i)
                    .category("手机")
                    .brand("大米")
                    .price(BigDecimal.valueOf(i))
                    .images("http://www.image/" + i + ".png")
                    .build());
        }
        log.info("执行1完成");
        items.clear();

        List<Item> items2 = new ArrayList<>();
        for(int i = 0; i < 3000000; i++) {
            items2.add(Item.builder()
                    .id((long) i)
                    .title("手机" + i)
                    .category("手机")
                    .brand("大米")
                    .price(BigDecimal.valueOf(i))
                    .images("http://www.image/" + i + ".png")
                    .build());
        }
        log.info("执行2完成");
        items2.clear();

        List<Item> items3 = new ArrayList<>();
        for(int i = 0; i < 3000000; i++) {
            items3.add(Item.builder()
                    .id((long) i)
                    .title("手机" + i)
                    .category("手机")
                    .brand("大米")
                    .price(BigDecimal.valueOf(i))
                    .images("http://www.image/" + i + ".png")
                    .build());
        }
        log.info("执行3完成");
    }

}

从上面的代码中不难发现,我就是把items和items2集合不用的时候做了个clear清除操作,执行结果却大不相同:
在这里插入图片描述
在这里插入图片描述
可以看到,程序执行过程中,多了两次内存释放回收的操作,从而让程序不会内存溢出。

相关扩展

java运行环境包含了一个内置的Garbage Collection (GC)垃圾回收进程,用于对不在使用的内存区域进行回收,释放被占用的内存,jvm会根据程序的运行情况,执行GC垃圾回收操作。java语言,程序员只需关注内存的分配,无需关注内存的回收。

这种机制也会有一些问题,就是被占用的内存,经过多次长时间的GC操作都无法回收,导致可用内存越来越少,俗称内存泄露

如果没有这个异常,会出现什么情况呢?经过垃圾回收释放的2%可用内存空间会快速的被填满,迫使GC再次执行,出现频繁的执行GC操作, 服务器会因为频繁的执行GC垃圾回收操作而达到100%的时使用率,服务器运行变慢,应用系统会出现卡死现象,平常只需几毫秒就可以执行的操作,现在需要更长时间,甚至是好几分钟才可以完成。

我之前也遇到过”JVM花费了98%的时间进行垃圾回收,而只得到2%可用的内存“这种情况,如图示可以看出,CUP的活动与GC占用的CPU已经相等,导致内存溢出:
在这里插入图片描述
这个问题是我有一个查询数据库的操作,由于Mongodb的数据结构大,数据量多,一次查询出来导致内存飙升,GC一直寻找可以释放回收的内存,可是回收的却很少。这个问题只需要将大数据量的数据分批次返回即可。

/**
 * 一次性返回代码
 **/
@Test
public void testJvm() {
    List<Item> items = itemService.findAll();
}
/**
 * List集合截取
 * @param list 集合
 * @param toIndex 截取量
 * @return List<List<String>>
 */
public static List<List<String>> groupStringList(List<String> list, int toIndex) {
    List<List<String>> listGroup = new ArrayList<>();
    int listSize = list.size();
    for (int i = 0; i < list.size(); i += toIndex) {
        if (i + toIndex > listSize) {
            toIndex = listSize - i;
        }
        List<String> newList = list.subList(i, i + toIndex);
        listGroup.add(newList);
    }
    return listGroup;
}

/**
 * 分批次返回代码
 **/
@Test
public void testJvm() {
	// 获取所有的ID集合
    List<String> ids = itemService.findAllIds();
    // 将ID集合截取成最大1000的小集合
    groupStringList(ids, 1000).forEach(smallIds -> {
        List<Item> items = itemService.findByIds(smallIds);
        System.out.println(items.size());
        items.clear();
    });
}

总结

遇到问题,多分析问题,就能找到解决问题的办法。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值