浅谈JVM之使用JFR解决内存泄露

简介

虽然java有自动化的GC,但是还会有内存泄露的情况。当然java中的内存泄露跟C++中的泄露不同。

在C++中所有被分配的内存对象都需要要程序员手动释放。但是在java中并不需要这个过程,一切都是由GC来自动完成的。那么是不是java中就没有内存泄露了呢?

要回答这个问题我们首先需要界定一下什么是内存泄露。如果说有时候我们不再使用的对象却不能被GC释放的话,那么就可以说发生了内存泄露。

一个内存泄露的例子

我们举一个内存泄露的例子,先定义一个大对象:

public class KeyObject {
    List<String> list = new ArrayList<>(200);
}

然后使用它:

public class TestMemoryLeak {

    public static HashSet<Object> hashSet= new HashSet();

    public static void main(String[] args) throws InterruptedException {
        boolean flag= true;
        while(flag){
            KeyObject keyObject= new KeyObject();
            hashSet.add(keyObject);
            keyObject=null;
            Thread.sleep(1);
        }
        System.out.println(hashSet.remove(new KeyObject()));
    }
}

在这个例子中,我们将new出来的KeyObject对象放进HashSet中。
然后将keyObject置为空。

但是因为类变量hashSet还保留着对keyObject的引用,所以keyObject对象并不会被回收。

注意,最后一行我们加了一个hashSet.remove的代码,来使用类变量hashSet。
为什么要这样做呢?这样做是为了防止JIT对代码进行优化,从而影响我们对内存泄露的分析。

使用JFR和JMC来分析内存泄露

Flight Recorder(JFR)主要用来记录JVM的事件,我们可以从这些事件中分析出内存泄露。

可以通过下面的指令来开启JFR:

java -XX:StartFlightRecording

当然我们也可以使用java神器jcmd来开启JFR:

jcmd pid JFR.dump filename=recording.jfr path-to-gc-roots=true

这里我们使用JMC来图形化分析一下上面的例子。

开启JMC,找到我们的测试程序,打开飞行记录器。

可以看到我们的对象在飞行记录器期间分配了4MB的内存,然后看到整体的内存使用量是稳步上升的。

我们什么时候知道会有内存泄露呢?最简单的肯定就是OutOfMemoryErrors,但是有些很隐蔽的内存泄露会导致内存使用缓步上涨,这时候就需要我们进行细致的分析。

通过分析,我们看到内存使用在稳步上涨,这其实是很可疑的。

接下来我们通过JVM的OldObjectSample事件来分析一下。

OldObjectSample

OldObjectSample就是对生命周期比较长的对象进行取样,我们可以通过研究这些对象,来检查潜在的内存泄露。

这里我们关注一下事件浏览器中的Old Object Sample事件,我们可以在左下方看到事件的详情。

或者你可以使用jfr命令直接将感兴趣的事件解析输出:

jfr print --events OldObjectSample flight_recording_1401comflydeanTestMemoryLeak89268.jfr   > /tmp/jfrevent.log

我们看一个具体的输出Sample:

jdk.OldObjectSample {

  startTime = 19:53:25.607

  allocationTime = 19:50:51.924

  objectAge = 2 m 34 s

  lastKnownHeapUsage = 3.5 MB

  object =  [

    java.lang.Object[200]

  ]

  arrayElements = 200

  root = N/A

  eventThread = "main" (javaThreadId = 1)

  stackTrace = [

    java.util.ArrayList.<init>(int) line: 156

    com.flydean.KeyObject.<init>() line: 11

    com.flydean.TestMemoryLeak.main(String[]) line: 17

  ]

}

lastKnownHeapUsage是heap的使用大小,从日志中我们可以看到这个值是一直在增加的。

allocationTime表示的是这个对象分配的时间。

startTime表示的是这个对象被dump的时间。

object表示的是分配的对象。

stackTrace表示的是这个对象被分配的stack信息。

注意,如果需要展示stackTrace信息,需要开启-XX:StartFlightRecording:settings=profile选项。

从上面的日志我们可以分析得出,main方法中的第17行,也就是 KeyObject keyObject= new KeyObject(); 在不断的创建新的对象。

从而我们可以进行更深层次的分析,最终找到内存泄露的原因。

以上就是浅谈JVM之使用JFR解决内存泄露的详细内容,更多关于JVM之使用JFR解决内存泄露的资料请关注我们其它相关文章!

(0)

相关推荐

  • JVM性能调优实现原理及配置

    1.JVM内存模型 总结:可以发现最明显的一个变化是元空间从虚拟机转移到了本地内存.默认情况下,元数据空间大小仅受限于本地内存, 这意味着以后不会因为永久代大小不够而抛出OOM异常了. jdk1.8以前,HotSpot VM将class和类的jar包数据存储在PermGen里, PermGen大小是固定的,而且项目之间无法公用公有的class,所以很容易碰到OOM异常.改成MateSpace后, 各个项目会共享同样的class空间.比如多个项目都引用了apache-common包, 在MateS

  • JVM类运行机制实现原理解析

    1.一段java程序是如何运行起来的呢? Java源文件,通过编译器,产生.Class字节码文件,字节码文件通过Java虚拟机中的解释器,编译成特定及其上的机器码,那Java虚拟机又是怎样加载java程序并执行起来的呢? 简单来说:通过类加载器加载字节码文件,被分配到JVM的运行时数据区的字节码会被执行引擎执行. (1)类加载器,加载.class文件 (2)运行数据区:栈区.堆区.PC寄存器.本地方法栈.方法区 (3)执行引擎:执行包在装载类方法中的指令 2. 类加载器 类的加载是指将类的.cl

  • java虚拟机之JVM调优详解

    JVM常用命令行参数 1. 查看参数列表 虚拟机参数分为基本和扩展两类,在命令行中输入 JAVA_HOME\bin\java就可得到基本参数列表. 在命令行输入 JAVA_HOME\bin\java –X就可得到扩展参数列表. 2. 基本参数说明: -client,-server: 两种Java虚拟机启动方式,client模式启动比较快,但是性能和内存管理相对较差,server模式启动比较慢,但是运行性能比较高,windos上采用的是client模式,Linux采用server模式 -class

  • 解决jmap命令打印JVM堆信息异常的问题

    jmap命令可以打印java进程的JVM堆信息,今天在某台机器上运行该命令查看 19560进程的堆信息 jmap -heap 19560 出现以下异常 Attaching to process ID 19560, please wait... Debugger attached successfully. Server compiler detected. JVM version is 24.79-b02 using thread-local object allocation. Paralle

  • Java 汇编JVM编写jasmin程序的操作方法

    Jasmin是Java汇编语言,以文本方式来描述JVM的指令集以及Java Class的结构,Jasmin编译器可以把汇编语言转换成二进制的字节码,使JVM可以调入执行. Jasmin最初是由Jon Meyer和Troy Downing编纂<Java Virtual Machine>时设计的范例,虽然该书不再出版,但是Jasmin成为了事实上的Java汇编语言标准,并作为开源项目得到发展:http://jasmin.sourceforge.net/. Jasmin在Java class方面的处

  • IDEA设置JVM可分配内存大小和其他参数的教程

    有时我们会遇到 java.lang.OutOfMemoryError 的问题,可能会需要设置jvm运行参数 作用 -Dproperty=Value 该参数通常用于设置系统级全局变量值,如配置文件路径,保证该属性在程序中任何地方都可访问.当然,也可以通过在程序中使用System.setProperty进行设置. 注意: 1.如果-Dproperty=value的value中包含空格,可以将value使用引号引起来.例如:-Dmyname="hello world". 2.如果配置了-Dp

  • JVM常量池的深入讲解

    提示:这里咱们要说的常量池,常量池就是咱们面试中所说的常量池,谈谈你对常量池的认识?面试官一问咱们就懵逼了,你要记得你脑子中有一张图!!! 剩下的就好办了 提示:请各位大佬批评指正!! 前言 提示:学习的时候会有点头疼哦 一.Class常量池与运行时常量池 Class常量池可以理解为是Class文件中的资源仓库. Class文件中除了包含类的版本.字段.方法.接口等描述信息外,还有一项信息就是 常量池(constant pool table) ,用于存放编译期生成的各种 字面量(Literal)

  • jvm运行原理以及类加载器实例详解

    JVM运行原理 首先从".java"代码文件,编译成".class"字节码文件,然后类加载器将".class"字节码文件中的类给加载带JVM中,最后就是JVM执行写好的代码.执行过程如下图 类加载器 类加载过程 加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载 加载 一旦JVM进程启动之后,一定会先把类加载到内存中,然后从main()方法的入口代码开始执行 public class

  • JVM创建对象及访问定位过程详解

    1.对象的创建 虚拟机接收到new指令时,检查这个指令能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析和初始化.如果都没有,先执行类加载过程. 在类加载通过后,虚拟机为新对象分配内存(把一块确定大小的内存从Java堆中划分出来),内存大小在类加载完成后即可完全确定. 两种分配方式: (1):指针碰撞:假设Java堆中内存是绝对规整的,即使用过的内存在一边,空闲的内存在另外一边,中间放着一个指针作为指示器,通过移动指针实现内存分配. (2):空闲列表:如果Jav

  • 浅谈JVM之使用JFR解决内存泄露

    简介 虽然java有自动化的GC,但是还会有内存泄露的情况.当然java中的内存泄露跟C++中的泄露不同. 在C++中所有被分配的内存对象都需要要程序员手动释放.但是在java中并不需要这个过程,一切都是由GC来自动完成的.那么是不是java中就没有内存泄露了呢? 要回答这个问题我们首先需要界定一下什么是内存泄露.如果说有时候我们不再使用的对象却不能被GC释放的话,那么就可以说发生了内存泄露. 一个内存泄露的例子 我们举一个内存泄露的例子,先定义一个大对象: public class KeyOb

  • 浅谈JVM内存溢出原因和解决思路

    目录 栈溢出(虚拟机栈和本地方法栈) 产生原因 解决思路 堆溢出 产生原因 解决思路 方法区和运行时常量池溢出 产生原因 解决思路 本机直接内存溢出 产生原因 解决思路 栈溢出(虚拟机栈和本地方法栈) 产生原因 在HotSpot中,只能由-Xss参数来设定.因为在HotSpot中不区分虚拟机栈和本地方法栈的. 栈溢出时会出现两种异常:StackOverflowError异常和OutOfMemoryError异常. StackOverflowError异常因为线程请求的栈深度大于虚拟机允许的最大深

  • 浅谈JVM垃圾回收有哪些常用算法

    一.前言: 垃圾回收: 在未来的JDK中可能G1会为ZGC所取代 先问自己几个问题: 什么是垃圾? 垃圾就是堆内存中(范指)没有任何指针指向的对象实体.不具有可达性. 为什么要回收垃圾? 因为我们的内存是有限的,内存长时间不清理就会导致内存溢出,OOM: 只要是程序正在跑,那么就不断生成新的对象,我们需要GC开辟新的空间分配给新的对象. 我们怎么回收垃圾? 依靠Java的自动内存回收机制,机制的优劣由算法决定: 或者说是机制的适配度由算法和应用场景共同决定. 什么时候回收垃圾? 当堆中的实体对象

  • 浅谈jvm中的垃圾回收策略

    java和C#中的内存的分配和释放都是由虚拟机自动管理的,此前我已经介绍了CLR中GC的对象回收方式,是基于代的内存回收策略,其实在java中,JVM的对象回收策略也是基于分代的思想.这样做的目的就是为了提高垃圾 回收的性能,避免对堆中的所有对象进行检查时所带来的程序的响应的延迟,因为jvm执行GC时,会stop the word,即终止其它线程的运行,等回收完毕,才恢复其它线程的操作.基于分代的思想是:jvm在每一次执行垃圾收集器时,只是对一小部分内存 对象引用进行检查,这一小部分对象的生命周

  • 浅谈JVM中的JOL

    JOL简介 JOL的全称是Java Object Layout.是一个用来分析JVM中Object布局的小工具.包括Object在内存中的占用情况,实例对象的引用情况等等. JOL可以在代码中使用,也可以独立的以命令行中运行.命令行的我这里就不具体介绍了,今天主要讲解怎么在代码中使用JOL. 使用JOL需要添加maven依赖: <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-co

  • 浅谈JVM之类的加载链接和初始化

    加载 JVM可以分为三大部分,五大空间和三大引擎,要讲起来也不是特别复杂,先看下面的总体的JVM架构图. 从上面的图中,我们可以看到JVM中有三大部分,分别是类加载系统,运行时数据区域和Execution Engine. 加载就是根据特定名称查找类或者接口的二进制表示,并根据此二进制表示来创建类和接口的过程. 运行时常量池 我们知道JVM中有一个方法区的区域,在JDK8中,方法区的实现叫做元空间.这个元空间是存放在本地内存中的. 方法区中存放着每个class对应的运行时常量池. 当类或者接口创建

  • 浅谈C++ 类的实例中 内存分配详解

    一个类,有成员变量:静态与非静态之分:而成员函数有三种:静态的.非静态的.虚的. 那么这些个东西在内存中到底是如何分配的呢? 以一个例子来说明: #include"iostream.h" class CObject { public: static int a; CObject(); ~CObject(); void Fun(); private: int m_count; int m_index; }; VoidCObject::Fun(){ cout<<"Fu

  • 浅谈python 导入模块和解决文件句柄找不到问题

    如果你退出 Python 解释器并重新进入,你做的任何定义(变量和方法)都会丢失.因此,如果你想要编写一些更大的程序,为准备解释器输入使用一个文本编辑器会更好,并以那个文件替代作为输入执行.这就是传说中的脚本 Python 提供了一个方法可以从文件中获取定义,在脚本或者解释器的一个交互式实例中使用.这样的文件被称为模块. 导入模块: python导入模块默认是从sys.path的路径中查找.所以应该把这个模块放在sys.path的值对应的文件夹里.否则就找不到要导入的模块.如果在cmd中或者ID

  • 浅谈spring boot 集成 log4j 解决与logback冲突的问题

    现在很流行springboot的开发,小编闲来无事也学了学,开发过程中遇见了log4j日志的一个小小问题,特此记载. 首先在pox.xml中引入对应的maven依赖: <!-- 引入log4j--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency&g

  • 浅谈vue中get请求解决传输数据是数组格式的问题

    qs的stringify接收2个参数,第一个参数是需要序列化的对象,第二个参数是转化格式,一般默认格式是给出明确的索引,如:arr[0]=1&arr[1]=2 //indices是index的复数格式,因此indices是索引的意思 //bracket是括号的意思,因此arrayFormat:'brackets'代表数组下标为空[] qs.stringify({ arr: [1,2,3] }, { indices: false }) //arr=1&arr=2&arr=3 qs.s

随机推荐