浅谈JVM - 内存结构(五)- 堆

本文深入解析Java堆内存的定义、作用、垃圾回收机制及其溢出原因。通过示例代码演示堆内存使用情况,并介绍jps、jmap、jconsole、jvisualvm等工具的使用方法,帮助开发者有效诊断和管理Java应用程序的堆内存。
摘要由CSDN通过智能技术生成

5.1 定义

Heap 堆

  • 通过new关键字,创建对象都会使用堆内存

  • 线程共享的,堆中对象都需要考虑线程安全的问题

  • 有垃圾回收机制

  • Java 堆(Java Heap)是 Java 虚拟机所管理的内存中最大的一块,也被称为 “GC堆”,是被所有线程共享的一块内存区域,在虚拟机启动时被创建

  • 唯一目的就是储存对象实例和数组(JDK7 已把字符串常量池和类静态变量移动到 Java 堆),几乎所有的对象实例都会存储在堆中分配。随着 JIT 编译器发展,逃逸分析、栈上分配、标量替换等优化技术导致并不是所有对象都会在堆上分配。

  • Java 堆是垃圾收集器管理的主要区域。堆内存分为新生代 (Young) 和老年代 (Old) ,新生代 (Young) 又被划分为三个区域:Eden、From Survivor、To Survivor。

5.2 堆内存溢出

如果 Java 堆尝试扩展内存的时候无法申请到足够的内存,那 Java 虚拟机将抛出一个 OutOfMemoryError 异常。

问题:既然有垃圾回收机制,为何还会出现堆内存溢出的情况呢?

解答:垃圾回收机制是回收不被使用的对象,当对象被引用或间接引用时,就不会被垃圾回收,导致内存占用越来越大,从而出现内存溢出。

示例代码

/**
 * -Xmx8m  调整堆内存最大为8m
 */
public class Demo {

    public static void main(String[] args) {
        int i = 0;
        try {
            List<Object> list = new ArrayList<>();
            String a = "hello";
            while (true) {
                list.add(a);
                a = a + a;
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(i);
        }
    }

}

输出

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 com.loksail.learndemo.Demo.main(Demo.java:18)
17

通过调整最大堆内存为8m,list在添加到第18个元素时,就出现了内存溢出,主要原因是程序中list集合一直处于使用中,且不断创建的String对象同样被list集合引用,从而无法被垃圾回收,导致list集合所占内存越来越大及String对象越来越多,从而抛出OutOfMemoryError异常。

5.3 堆内存诊断

5.3.1 jps工具

查看当前系统中有哪些java进程

[root@iZwz9d10hr1juhw84f6r31Z ~]# jps
1237 AgentDaemon
27773 es-yxfbp-main-1.0.0.jar
2974 Jps

5.3.2 jmap工具

  • 查看当前堆内存占用情况

    jmap -heap 进程id
    

    示例代码

    public class Demo {
    
      public static void main(String[] args) throws InterruptedException {
          System.out.println("1...........");
          Thread.sleep(20000L);
          byte[] bytes = new byte[1024 * 1024 * 10];  //10m
          System.out.println("2...........");
          Thread.sleep(10000L);
          bytes = null;
          System.gc();
          System.out.println("3...........");
          Thread.sleep(100000000L);
      }
    
    }
    

    查看当前进程号

    D:\JWF\Gitee\learn-demo\src\main\java\com\loksail\learndemo>jps
    15380 Demo
    12840 Launcher
    17000 Jps
    3928
    10012 Launcher
    

    当打印1………..后的内存输出

    D:\JWF\Gitee\learn-demo\src\main\java\com\loksail\learndemo>jmap -heap 15380
    Attaching to process ID 15380, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.221-b11
    
    using 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 Generation
    Eden Space:
     capacity = 66584576 (63.5MB)
     used     = 6658584 (6.350120544433594MB)
     free     = 59925992 (57.149879455566406MB)
     10.00018983375369% used
    From Space:
     capacity = 11010048 (10.5MB)
     used     = 0 (0.0MB)
     free     = 11010048 (10.5MB)
     0.0% used
    To Space:
     capacity = 11010048 (10.5MB)
     used     = 0 (0.0MB)
     free     = 11010048 (10.5MB)
     0.0% used
    PS Old Generation
     capacity = 177733632 (169.5MB)
     used     = 0 (0.0MB)
     free     = 177733632 (169.5MB)
     0.0% used
    
    3170 interned Strings occupying 260168 bytes.
    

    当打印2………..后的内存输出

    D:\JWF\Gitee\learn-demo\src\main\java\com\loksail\learndemo>jmap -heap 15380
    Attaching to process ID 15380, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.221-b11
    
    using 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 Generation
    Eden Space:
     capacity = 66584576 (63.5MB)
     used     = 17144360 (16.350135803222656MB)
     free     = 49440216 (47.149864196777344MB)
     25.748245359405757% used
    From Space:
     capacity = 11010048 (10.5MB)
     used     = 0 (0.0MB)
     free     = 11010048 (10.5MB)
     0.0% used
    To Space:
     capacity = 11010048 (10.5MB)
     used     = 0 (0.0MB)
     free     = 11010048 (10.5MB)
     0.0% used
    PS Old Generation
     capacity = 177733632 (169.5MB)
     used     = 0 (0.0MB)
     free     = 177733632 (169.5MB)
     0.0% used
    
    3171 interned Strings occupying 260232 bytes.
    

    当打印3………..后的内存输出

    D:\JWF\Gitee\learn-demo\src\main\java\com\loksail\learndemo>jmap -heap 15380
    Attaching to process ID 15380, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.221-b11
    
    using 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 Generation
    Eden Space:
     capacity = 66584576 (63.5MB)
     used     = 1331712 (1.27001953125MB)
     free     = 65252864 (62.22998046875MB)
     2.0000307578740157% used
    From Space:
     capacity = 11010048 (10.5MB)
     used     = 0 (0.0MB)
     free     = 11010048 (10.5MB)
     0.0% used
    To Space:
     capacity = 11010048 (10.5MB)
     used     = 0 (0.0MB)
     free     = 11010048 (10.5MB)
     0.0% used
    PS Old Generation
     capacity = 177733632 (169.5MB)
     used     = 1083488 (1.033294677734375MB)
     free     = 176650144 (168.46670532226562MB)
     0.6096133791943216% used
    
    3157 interned Strings occupying 259256 bytes.
    

    分析

    在输出的内存信息中,Heap Configuration为程序的堆设置,Heap Usage为堆使用情况,我们最主要关注的是Eden Space的使用情况

  • 在打印1之后,使用的堆内存为6.350120544433594MB,此时并没有对象创建

  • 在打印2之后,使用的堆内存为16.350135803222656MB,多增加10M左右是因为代码中创建了10M的byte数组

  • 在打印3之后,使用的堆内存为1.27001953125MB,因为此时byte数组置为空,对应的对象空间不再使用,此时调用垃圾回收将会回收这部分内存,从而使堆内存的使用下降。

  • 生成堆内存转储快照

    jmap -dump:format=b,file=文件名称 进程号
    如:jmap -dump:format=b,file=D://demo.hprof 13777
    

    将堆内存dump下来后,使用jvisualvm加载文件,同样可以分析堆内存占用情况

5.3.3 jconsole工具

jconsole

执行之前的Demo程序,通过jconsole可以明显观察到堆内存的变化,同时jconsole可以观察线程及CPU的相关信息,也可手动进行垃圾回收。

5.3.4 jvisualvm工具

jvisualvm

此工具与jconsole功能相似,但是其可通过堆dump来查看堆内存的具体占用情况

示例代码

public class Demo {

    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(100000000);
    }

    static class Student {
        private byte[] bytes = new byte[1024 * 1024];;
    }

}

通过jvisualvm可看到当前堆内存信息,堆内存占用229m左右

点击执行垃圾回收后,堆内存占用218m左右

可以看到堆内存占用并没有减少多少,如果不分析代码,如何来分析堆内存的具体占用信息呢?

使用jvisualvm的堆dump

通过右侧的查看大小最大的对象可以看到,ArrayList对象占用堆内存最大,点击查看

可以看到,ArrayList对象占用了200m左右的内存,其size为200,集合中元素为Student对象,大小为1m,主要是byte数组占用了1m。也就是存储了200Student对象的list集合占用了200m的堆内存,再结合代码分析可知,由于ArrayList对象仍在方法的作用域中,仍被虚拟机栈的局部变量表引用,从而不会被垃圾回收,从而导致堆内存占用不会下降。

欢迎关注公众号,后续文章更新通知,一起讨论技术问题 。

公众号二维码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值