jvm内存溢出性能调优

leifonlyone      2022-04-25     499

关键词:

常用工具及命令

jps jstat Top jstack jmap mat工具

 top -Hp pid可以查看某个进程的线程信息

-H 显示线程信息,-p指定pid

jps:可以列出正在运行的虚拟机进程,并显示虚拟机执行主类名称及进程pid 如:jps -l pid

Jstack命令

jstack是java虚拟机自带的一种堆栈跟踪工具。用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。

线程的几种状态:

NEW:未启动的。不会出现在Dump中。

RUNNABLE:在虚拟机内执行的。运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。

BLOCKED:受阻塞并等待监视器锁。被某个锁(synchronizers)給block住了。

WATING:无限期等待另一个线程执行特定操作。等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。

TIMED_WATING:有时限的等待另一个线程的特定操作。和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。

TERMINATED:已退出的。

技术图片

 

例一:

public class MyThread implements Runnable{

    public void run() {

        synchronized(this) {

            for (int i = 0; i < 1; i--) {

                System.out.println(Thread.currentThread().getName() +Thread.currentThread().getId()+ " do loop " + i);

            }

        }

    }

    public static void main(String[] args) {

        MyThread t1 = new MyThread();

        Thread ta = new Thread(t1, "A");

        Thread tb = new Thread(t1, "B");

        ta.start();

        tb.start();

    }

}

 

jstack -l 6212 > d:11.jstack.txt

 

2019-06-25 23:00:05

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.201-b09 mixed mode):

 

"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000002b7e800 nid=0xb80 waiting on condition [0x0000000000000000]

   java.lang.Thread.State: RUNNABLE

 

"B" #12 prio=5 os_prio=0 tid=0x000000001b4b1800 nid=0x1d0 waiting for monitor entry [0x000000001c12f000]

   java.lang.Thread.State: BLOCKED (on object monitor)

        at MyThread.run(MyThread.java:12)

        - waiting to lock <0x0000000702c02720> (a MyThread)

        at java.lang.Thread.run(Thread.java:748)

 

"A" #11 prio=5 os_prio=0 tid=0x000000001b4b1000 nid=0x1cec runnable [0x000000001c02f000]

   java.lang.Thread.State: RUNNABLE

        at java.io.FileOutputStream.writeBytes(Native Method)

        at java.io.FileOutputStream.write(FileOutputStream.java:326)

        at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)

        at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)

        - locked <0x0000000702c407f8> (a java.io.BufferedOutputStream)

        at java.io.PrintStream.write(PrintStream.java:482)

        - locked <0x0000000702c04820> (a java.io.PrintStream)

        ...

        at MyThread.run(MyThread.java:13)

        - locked <0x0000000702c02720> (a MyThread)

        at java.lang.Thread.run(Thread.java:748)

 

dump时间,虚拟机的信息 在top命令中,已经获取到了占用cpu资源较高的线程pid,将该pid转成16进制的值,在thread dump中每个线程都有一个nid,找到对应的nid即可;

此处是我们最为关心的地方,也是通过stack trace查找问题的地方,每个item的具体含义是

-Thread name: "A"

-线程优先级: prio=10

-java线程的identifier:tid=0x09b7b400

-native线程的identifier: nid=0x12f2

-线程的状态: in Object.wait()  java.lang.Thread.State: TIMED_WAITING (on object monitor) 

-线程栈起始地址:[0xb30f9000]

例二:

public void run() {

        synchronized(this) {

            for (int i = 0; i < 1; i--) {

                //System.out.println(Thread.currentThread().getName() +Thread.currentThread().getId()+ " do loop " + i);

                try {

                    this.wait();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        }

    }

 

2019-06-25 22:52:25

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.201-b09 mixed mode):

 

"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x000000000252e800 nid=0x3e5c waiting on condition [0x0000000000000000]

   java.lang.Thread.State: RUNNABLE

 

"B" #12 prio=5 os_prio=0 tid=0x000000001b19b000 nid=0x4d38 in Object.wait() [0x000000001bdee000]

   java.lang.Thread.State: WAITING (on object monitor)

        at java.lang.Object.wait(Native Method)

        - waiting on <0x000000078105ba78> (a MyThread)

        at java.lang.Object.wait(Object.java:502)

        at MyThread.run(MyThread.java:15)

        - locked <0x000000078105ba78> (a MyThread)

        at java.lang.Thread.run(Thread.java:748)

 "A" #11 prio=5 os_prio=0 tid=0x000000001b198000 nid=0x2b18 in Object.wait() [0x000000001bcef000]

   java.lang.Thread.State: WAITING (on object monitor)

        at java.lang.Object.wait(Native Method)

        - waiting on <0x000000078105ba78> (a MyThread)

        at java.lang.Object.wait(Object.java:502)

        at MyThread.run(MyThread.java:15)

        - locked <0x000000078105ba78> (a MyThread)

        at java.lang.Thread.run(Thread.java:748)

 使用MAT工具进行内存泄露分析

jmap命令

既然要分析内存,首先需要获取可供分析的原始内存文件,这就需要用到jmap命令。jmap是JDK自带的一种用于生成内存镜像文件的工具,通过该工具,开发人员可以快速生成dump文件。开发人员可以使用命令“jmap -help”查看jmap的常用命令

获取dump文件有两种方法:

其一,通过上面介绍的 jmap工具生成,可以生成任意一个java进程的dump文件;

其二,通过配置JVM参数生成,选项“-XX:+HeapDumpOnOutOfMemoryError ”和-“XX:HeapDumpPath”所代表的含义就是当程序出现OutofMemory时,将会在相应的目录下生成一份dump文件,而如果不指定选项“XX:HeapDumpPath”则在当前目录下生成dump文件。

虽然有两种方式获取dump文件,但是考虑到生产环境中几乎不可能在线对其进行分析,大都是采用离线分析,因此使用jmap+MAT工具是最常见的组合。

模拟堆溢出场景:设置如:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:dump

 

@Test

    public void testOOM() {

        List<Person> users = new ArrayList<Person>();

        while (true) {

            users.add(new Person("", 1));

        }

    }

Overview选项:

当成功启动MAT后,通过菜单选项“File->Open heap dump...”打开指定的dump文件后,将会生成Overview选项,如下所示:

 技术图片

 

 在Overview选项中,以饼状图的形式列举出了程序内存消耗的一些基本信息,其中每一种不同颜色的饼块都代表了不同比例的内存消耗情况。

Dominator Tree选项:

如果说需要定位内存泄露的代码点,我们可以通过Dominator Tree菜单选项来进行排查。Dominator Tree提供了一个列表。Dominator Tree:对象之间dominator关系树。如果从GC Root到达Y的的所有path都经过X,那么我们称X dominates Y,或者X是Y的Dominator 。Dominator Tree由系统中复杂的对象图计算而来。从MAT的dominator tree中可以看到占用内存最大的对象以及每个对象的dominator,如下所示:

 技术图片

 

 可以进一步查看内层应用情况,同时还可以看到对应类对象的属性值,如下所示:

 技术图片

Histogram选项:

可以通过Histogram分析,Histogram列出了每个类的实例数量,点击Action下的Histogram,得到以下结果:

 技术图片

如果需要查询特性的某个类,我们可以在第一行输入类名或者关键词进行正则匹配查找,如查找“erson”:

 技术图片

Path to GC Root:

    查看一个对象到RC Roots的引用链

    通常在排查内存泄漏的时候,我们会选择exclude all phantom/weak/soft etc.references,意思是查看排除虚引用/弱引用/软引用等的引用链,因为被虚引用/弱引用/软引用的对象可以直接被GC给回收,我们要看的就是某个对象否还存在Strong 引用链(在导出HeapDump之前要手动出发GC来保证),如果有,则说明存在内存泄漏,然后再去排查具体引用

 技术图片

总结:遇到线上问题,首先确认排查问题的思路:

查看日志

    查看CPU情况 top

    查看TCP情况 netstat

    查看java线程,jstack

    查看java堆,jmap

通过MAT分析堆文件,寻找无法被回收的对象

 

问题排查

1.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取比较多条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
2.检查代码中是否有死循环或递归调用。 

3.检查是否有大循环重复产生新对象实体。 

4.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

5.结合内存查看工具动态查看内存使用情况

问题解决

1.检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。对代码进行走查和分析,找出可能发生内存溢出的位置。修正。

2.修改JVM启动参数,直接增加内存。

第一个异常:设置堆的方法是通过-Xms(堆的最小值),-Xmx(堆的最大值)

第一个异常:设置栈大小的方法是设置-Xss参数-Xss100k

第三个异常:设置元空间-XX:PermSize和-XX:MaxPermSize参数(java8中去掉了PermGen(1.8前称永久代,主要用来 存放Class的静态信息,Main方法信息,常量信息,静态方法和变量信息,共享变量等信息 参数设置示例: -XX:PermSize=5M -XX:MaxPermSize=7M) 改为 Metaspace 默认情况下,类元数据只受可用的本地内存限制(容量取决于是32位或是64位操作系统的可用虚拟内存大小) 新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小。如果没有指定这个参数,元空间会在运行时根据需要动态调整)

总结

内存查看工具有许多,比较有名的有:mt、Optimizeit Profiler、JProbe Profiler、JinSight和Java1.5的Jconsole、visualVM(BTrace动态日志,不用修改服务可以加日志)等。它们的基本工作原理大同小异,都是监测Java程序运行时所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分析、可视化。开发人员可以根据这些信息判断程序是否有内存泄漏问题。一般来说,一个正常的系统在其启动完成后其内存的占用量是基本稳定的,而不应该是无限制的增长的。持续地观察系统运行时使用的内存的大小,可以看到在内存使用监控窗口中是基本规则的锯齿形的图线,如果内存的大小持续地增长,则说明系统存在内存泄漏问题。通过间隔一段时间取一次内存快照,然后对内存快照中对象的使用与引用等信息进行比对与分析,可以找出是哪个类的对象在泄漏。

性能测试三十六:内存溢出和jvm常见参数及jvm参数调优

堆内存溢出:此种溢出,加内存只能缓解问题,不能根除问题,需优化代码堆内存中存在大量对象,这些对象都有被引用,当所有对象占用空间达到堆内存的最大值,就会出现内存溢出OutOfMemory:Javaheapspace永久代溢出如果发生,... 查看详情

jvm性能监控与gc调优(代码片段)

...2.1类加载信息2.2垃圾回收信息2.3JIT编译信息3.jmap+MAT分析内存溢出[实战]3.1模拟内存溢出3.2导出内存影像文件3.3使用MAT分析dump文件4.jstack分析死循环与死锁[实战]三基于JVisualVM的可视化监控四基于 查看详情

jvm内存调优(代码片段)

...程序的特性来更改GC行为控制JVM堆栈大小.一般来说,JVM在内存分配上不需要你修改,(举例)但是当你的程序新生代对象在某个时间段产生的比较多的时候,就需要控制新生代的堆大小.同时,还要需要控制总的JVM大小避免内存溢出控制JV... 查看详情

深入理解jvm之知识体系(代码片段)

一.JVM知识体系组成1.JVM内存区域(1)组成部分(2)各部分的作用2.JVM内存溢出(1)内存溢出(2)栈溢出(3)内存区域与溢出的关联3.垃圾回收机制(1)对象存活状态判断(2)垃圾收集算法(3)垃圾收集器(4)垃圾回收过程4.类加载(1)类的生命周期(2)类... 查看详情

jvm调优概述

...步骤4.调优指标1.调优背景生产中的问题生产环境发生了内存溢出该如何处理?生产环境应该给服务器分配多少内存合适?如何对垃圾回收器的性能进行调优?生产环境CPU负载过高时该如何处理?生产环境应该给应... 查看详情

jvm调优概述

...步骤4.调优指标1.调优背景生产中的问题生产环境发生了内存溢出该如何处理?生产环境应该给服务器分配多少内存合适?如何对垃圾回收器的性能进行调优?生产环境CPU负载过高时该如何处理?生产环境应该给应... 查看详情

jvm原理及性能调优系列(自动内存管理机制)

JAVA内存区域和内存溢出异常1、JAVA运行时数据区如下图    程序计数器:当前线程正在执行的代码的行号指示器。是线程私有的。它是JAVA虚拟机唯一没有指定OutOfMerroryError异常的区域。  虚拟机栈:是线程私有的。描述的... 查看详情

深入理解java虚拟机(jvm性能调优+内存模型+虚拟机原理)

...建以及jdk,jre,jvm的关系  00:20:48分钟第4节jvm初体验-内存溢出问题的分析与解决  00:17:59分钟第5节jvm再体验-jvm可视化监控工具  00:21 查看详情

深入理解java虚拟机(jvm性能调优+内存模型+虚拟机原理)

...建以及jdk,jre,jvm的关系  00:20:48分钟第4节jvm初体验-内存溢出问题的分析与解决  00:17:59分钟第5节jvm再体验-jvm可视化监控工具  0 查看详情

《jvm》内存溢出异常与调优

内存溢出异常 除了程序计数器之外,jvm的其他几个运行时区域都存在着OOM异常的可能性 java堆溢出 对象数量达到最大堆的容量限制后 虚拟机栈和本地方法栈溢出 线程请求的栈深度大于虚拟机所允许的最大深度 虚拟机在扩展... 查看详情

jvm性能调优

...圾回收与性能调优总结JVM调优的几种策略  一、JVM内存模型及垃圾收集算法 1.根据Java虚拟机规范,JVM将内存划分为:New(年轻代)Tenured(年老代)永久代(Perm) 其中New和Tenured属于堆内存,堆内存会从JVM启动参数... 查看详情

jvm面试题大全

1.JVM运行时内存区域划分? 2.内存溢出OOM和堆栈溢出SOE的案例、原因、排查及解决? 3.常用的JVM性能监控工具? 4.JVM参数设置? 5.类加载过程? 6.JVM内存模型? 7.四大引用类型?区别? 8.JVM性能调优?&n... 查看详情

jvm性能调优

...行JVM调优遇到以下情况,就需要考虑进行JVM调优了:Heap内存(老年代)持续上涨达到设置的最大内存值;FullGC次数频繁;GC停顿时间过长(超过1秒);应用出现OutOfMemory等内存异常;应用中有使用本地缓存且占用大量内存空间;... 查看详情

jvm性能调优都做了啥

...讲解JVM常见的调优参数包括:-Xmx:指定java程序的最大堆内存,使用java-Xmx5000M-version判断当前系统能分配的最大堆内存;-Xms:指定最小堆内存,通常设置成跟最大堆内存一样,减少GC;-Xmn:设置年轻代大小。整个堆大小=年轻代大小... 查看详情

jvm调优概述

...步骤4.调优指标1.调优背景生产中的问题生产环境发生了内存溢出该如何处理?生产环境应该给服务器分配多少内存合适?如何对垃圾回收器的性能进行调优?生产环境CPU负载过高时该如何处理?生产环境应该给应... 查看详情

jvm调优

栈内存溢出,主要发生在大数据批量处理的情况,一般解决方案:1.加大栈内存2.分批处理(用事物,全通过则通过,没有通过则回滚) cpu过高,死锁啊,内存过高啊,i/0问题啊都可以看线程栈jstat堆内存溢出用jmap fullgc... 查看详情

jvm原理及性能调优系列(jvm调优)

...认为jvm调优主要通过以下方法解决1、设置合适的最大堆内存(新生代和老生代的最大和值)和最小堆内存(jvm启动时占用的操作系统内存大小),及设置好堆的比例分配。2、设置合适的新生代因为对其对系统性能和GC回收有一... 查看详情

jvm性能调优之生成堆的dump文件

 最近因项目存在内存泄漏,故进行大规模的JVM性能调优 , 现把经验做一记录。一、JVM内存模型及垃圾收集算法 1.根据Java虚拟机规范,JVM将内存划分为:New(年轻代)Tenured(年老代)永久代(Perm) 其中New和T... 查看详情