上一节介绍了针对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堆分析的更多相关文章

  1. JVM学习六:堆分析

    一.内存溢出(OOM)的原因 在JVM中,有哪些内存区间? 堆溢出 public static void main(String args[]){ ArrayList<byte[]> li ...

  2. 深入理解JVM(八)——java堆分析

    上一节介绍了针对JVM的监控工具,包括JPS可以查看当前所有的java进程,jstack查看线程栈可以帮助你分析是否有死锁等情况,jmap可以导出java堆文件在MAT工具上进行分析等等.这些工具都非 ...

  3. 深入理解JVM一java堆分析

    上一节介绍了针对JVM的监控工具,包括JPS可以查看当前所有的java进程,jstack查看线程栈可以帮助你分析是否有死锁等情况,jmap可以导出java堆文件在MAT工具上进行分析等等.这些工具都非 ...

  4. 性能监控工具以及java堆分析OOM

      一.性能监控工具 1.系统性能监控 Linux -确定系统运行的整体状态,基本定位问题所在 -uptime: ------系统时间 ------运行时间(例子中为127天) ------连接数(每 ...

  5. JVM虚拟机-了解Java堆中对象分配、布局和访问的全过程

    目录 前言 对象的创建 类加载检查 分配内存 内存空间分配方式 指针碰撞 空闲列表 并发时的内存分配 同步处理:CAS 本地线程分配缓冲:TLAB 初始化零值 设置对象头 执行 init 方法 对象的 ...

  6. JVM内核-原理、诊断与优化学习笔记(八):JAVA堆分析

    文章目录 内存溢出(OOM)的原因 在JVM中,有哪些内存区间? 堆溢出 永久区 Java栈溢出 直接内存溢出 小问题? MAT使用基础 柱状图显示 支配树 显示线程信息 显示堆总体信息,比如消耗最大 ...

  7. JVM学习八:常用JVM配置参数

    前面学习的都是和类加载相关的知识,接下来学习的则和GC相关的知识,都是JVM的几个重点块. 零.在IDE的后台打印GC日志: 既然学习JVM,阅读GC日志是处理Java虚拟机内存问题的基础技能,它只是 ...

  8. jvm系列(九):Java GC 分析

    Java GC就是JVM记录仪,书画了JVM各个分区的表演. 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之 ...

  9. JVM学习八-(复习)年轻代、老年代、永久代

    Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象,如下图所示: 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young ).老年代 ( Old).新生代 ...

随机推荐

  1. Cocos2D瓦块地图高清屏(retina)显示比例问题的解决

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 在Cocos2D的游戏编程里,常用到瓦块地图.而cocos2D ...

  2. HDFS追本溯源:体系架构详解

    Hadoop是一个开发和运行处理大规模数据的软件平台,是Apache的一个用Java语言实现开源软件框架,实现在大量计算机组成的集群中对海量数据进行分布式计算.用户可以在不了解分布式底层细节的情况下, ...

  3. 银联在线 网关支付 (JAVA版)

    这一版本的编写是在我上一次博客的基础上写的,有不懂得童鞋可以先看下我的原先在线支付的博客,熟悉下:http://blog.csdn.net/yulei_qq/article/details/45197 ...

  4. 【unix网络编程第三版】阅读笔记(三):基本套接字编程

    unp第三章主要介绍了基本套接字编程函数.主要有:socket(),bind(),connect(),accept(),listen()等. 本博文也直接进入正题,对这几个函数进行剖析和讲解. 1. ...

  5. 重载重写重定义-易混淆概念-C++编译器处理方式

    1.函数重载 1)必须在同一个类中进行. 2)子类无法重载父类的函数,父类同名函数将被名称覆盖 3)重载是在编译期间根据参数类型和个数决定函数调用 2.函数重写 1)必须发生于父类与子类之间 2)并且 ...

  6. 连接器与容器的桥梁——CoyoteAdapter

    如果把整个tomcat内核最高抽象程度模块化,可以看成是由连接器Connector和容器Container组成,连接器负责HTTP请求接收及响应,生成请求对象及响应对象并交由容器处理,而容器则根据请求 ...

  7. OC语言实现中等难度通讯录

    实现中等难度通讯录.需求: 1.定义联系⼈人类Contact.实例变量:姓名(拼⾳音,⾸首字⺟母⼤大写).性别.电话号码. 住址.分组名称.年龄.⽅方法:⾃自定义初始化⽅方法(姓名.电话号码).显⽰示 ...

  8. Python学习笔记 - 数据类型和变量

    Python中有整数和浮点数,表示方法和C语言一样 浮点数也很像,不过Python可以用单引号把字符串括起来 字符串是以单引号'或双引号"括起来的任意文本,比如'abc',"xyz ...

  9. 【Unity Shaders】Diffuse Shading——在Surface Shader中使用properties

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  10. Css的学习之旅-css的选择器(2)

    1.最常用的是派生选择器:eg:ul li{ color:red} 2.id选择器:eg:#id{color:red} 3.类选择器:设置标签的class = "",类似id.用点 ...