05_堆

定义

在JVM中通过new关键字创建的对象都会被放在堆内存中

  • 所有线程共享,堆内存中的对象都需要考虑线程安全问题
  • 有垃圾回收机制(当对象不再不引用时才会被回收)

堆内存溢出

有代码如下所示:

package org.slumberjax.jvm.d05;

import java.util.ArrayList;
import java.util.List;

/**
 * 演示堆内存溢出 -Xmx8m
 */
public class T01 {

    public static void main(String[] args) {
        int i = 0;

        try {
            
            /*
            list的生存范围在整个try语句块
            由于下面的代码中有while死循环导致该list一直处于被引用,无法被回收
            同时while死循环也在不断的往list中添加指数级别增长的hello字符串,这些字符串也处于被引用状态,无法被回收
            最终撑爆堆区内存
             */
            
            List list = new ArrayList<>();

            String a = "hello";

            /*
            循环往list中添加指数级别增长的hello的字符串
             */
            while (true) {
                list.add(a);//hello,hellohello,hellohellohellohello,hellohellohellohellohellohellohellohello,...

                a = a + a;

                i++;
            }

        } catch (Throwable e) {
            e.printStackTrace();

            System.out.println(i);
        }
    }
}

运行输出结果如下:

26
java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at org.slumberjax.jvm.d05.T01.main(T01.java:22)

从结果得知java.lang.OutOfMemoryError: Java heap space是java堆空间不足导致的内存溢出,循环了26次就堆空间内存溢出了

此时重新运行该程序添加堆空间大小参数-Xmx8m设置堆空间大小为8M,结果如下:

java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at org.slumberjax.jvm.d05.T01.main(T01.java:33)
17

循环17次就堆空间内存溢出了

注: 生产环境上某个服务器内存比较大的情况下,不太容易暴露堆空间内存溢出,也许随着时间的累积最终会导致溢出,短时间内无法复现这个情况,在排查这种问题时,将堆空间内存的虚拟机参数-Xmx的值设置小一些能更快的定位到问题原因

堆内存诊断工具

  1. jps 查看当前系统中有哪些java进程
  2. jmap 查看堆内存占用情况(只能查询某一个时刻堆内存占用情况)
  3. jconsole 图形化界面,多功能的检测工具,可以连续检测
  4. jvirsualvm 图形化页面的虚拟机检测工具(更为强大)

堆内存诊断演示

使用如下代码进行演示:

package org.slumberjax.jvm.d05;

/**
 * 演示堆内存
 */
public class T02 {
    public static void main(String[] args) throws InterruptedException {

        System.out.println("1...");

        Thread.sleep(30000);

        byte[] bytes = new byte[1024 * 1024 * 10];//10 MB new关键字创建占用堆空间

        System.out.println("2...");

        Thread.sleep(30000);

        bytes = null;//将bytes指向null 原有的10MB的bytes将不被引用

        System.gc();//显示调用gc进行垃圾回收,此时上面的10MB的未被引用的bytes将会被回收

        System.out.println("3...");

        Thread.sleep(1000000L);
    }
}

jps,jmap演示

  1. 运行T02程序后使用jps查看该程序进程

    05_堆_第1张图片

    得到T02程序的进程号为14348

  2. 分别在T02程序输出1...,2...,3...时使用jmap抓取此时的堆内存快照进行分析jps -heap 进程编号-heap参数查看堆区内存

    • T02程序输出1...时执行jmap -heap 14348得到堆区快照数据如下

      05_堆_第2张图片

      新创建的对象都是放在Eden区(相关知识在垃圾回收章节阐述)

    • T02程序输出2...时执行jmap -heap 14348得到堆区快照数据如下

      05_堆_第3张图片

      由于在输出2...之前创建了一个10MB的byte[],导致堆区Eden Space内存增加10MB

    • T02程序输出3...时执行jmap -heap 14348得到堆区快照数据如下

      05_堆_第4张图片

      由于在输出3...之前回收了不再被引用的10MB的bytep[],Eden Space释放到只剩1MB左右(GC还释放了其他内存,所以这里释放的不止10MB)

jconsole演示

运行T02程序后,终端输入jconsole命令进行检测,演示截图如下

05_堆_第5张图片

选择T02进程

05_堆_第6张图片

检测数据与jmap快照数据是一致的

05_堆_第7张图片

jconsole还可以检测线程死锁

堆内存诊断案例

案例描述: 垃圾回收后,内存占用仍然很高

有程序T03(先不给出源码),运行后发现内存占用过高,执行垃圾回收后仍然过高,对该程序进行诊断分析(同时演示另一个更为强大的jvisualvm工具)

  1. 终端执行jvisualvm并查看T03程序发现堆区内存高达250MB之多

    05_堆_第8张图片

  2. 执行一次垃圾回收后,内存居高不下

    05_堆_第9张图片

  3. 此时查看堆Dump

    05_堆_第10张图片

  4. 查找占用内存最大的20个对象

    05_堆_第11张图片

  5. 发现有多个占用1MB的Something对象,并且有一个ArrayList占用了200MB的内存

    05_堆_第12张图片

  6. 查看该ArrayList的详情,发现该ArrayList中存放了200个1MB的Something对象

    05_堆_第13张图片

  7. 此时再来看T03程序的源码,导致该现象的原因一目了然

    package org.slumberjax.jvm.d05;
    
    import java.util.ArrayList;
    import java.util.List;
    
    class Something {
        private final byte[] bytes = new byte[1024 * 1024];//1MB
    }
    
    public class T03 {
        public static void main(String[] args) throws InterruptedException {
            List list = new ArrayList<>();//该list的生命作用域在整个main代码块中
    
            for (int i = 0; i < 200; i++) {
                list.add(new Something());
            }
    
            Thread.sleep(1000000000L);//由于此处超长时间的睡眠 导致list的引用无法释放 垃圾回收无法回收该list
        }
    }
    

你可能感兴趣的:(05_堆)