03_堆+MAT工具

堆栈方法区的关系:

HotSpot是使用指针的方式来访问对象:

  • Java堆中会存放访问类元数据的地址

  • reference存储的就是对象的地址

三种JVM:

  • Sun公司的HotSpot
  • BEA公司的JRockit
  • IBM公司的J9 VM

一、堆体系概述

Java7之前

Heap 堆:一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,堆内存逻辑上分为三部分:

  • Young Generation Space 新生区 Young/New

  • Tenure generation space 养老区 Old/Tenure

  • Permanent Space 永久区 Perm

也称为:新生代(年轻代)、老年代、永久代(持久代)。

其中JVM堆分为新生代和老年代

1. 新生区

新生区是对象的诞生、成长、消亡的区域,一个对象在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分: 伊甸区(Eden space)和幸存者区(Survivor pace) ,所有的对象都是在伊甸区被new出来的。幸存区有两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存 0区。若幸存 0区也满了,再对该区进行垃圾回收,然后移动到 1 区。那如果1 区也满了呢?再次垃圾回收,满足条件后再移动到养老区。若养老区也满了,那么这个时候将产生MajorGC(FullGC),进行养老区的内存清理。若养老区执行了Full GC之后发现依然无法进行对象的保存,就会产生OOM异常“OutOfMemoryError”。

如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:

(1)Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。

(2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。

2. 老年代

经历多次GC仍然存在的对象(默认是15次),老年代的对象比较稳定,不会频繁的GC

3. 永久代

永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。

如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致Perm区被占满。

Jdk1.6及之前: 有永久代,常量池1.6在方法区

Jdk1.7: 有永久代,但已经逐步“去永久代”,常量池1.7在堆

Jdk1.8及之后: 无永久代,常量池1.8在元空间(Metaspace)

实际而言,方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等等,虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。

对于HotSpot虚拟机,很多开发者习惯将方法区称之为“永久代(Parmanent Gen)” ,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。

常量池(Constant Pool)是方法区的一部分,Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,这部分内容将在类加载后进入方法区的运行时常量池中存放。

二、堆参数调优入门

均以JDK1.8+HotSpot为例

jdk1.7:

jdk1.8:

-XX:MetaspaceSize 这个参数是初始化的Metaspace大小

-XX:MaxMetaspceSize 指定元数据区域最大的大小。

1. 常用JVM参数

怎么对jvm进行调优?通过参数配置

参数 备注
-Xms 初始堆大小。只要启动,就占用的堆大小,默认是内存的1/64
-Xmx 最大堆大小。默认是内存的1/4
-Xmn 新生区堆大小
-XX:+PrintGCDetails 输出详细的GC处理日志

java代码查看jvm堆的默认值大小:

Runtime.getRuntime().maxMemory()   // 堆的最大值,默认是内存的1/4
Runtime.getRuntime().totalMemory()  // 堆的当前总大小,默认是内存的1/64

2. 怎么设置JVM参数

程序运行时,可以给该程序设置jvm参数,不同的工具设置方式不同。

如果是命令行运行:

java -Xmx50m -Xms10m HeapDemo

eclipse运行的设置方式如下:  

idea运行时设置方式如下:

3. 查看堆内存详情

public class Demo2 {
    public static void main(String[] args) {

        System.out.print("最大堆大小:");
        System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");
        System.out.print("当前堆大小:");
        System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
        System.out.println("==================================================");

        byte[] b = null;
        for (int i = 0; i < 10; i++) {
            b = new byte[1 * 1024 * 1024];
        }
    }
}

执行前配置参数:-Xmx50m -Xms30m -XX:+PrintGCDetails

执行:看到如下信息

新生代和老年代的堆大小之和是Runtime.getRuntime().totalMemory()

4. GC演示

public class HeapDemo {

    public static void main(String args[]) {

        System.out.println("=====================Begin=========================");
        System.out.print("最大堆大小:Xmx=");
        System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");

        System.out.print("剩余堆大小:free mem=");
        System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");

        System.out.print("当前堆大小:total mem=");
        System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");

        System.out.println("==================First Allocated===================");
        byte[] b1 = new byte[5 * 1024 * 1024];
        System.out.println("5MB array allocated");

        System.out.print("剩余堆大小:free mem=");
        System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");

        System.out.print("当前堆大小:total mem=");
        System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");

        System.out.println("=================Second Allocated===================");
        byte[] b2 = new byte[10 * 1024 * 1024];
        System.out.println("10MB array allocated");

        System.out.print("剩余堆大小:free mem=");
        System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");

        System.out.print("当前堆大小:total mem=");
        System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");

        System.out.println("=====================OOM=========================");
        System.out.println("OOM!!!");
        System.gc();
        byte[] b3 = new byte[40 * 1024 * 1024];
    }
}

jvm参数设置成最大堆内存100M,当前堆内存10M:-Xmx100m -Xms10m -XX:+PrintGCDetails

再次运行,可以看到minor GC和full GC日志:

5. OOM演示

把上面案例中的jvm参数改成最大堆内存设置成50M,当前堆内存设置成10M,执行测试: -Xmx50m -Xms10m

====================Begin=========================
最大堆大小:Xmx=44.5M
剩余堆大小:free mem=8.186859130859375M
当前堆大小:total mem=9.5M
=================First Allocated=====================
5MB array allocated
剩余堆大小:free mem=3.1868438720703125M
当前堆大小:total mem=9.5M
================Second Allocated====================
10MB array allocated
剩余堆大小:free mem=3.68682861328125M
当前堆大小:total mem=20.0M
=====================OOM=========================
OOM!!!
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.atguigu.demo.HeapDemo.main(HeapDemo.java:40)

实际开发中怎么定位这种错误信息?MAT工具

三、MAT工具

安装方式:eclipse插件市场下载

1. MAT工具的使用

运行参数:-Xmx30m -Xms10m -XX:+HeapDumpOnOutOfMemoryError

重新刷新项目:看到dump文件

打开:

2. idea分析dump文件

把上例中运行参数改成:

-Xmx50m -Xms10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\tmp 

-XX:HeapDumpPath:生成dump文件路径。

再次执行:生成C:\tmp\java_pid20328.hprof文件

生成的这个文件怎么打开?jdk自带了该类型文件的解读工具:jvisualvm.exe

双击打开:

文件-->装入-->选择要打开的文件即可

装入后:

四、常用命令行(了解)

查看java进程:jps -l

查看某个java进程所有参数:jinfo 进程号

查看某个java进程总结性垃圾回收统计:jstat -gc 进程号

S0C:幸存者0区的大小
S1C:幸存者1区的大小
S0U:幸存者0区的使用大小
S1U:幸存者1区的使用大小
EC:伊甸区的大小
EU:伊甸区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

猜你喜欢

转载自blog.csdn.net/qq_45037155/article/details/130880976