深入理解JVM(八)——java堆分析
上一节介绍了针对JVM的监控工具,包括JPS可以查看当前所有的java进程,jstack查看线程栈可以帮助你分析是否有死锁等情况,jmap可以导出java堆文件在MAT工具上进行分析等等。这些工具都非常有用,但要用好他们需要不断的进行实践分析。本文将介绍使用MAT工具进行java堆分析的案例。
内存溢出(OOM)的原因
我们常见的OOM(OutOfMemoryError)发生的原因不只是堆内存溢出,堆内存溢出只是OOM其中一种情况,OOM还可能发生在元空间、线程栈、直接内存。
下面演示在各个区发生OOM的情况:
堆OOM
public static void main(String[] args)
{
List<Byte[]> list=new ArrayList<Byte[]>();
for(int i=0;i<100;i++){
//构造1M大小的byte数值
Byte[] bytes=new Byte[1024*1024];
//将byte数组添加到list列表中,因为存在引用关系所以bytes数组不会被GC回收
list.add(bytes);
}
}
以上程序设置最大堆内存50M,执行:


显然程序通过循环将占用100M的堆空间,超过了设置的50M,所以发生了堆内存的OOM。
针对这种OOM,解决办法是增加堆内存空间,在实际开发中必要的时候去掉引用关系,使垃圾回收器尽快对无用对象进行回收。
元空间OOM
public static void main(String[] args) throws Exception
{
for(int i=0;i<1000;i++){
//动态创建类
Map<Object,Object> propertyMap = new HashMap<Object, Object>();
propertyMap.put("id", Class.forName("java.lang.Integer"));
CglibBean bean=new CglibBean(propertyMap);
//给 Bean 设置值
bean.setValue("id", new Random().nextInt(100));
//打印 Bean的属性id
System.out.println("id=" + bean.getValue("id"));
}
}
以上代码通过Cglib动态创建class,设置元数据区大小为4M:

由于代码循环创建class,大量的class元数据,存放在元数据区超过了设置的4M空间,因此报元数据区OOM:

解决该OOM的办法是增大MaxMetaspaceSize参数值,或者干脆不设置该参数,在默认情况元空间可使用的内存会受到本地内存的限制。
栈OOM
当创建新的线程时JVM会给每个线程分配栈内存,当创建线程过多,占用的内存也就越多,这种情况下有可能发生OOM:
public static void main(String[] args) throws Exception {
//循环创建线程
for (int i = 0; i < 1000000; i++) {
new Thread(new Runnable() {
public void run() {
try {
//线程sleep时间足够长,保证线程不销毁
Thread.sleep(200000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
System.out.println("created " + i + "threads");
}
}

很明显解决此OOM的办法是减小线程数。
直接内存OOM
public static void main(String[] args) throws Exception {
for (int i = 0; i < 1000000; i++) {
//申请堆外内存,这个内存是本地的直接内存,并非java堆内存
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024*1024*1024);
System.out.println("created "+i+" byteBuffer");
}
}
ByteBuffer的allocateDirect方法可以申请直接内存,当申请的内存超过的本地可用内存时,会报OOM:

解决该OOM的办法是适当使用堆外内存,如有必要可显式执行垃圾回收。(即在代码中执行System.gc();)
MAT工具使用
当java应用出现故障时,我们可能需要使用MAT分析问题,找出问题出现的原因,下面通过一个案例介绍MAT的使用方法:
准备:
我们事先从程序运行环境上使用jmap工具或者jvisualvm导出一个堆快照文件出来。
使用MAT工具打开:

我们发现占用内存最大的对象是AppClassLoader,我们知道AppClassLoader是用来加载应用的类,因此我们进一步查看它引用的对象。

下图显示了AppClassLoader引用的对象空间使用情况,“Shallow Heap”表示浅堆的大小,浅堆就是类自身所占用的空间大小,也就是类本身元数据的大小。“Retained Heap”表示深堆的大小,深堆表示该类以及它引用的其他类所占用空间的总和,也表示该类被垃圾回收后,所能够释放的空间大小。(如果该类被回收了,他引用的对象会变成不可达对象因此也会被回收)

我们随藤摸瓜,继续查看深堆占用最大的对象。

从上图可以看出造成深堆比较大的原因是程序当中包含了一个ArrayList,他里面包含有大量的String对象,并且每个String对象有80216字节大小。
因此针对这个堆的分析基本清楚了,因为程序中包括大量的String对象,而他们又在ArrayList当中,引用关系一直存在,因此无法被垃圾回收,造成OOM。
MAT其他功能说明
除了上述我们使用到的MAT功能外,还有一些功能也是经常用到的。
Histogram:显示每个类使用情况以及占用空间大小。

上图可以看到char[]类,有1026个对象,占用5967480字节的空间,通过上面的分析得出结论是String对象占用了大部分的空间,而Stirng对象内部存放字符使用char[]来存放的,所以这里显示char[]的浅堆大小为5967480字节也是可以理解的。
Thread_overview:显示线程相关的信息。

OQL:通过类似SQL语句的表达式查询对象信息。

上图通过OQL语句查询字符串中匹配123的String对象。
结语
本文首先介绍了java程序中出现OOM的几种情况,然后通过简单的案例介绍了MAT的基本用法。
深入理解JVM(八)——java堆分析的更多相关文章
- 深入理解JVM一java堆分析
上一节介绍了针对JVM的监控工具,包括JPS可以查看当前所有的java进程,jstack查看线程栈可以帮助你分析是否有死锁等情况,jmap可以导出java堆文件在MAT工具上进行分析等等.这些工具都非 ...
- JVM学习--(八)java堆分析
上一节介绍了针对JVM的监控工具,包括JPS可以查看当前所有的java进程,jstack查看线程栈可以帮助你分析是否有死锁等情况,jmap可以导出java堆文件在MAT工具上进行分析等等.这些工具都非 ...
- 性能监控工具以及java堆分析OOM
一.性能监控工具 1.系统性能监控 Linux -确定系统运行的整体状态,基本定位问题所在 -uptime: ------系统时间 ------运行时间(例子中为127天) ------连接数(每 ...
- JVM内核-原理、诊断与优化学习笔记(八):JAVA堆分析
文章目录 内存溢出(OOM)的原因 在JVM中,有哪些内存区间? 堆溢出 永久区 Java栈溢出 直接内存溢出 小问题? MAT使用基础 柱状图显示 支配树 显示线程信息 显示堆总体信息,比如消耗最大 ...
- jvm系列:Java GC 分析
Java GC就是JVM记录仪,书画了JVM各个分区的表演. 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之 ...
- 理解JVM之Java内存区域
Java虚拟机运行时数据区分为以下几个部分: 方法区.虚拟机栈.本地方法栈.堆.程序计数器.如下图所示: 一.程序计数器 程序计数器可看作当前线程所执行的字节码行号指示器,字节码解释器工作时就是通过改 ...
- 面试题:JVM在Java堆中对对象的创建、内存结构、访问方式
一.对象创建过程 1.检查类是否已被加载 JVM遇到new指令时,首先会去检查这个指令参数能否在常量池中定位到这个类的符号引用,检查这个符号引用代表的类是否已被加载.解析.初始化,若没有,则进行类加载 ...
- 深入理解JVM - 1 - Java内存区域划分
作者:梦工厂链接:https://www.jianshu.com/p/7ebbe102c1ae来源:简书简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处. Java与C++之间有一堵 ...
- 深入理解JVM(一)--Java 内存区域
一. 运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域. Java虚拟机所管理的内存将会包括以下几个运行时数据区域: ...
随机推荐
- Scrapy爬取西刺代理ip流程
西刺代理爬虫 1. 新建项目和爬虫 scrapy startproject daili_ips ...... cd daili_ips/ #爬虫名称和domains scrapy genspider ...
- 表达式求值(栈方法/C++语言描述)(三)
代码清单 // calculator.h #ifndef CALCULATOR_H #define CALCULATOR_H #include <stack> #include <s ...
- InputStreamReader读取文件出现乱码
当你读取一个文本文件时,如果是在电脑记事本上面写的,然后保存.使用InputStreamReader读取出来,并且是按照他的编码格式读取出来,发现还是存在乱码,比如文本内容是“你好”,读出来显示是“? ...
- ABP+AdminLTE+Bootstrap Table权限管理系统第九节--AdminLTE模板页搭建
AdminLTE 官网地址:https://adminlte.io/themes/AdminLTE/index2.html 首先去官网下载包下来,然后引入项目. 然后我们在web层添加区域Admin以 ...
- Java Collection(转载)
在 Java2中,有一套设计优良的接口和类组成了Java集合框架Collection,使程序员操作成批的数据或对象元素极为方便.这些接口和类有很多对 抽象数据类型操作的API,而这是我们常用的且在数据 ...
- Java 冒泡排序的实现
实现原理: 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.在这一点,最后的元素应该会是最大的数. 针对所有的元素重复以上的步骤,除 ...
- RabbitMQ入门与使用篇
介绍 RabbitMQ是一个由erlang开发的基于AMQP(Advanced Message Queue)协议的开源实现.用于在分布式系统中存储转发消息,在易用性.扩展性.高可用性等方面都非常的优秀 ...
- HTML5 web存储
既然涉及到HTML5知识,那么必定会存在一定的兼容性问题,这里就涉及到浏览器的支持情况了. 浏览器支持情况: Internet Explorer 8+, Firefox, Opera, Chrome, ...
- Big Data Solution in Azure: Azure Data Lake
https://blogs.technet.microsoft.com/dataplatforminsider/2015/09/28/microsoft-expands-azure-data-lake ...
- SpringMVC过程中@RequestBody接收Json的问题 总是报415
在SpringMVC中用@RequestBody接收Json的问题,总是报415,经过一翻查找 前台js的post: var postdata = '{"title":" ...