一起学习JVM-内存结构-堆(Heap)/堆内存的监视诊断(线程共享的区域)

堆(Heap)

在这里插入图片描述
定义: 通过 new 关键字,创建对象都会使用堆内存
特点:
1.它是线程共享的,堆中对象都需要考虑线程安全的问题
2.有垃圾回收机制

堆内存溢出

演示代码:设置启动参数堆内存为8M(-Xmx8m)

/**
 * 演示堆内存溢出 java.lang.OutOfMemoryError: Java heap space
 * -Xmx8m
 */
public class Demo1_5 {
    public static void main(String[] args) {
        int i = 0;
        try {
            List<String> list = new ArrayList<>();
            String a = "hello";
            while (true) {
                list.add(a); // hello, hellohello, hellohellohellohello ...
                a = a + a;  // hellohellohellohello
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(i);
        }
    }
}

运行结果:堆内存溢出
在这里插入图片描述

堆内存诊断/如何查看堆内存的情况

方式一: jps+ jmap
jps 工具:查看当前系统中有哪些 java 进程
jmap 工具:查看堆内存占用情况 jmap - heap 进程id
由于此命令只能在Linux和Solaris系统上运行,在此就不用这种方式查看。想尝试的可以自己搞搞。
方式二: jconsole 工具
功能:图形界面的,多功能的监测工具,可以连续监测
演示代码:

/**
 * 演示堆内存
 */
public class Demo1_4 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("1...");
        Thread.sleep(30000);//预留时间方便操作检测工具
        byte[] array = new byte[1024 * 1024 * 10]; // 占用堆内存10 Mb
        System.out.println("2...");
        Thread.sleep(20000);
        array = null;//满足垃圾回收的机制
        System.gc();//垃圾回收
        System.out.println("3...");
        Thread.sleep(1000000L);
    }
}

运行代码后打开jconsole 工具:win+r->cmd->执行命令jconsole 打开jconsole 界面

在这里插入图片描述
在这里插入图片描述
连接后注意看:

控制台打印1...的时候,堆内存还没什么东西处于初始化状态

在这里插入图片描述

控制台打印2...的时候,已经new了一个10M的byte数组,看堆内存的变化是不是上涨了10M

在这里插入图片描述

控制台打印3...的时候,已经触发了垃圾回收,看堆内存变化是不是清理了内存

在这里插入图片描述
方式三(重点): jvisualvm工具(常用)
功能:用可视化的方式来展示虚拟机的内容

问题:垃圾回收后,内存占用仍然很高

假设代码已经跑起来了,但是我们不知道代码。

打开jvisualvm工具:win+r->cmd->执行命令jvisualvm 打开jvisualvm界面

在这里插入图片描述

连接要查看的项目(这里其实就是一段代码而已,真实情况肯定是一个项目的)->点击监视->查看堆内存已经占用250MB左右了

在这里插入图片描述

手动执行垃圾回收
看折线图是不是感觉堆内存下去一大截,其实不然,重点看执行前堆内存占用将近275MB,执行GC后占用200MB出头。想想,触发GC后才清理了那么一点,那么就要考虑是不是因为我们编码的失误导致有一些对象始终被引用而无法释放。

在这里插入图片描述

找出问题原因
1.点击堆dump:截取堆内存的详细信息

在这里插入图片描述

2.查找占堆内存最大的20个对象

在这里插入图片描述

3.看看占用最大的对象是谁?
如图:显示有个ArrayList和一个Object

在这里插入图片描述

4.点击查看这个ArrayList
发现ArrayList包含了200个元素,而且这200个元素都是一个叫Student的对象,那么我们就可以看是不是这个对象占用的内存比较大呢

在这里插入图片描述

5.点击查看对象
发现Student对象中有个big属性是一个byte数组,占用大概1MB内存
ArrayList包含200个Student对象那就占用了200多MB了,而且这个对象是长时间使用的,导致GC的时候无法回收,最终堆内存一直居高不下。

在这里插入图片描述

看下源代码,看问题是不是出在这个student对象里,并且一直被使用着

演示代码:

new了200个Student对象并且add到ArrayList中,线程睡眠期间,这个ArrayList集合是一直被使用的状态。所以导致对象不能被回收,堆内存居高不下

/**
 * 演示查看对象个数 堆转储 dump
 */
public class Demo1_13 {
    public static void main(String[] args) throws InterruptedException {
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 200; i++) {
            students.add(new Student());
        }
        Thread.sleep(1000000000L);
    }
}
class Student {
    private byte[] big = new byte[1024*1024];
}

总结:这个例子是刻意做出来的,当然实际的生产环境下分析的手段和排查的方式也是类似的。先找出占用最大的一些对象,再定位每个对象进行排查。在对应源代码找出问题根源。

又get一个新技能 -->jvisualvm工具的使用。
再见

发布了12 篇原创文章 · 获赞 22 · 访问量 2610

猜你喜欢

转载自blog.csdn.net/weixin_45240169/article/details/103857439