前言:

本文内容:HotSpot和堆、新生区,永久区,堆内存调优、使用JPofiler工具分析OOM原因

推荐免费JVM快速入门视频:【狂神说Java】JVM快速入门篇_哔哩哔哩_bilibili

HotSpot和堆

三种JVM

  • Sun HotSpot
  • BEA JRockit
  • IBM J9 VM

堆(Heap),一个JVM只有一个堆内存,堆内存的大小是可以调节的。

类加载器读取了类文件后,会把类,方法,常量和变量等放到堆中,保存所有引用类型的真实对象。

堆内存中还要细分为三个区域:

  • 新生区
  • 老年区
  • 永久区

32

假设内存满了,OOM,堆内存不够。

在JDK8以前,永久存储区改名为元空间

新生区,永久区,堆内存调优

新生区

  • 类诞生、成长的地方,甚至是死亡
  • 伊甸园(对象实在伊甸园区new出来的)
  • 幸存者区(0,1)

老年区

  • 新生区满了之后,会执行一次Full GC,能够留下来的对象会存放到老年区
  • 99%的对象都是临时对象,一般不会存放到老年区

永久区

这个区域常驻内存,用来存放JDK自身携带的Class对象。Interface元数据(存储Java运行时环境)

不存在垃圾回收,关闭虚拟机后被释放

  • JDK 1.6以前:永久代,常量池在方法区中
  • JDK 1.7:永久代,慢慢去永久代,常量池在堆中
  • JDK 1.8之后:无永久代,常量池在元空间

31

堆内存调优

JVM最大内存和初始化内存

测试电脑为8G运行内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package demo02;

/**
* @author Joker大雄
* @data 2022/8/13 - 9:45
**/
public class Demo02 {
public static void main(String[] args) {
// 返回虚拟机使用的最大内存
long max = Runtime.getRuntime().maxMemory();
// 返回JVM使用的初始化总内存
long total= Runtime.getRuntime().totalMemory();
System.out.println("max:"+max+"字节 "+max/1024/1024+"MB");
System.out.println("total:"+total+"字节 "+total/1024/1024+"MB");
// 默认:分配的总内存是电脑内存的1/4,而初始化的内存:1/65
}
}

运行结果

1
2
3
4
max:1888485376字节 1801MB
total:128974848字节 123MB

Process finished with exit code 0

内存调优

新版IDEA需要手动选择显示Add VM options

30

VM options中输入

1
-Xms1024m -Xmx1024m -XX:+PrintGCDetails

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
max:1029177344字节 981MB
total:1029177344字节 981MB

Heap
PSYoungGen total 305664K, used 20971K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
eden space 262144K, 8% used [0x00000000eab00000,0x00000000ebf7afb8,0x00000000fab00000)
from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
to space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
ParOldGen total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
Metaspace used 3124K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 342K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

OOM问题解决:

  • 尝试扩大堆内存看结果
  • 分析内存,看哪个地方出现了问题

计算:

(新生代+老年代)/1024:(305664K+699392K)/1024= 981MB

得出:元空间逻辑上存在,物理上不存在

使用JProfiler工具分析OOM原因

快照分析工具:

Eclipse:MAT

IDEA:JProfiler

概述

JProfiler是一个重量级的JVM监控工具,提供对JVM精确监控,其中堆遍历、CPU剖析、线程剖析看成定位当前系统瓶颈的得力工具。

作用

  • 获得堆中数据

  • 可以统计压测过程中JVM的监控数据,定位性能问题。

JProfiler安装:

  1. 在IDEA的Plugins中搜索JProfiler安装
  2. JProfiler Download下载客户端安装
  3. 注册码:JProfiler 9版本注册码
  4. 打开IDEA,的Settings/Tools/JProfiler,选择你安装JProfiler的路径/bin/jprofiler.exe

使用JProfiler分析

  1. 编写会出现OOM的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package demo02;

    import java.util.ArrayList;

    /**
    * @author Joker大雄
    * @data 2022/8/13 - 10:50
    **/
    public class Demo03 {
    byte[]array = new byte[1*1024*1024]; //1m

    public static void main(String[] args) {
    ArrayList<Demo03> list = new ArrayList<>();
    int count = 0;

    try {
    while(true){
    list.add(new Demo03());
    count+=1;
    }
    } catch (Exception e) {
    System.out.println("count:"+count);
    e.printStackTrace();
    }
    }
    }
  2. 配置VM options

    1
    -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
  3. 运行代码

    1
    2
    3
    4
    5
    6
    7
    8
    java.lang.OutOfMemoryError: Java heap space
    Dumping heap to java_pid11512.hprof ...
    Heap dump file created [7713362 bytes in 0.010 secs]
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at demo02.Demo03.<init>(Demo03.java:12)
    at demo02.Demo03.main(Demo03.java:20)

    Process finished with exit code 1
  4. 根目录会生成这个文件

    如果IDEA没显示就去项目下找

    29

  5. 双击打开

    28

  6. 分析错误

    27

  7. 小结

    1
    2
    3
    4
    -Xms:设置初始化内存大小 1/64(默认)
    -Xmx:设置最大内存大小 1/4(默认)
    -XX:+PrintGCDetails // 打印GC垃圾回收信息
    -XX:+HeapDumpOnOutOfMemoryError // OOM Dump