15分钟教你快速学会!JVM内存区域异常分析

140 篇文章 1 订阅
83 篇文章 0 订阅

Java虚拟机在执行java程序时会把它所管理的内存会分为若干个不同的数据区域,不同的区域在内存不足时会抛出不同的异常。

接下来将对各区域分别进行分析介绍,内容包括触发各区域OutOfMemoryError异常的代码,以及对其进行排查判定的过程。还会初步涉及几个与内存相关的最基本虚拟机参数。

1、Java堆溢出

Java堆用于存储对象实例,只要不断创建对象并保证对象不会被回收,那么当对象数量到达最大堆的容量限制后就会产生堆内存溢出异常。

测试代码如下:

/**
 * 探究Java堆溢出
 * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 * 将堆最小值(-Xms)与堆最大值(-Xmx)参数设置为一样(20MB),可避免堆自动扩展
 * 
 * -XX:+HeapDumpOnOutOfMemoryError :让虚拟机在出现内存溢出异常时可Dump出
 * 当前的内存堆转储快照以便时候进行分析
 * @author yangtf
 *
 */
public class HeapOOM {
    static class OOMObject{}
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

运行结果:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid18152.hprof ...
Heap dump file created [28008150 bytes in 0.132 secs]

产生上述异常后,异常信息:
java.lang.OutOfMemoryError: Java heap space 。

并且会在代码根路径下生成一个 java_pid18152.hprof 文件,此文件的生成是由于-XX:+
HeapDumpOnOutOfMemoryError 配置的结果。

要解决这个区域的异常,一般是通过内存映像分析工具(如 Memory Analyzer)对Dump出来的堆转储快照进行分析,重点是确认出现了内存泄漏还是内存溢出。在这里插入图片描述
另外我还整理了一些我自己的学习资和其他大厂的面试经验,关注我,私信回复【学习】获取往期Java高级架构资料、源码、笔记、视频,Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术、面试指导!

内存泄漏:对象已经死了,无法通过垃圾收集器进行自动回收,通过找出泄露的代码位置和原因,才好确定解决方案

内存溢出:内存中的对象都还必须存活着,这说明Java堆分配空间不足,检查堆设置大小(-Xmx与-Xms),检查代码是否存在对象生命周期太长、持有状态时间过长的情况

下面介绍用 Memory Analyzer (以下简称MAT ~Tool)进行分析的过程

1)下载与安装:下载链接点击上面 Memory Analyzer。

2)打开分析文件:在根目录下查找 java_pid18152.hprof 文件,并打开。

3)打开后见下图:在这里插入图片描述
从上图可以看到它的大部分功能。

  1. Histogram可以列出内存中的对象,对象的个数以及大小。
  2. Dominator Tree可以列出那个线程,以及线程下面的那些对象占用的空间。
    3.Top consumers通过图形列出最大的object。
    4.Leak Suspects通过MA自动分析泄漏的原因

Histogram如下图:

Objects:类的对象的数量。

Shallow size:就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。

Retained size:是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。

可以看出Object类、Thread类、HeapOOM类占用了绝大部分空间在这里插入图片描述
Dominator Tree如下图:

可以看出 Thread 线程占用了很大空间在这里插入图片描述
Top consumers如下图:

这里显示了内存中最大的对象有哪些,他们对应的类是哪些,类加载器classloader是哪些。

有些时候,我们在这里就可以看到代码泄露的位置。

图中每个选项均可展开,显示详细图标信息。在这里插入图片描述
Leak Suspects如下图:

深色区域被怀疑出现问题,深色区域占用内存量过大。

后面的描述告诉我们线程 Java.lang.Thread 占用的大量空间,并指出 system class loader 加载的 Java.lang.Object[] 实例在内存中聚集。

MAT通过简单的报告就分析出了问题所在。
 在这里插入图片描述
点击上图中 【Details】

可以看出积累了过多的 OOMObject ,选择一个左击==>List objects==>with outgoing rederences,查看Thread都应用了什么对象。在这里插入图片描述
现在看到引用的对象:在这里插入图片描述
可以看到对象是引起问题的关键,在对象上右击==>Path to GC Roots==>exclude weak reference

从下图看出在 java.lang.Thread 中保存了 OOMObject的引用。所以可以得出结论是由于运行时持有大量的 OOMObject 引起了内存泄漏。在这里插入图片描述
以上是通过MAT分析测试程序查找内存溢出的原因。

堆溢出解决方式:

内存泄漏:掌握内存泄露对象信息及GC Roots引用链信息,就可以比较准确地定位出泄漏代码的位置。

内存溢出:检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过程、持有状态时间过长的情况,尝试减少程序运行期间的内存消耗。

2、虚拟机栈和本地方法栈

此次测试用的为HotSpot虚拟机,在此虚拟机中不区分虚拟机栈和本地方法栈,因此虽然-Xoss参数(设置本地方法栈大小)存在,但实际上是无效的,栈容量只由-Xss参数设定。

查看虚拟机版本的方式为:在这里插入图片描述
这两个栈中均有两种异常:StackOverflowError 和 OutOfMemoryError 在此分别进行介绍:

在单线程下,无论由于栈帧太大还是虚拟机容量太小,当内存无法分配的时候,抛出的都是 StackOverflowError 异常。

StackOverflowError 代码:

/**
 * VM Args: -Xss128k
 * @author yangtf
 *
 */
public class JavaVMStackOF {
    private int stackLength = 1;
    
    // 不断调用,不断压栈
    public void stackLeak(){
        // 记录调用数量
        stackLength++;
        stackLeak();
    }
    
    public static void main(String[] args) {
        JavaVMStackOF oom = new JavaVMStackOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length: " + oom.stackLength);
            throw e;
        }
    }
}

在这里插入图片描述
此处限制了栈内存的大小,不能满足栈请求的深度,弹出了 StackOverflowError 异常

解决方法:增加栈深度,或尽量减少不必要的调用。

OutOfMemoryError 异常:动态扩展申请不到足够的内存。

此异常并不容易实现,如果在单线程下引起的为 StackOverflowError 异常,若是在多线程下虽然能引起 OutOfMemoryError 异常,但是这与栈空间是否足够大并不存在联系(此处应该是能否动态扩展申请到足够的内存,栈空间不够大,所以申请不到足够的内存)。多线程情况下,为每个线程的栈分配的内存越大,反而越容易引起内存溢出。如果是由于建减立过多线程导致内存溢出,只能通过少减少最大堆容量和减少栈容量来换取更多线程。

此处给出代码:

/**
 * VM Args:-Xss2m
 * 在Windows平台的虚拟机中,Java线程映射到操作系统内核线程上,
 * 代码执行时可能会导致操作系统假死
 * 
 * 通过不断地创建运行着的线程,引起异常
 * @author yangtf
 *
 */
public class JavaVMStackOOM {
    private void dontStop(){
        while (true) {
        }
    }
    
    public void stackLeakByThread(){
        while (true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }
    
    public static void main(String[] args) {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}
```3、运行时常量池和方法区

  运行时常量池属于方法区,所以此处一起测试。

  常量池溢出代码,由于JDK1.7开始逐步“去永久代”,所以此处测试代码应在JDK1.7以下的版本中运行:

/**

  • VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
  • -XX:PermSize 和 -XX:MaxPermSize 限制方法区大小,也就限制了常量池的大小
  • @author yangtf

*/
public class RunConstantPoolOOM {
public static void main(String[] args) {
List list = new ArrayList();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}

在这里插入图片描述

另外我还整理了一些我自己的学习资和其他大厂的面试经验,关注我,私信回复【学习】获取往期Java高级架构资料、源码、笔记、视频,Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术、面试指导!

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值