一、说明

当虚拟机申请不到内存空间的时候,会报堆内存溢出: OutOfMemoryError:java heap space

常见的原因:http://outofmemory.cn/c/java-...

我测试到时候,运行在 16G 内存的机器上。JVM 堆内存 默认为物理内存的1/4,即 16 * 1/4 = 4G

JDK 8的 JVM 在 JDK 7 的基础上从堆内存中移除了永久代(Perm Generation),替换为了堆内存之外的元空间(Metaspace),元空间是堆外直接内存,不受堆内存的限制,只受物理内存的限制,可以提供更大的空间。

二、原因及解决办法

OutOfMemoryError 异常的常见原因:

  1. 加载的数据过大。如:加载的文件或者图片过大、一次从数据库取出过多数据
  2. 代码存在死循环或循环产生过多的对象

解决方法

  1. 增加jvm的内存大小,使用 -Xmx 和 -Xms 来设置
  2. 检查代码中是否有死循环或递归调用。
  3. 检查是否有大循环重复产生新对象实体。
  4. 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
  5. 检查List、Map等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

三、代码示例

/**java堆溢出实例 * 原理:java的堆是用来存放对象实例的,所以我们只要做到以下三点就可以使堆溢出: * 1、限制堆的大小,不可扩展 * 2、不断新建对象 * 3、保持对象存活不被回收 * 对应的,我们需要: * 1、改变JVM的启动参数,将堆的最小值和最大值设成一样,这样就可以避免堆自动扩展(其实不一样也可以) * 2、不断产生对象 * 3、使用一个List来保存对象,保持对象存活 * * JVM配置参数: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError * */public class HeapOom {    public static void main(String[] args) {        // 此list实例会存放在堆内存中        List<byte[]> list = new ArrayList<>();        int i = 0;        boolean flag = true;        while (flag) {            try {                i++;                // 每次增加一个1M大小的数组对象                list.add(new byte[1024 * 1024]);            } catch (Throwable e) {  // catch 捕获的是 Throwable,而不是 Exception。因为 OutOfMemoryError 不属于 Exception 的子类。                e.printStackTrace();                flag = false;                // 记录次数                System.out.println("count=" + i);            }        }        // 不让进程结束,便于使用分析工具来查看内存情况        try {            Thread.sleep(24 * 60 * 60 * 1000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

使用的 java 1.8.0_171 版本:

$ java -versionjava version "1.8.0_171"Java(TM) SE Runtime Environment (build 1.8.0_171-b11)Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)

报错信息:

count=3316java.lang.OutOfMemoryError: Java heap space    at com.song.HeapOom.main(HeapOom.java:21)

运行结果表明,运行到 3316 次时,出现了堆内存溢出。由于每次增加1M内存。粗略估计:程序大约使用3G内存时,出现了内存溢出情况。

通过jmap或VisualVM 者工具可以查看内存情况:最后可以看出,老年代内存使用情况,大约使用了2709MB,使用率近100%。从而导致了 OutOfMemoryError

TIPS:下面查看内存时,由于使用工具查看内存的时间不是同一时间,所以内存使用量有细微差别

1. 使用jmap查看的内存信息

$ jmap -heap 3428Attaching to process ID 3428, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.66-b18using thread-local object allocation.Parallel GC with 4 thread(s)Heap Configuration:   MinHeapFreeRatio         = 0   MaxHeapFreeRatio         = 100   MaxHeapSize              = 4261412864 (4064.0MB)   NewSize                  = 88604672 (84.5MB)   MaxNewSize               = 1420296192 (1354.5MB)   OldSize                  = 177733632 (169.5MB)   NewRatio                 = 2   SurvivorRatio            = 8   MetaspaceSize            = 21807104 (20.796875MB)   CompressedClassSpaceSize = 1073741824 (1024.0MB)   MaxMetaspaceSize         = 17592186044415 MB   G1HeapRegionSize         = 0 (0.0MB)Heap Usage:PS Young GenerationEden Space:   capacity = 637009920 (607.5MB)   used     = 637009920 (607.5MB)   free     = 0 (0.0MB)   100.0% usedFrom Space:   capacity = 11010048 (10.5MB)   used     = 0 (0.0MB)   free     = 11010048 (10.5MB)   0.0% usedTo Space:   capacity = 11010048 (10.5MB)   used     = 0 (0.0MB)   free     = 11010048 (10.5MB)   0.0% usedPS Old Generation  // 老年代内存使用情况,大约使用了2709MB,使用率近100%(导致OutOfMemoryError)   capacity = 2841116672 (2709.5MB)   used     = 2840991320 (2709.38045501709MB)   free     = 125352 (0.11954498291015625MB)   99.99558793198338% used4955 interned Strings occupying 422328 bytes.

2. 使用 VisualVM 查看的内存信息