在本文中,我们将看到Java中内存泄漏的示例代码。之后,我们将把Java应用程序连接到JConsole,比较有无内存泄漏时应用程序的内存使用情况。深入研究JConsole的内存监控工具可以让我们看到堆内存是如何划分为不同的空间的,垃圾收集器是如何有效地管理Java应用程序的内存的。

Java中的垃圾回收

在Java中,垃圾收集器(GC)负责释放未使用对象使用的内存。任何没有引用的对象都可以进行垃圾收集,如下图所示。因此,GC中的第二个对象可以被设置为空。

什么是Java内存泄漏

当程序无法释放未使用的内存时,会导致内存泄漏,这可能导致意外结果或应用程序崩溃。尽管Java中没有关于内存泄漏的正式定义,但为了便于理解,我们可以将其大致分为两类。

运行代码无法访问的对象导致的内存泄漏。

可通过运行代码访问但不会再次使用的对象导致的内存泄漏。

第一种内存泄漏发生在对对象的引用不再出现在正在运行的代码中,但垃圾收集器仍然无法为这些对象释放空间时。

第二种内存泄漏主要是由于程序中的错误逻辑造成的,在该程序中,对未使用对象的引用保留在运行代码中(即使该对象将不再被使用),这不允许GC回收内存。一些开发人员将此泄漏视为“实际内存泄漏”,因为将引用设置为null将允许GC回收内存。但是,如果代码已经部署,则无法将未使用对象的引用设置为null。在下面的例子中,我们将考虑这种泄漏。

Java程序内存泄漏示例

让我们考虑一个现实生活中的例子,狗收容所的狗被添加到收容所,并从收容所移除时,他们被领养。

这个Dog.java类有下面描述的三个变量:

microchip ID–唯一标识狗。

name–表示狗名的可选变量。

字节数组–为了加速内存泄漏,我们有一个10Mb的字节数组。

/** The Unique MicroChip ID of the dog. */

private int microChipID;

/** The name of the dog. */

private String name;

/** Extra memory space for each instance to speed up the memory leak. */

private byte[] toExpediteLeak;

下面是可能导致内存泄漏的有问题的代码段。在overrided equals方法中,我们通过将microchip ID和Dog name相等来比较两个Dog对象。由于狗的名字可以更改,因此使用它来等同于两个狗对象可能会导致意外的结果。例如,如果存储在HashSet中的Dog对象是使用旧名称存储的,并且我们尝试使用新名称删除它,那么删除该Dog对象将失败。

@Override

public boolean equals(Object obj) {

if (obj == this)

return true;

if (!(obj instanceof Dog))

return false;

Dog dog = (Dog) obj;

return dog.microChipID == microChipID && dog.name.equals(name);

}

这个狗狗庇护所DogShelter.java类负责将目前在收容所的狗的名单保存在一个HashSet中,如下所示。

/** In Memory Store containing the dogs present in the shelter. */

private Set shelterDogs = new HashSet();

这个类公开了两个公共方法,可以用来添加和删除收容所中的狗。

public void addEntry(int microChipID, String name) {

Dog dog = new Dog(microChipID, name);

shelterDogs.add(dog);

}

public void removeEntry(int microChipID) {

Dog dog = new Dog(microChipID);

shelterDogs.remove(dog);

}

请注意,我们使用Microchip ID和name添加dog对象,而仅使用Microchip ID删除dog对象。但是,要从集合中实际删除dog对象,我们需要同时提供Microchip ID和dog名称,因为重写equals方法时使用的逻辑错误。

最后,为了触发内存泄漏,我们将在HashSet中连续添加和删除Dog对象。

public void addAndRemoveRandomEntries(int entriesCount) {

Random rand = new Random();

String[] commonDogNames = { "Buddy", "Coco", "Charlie", "Cooper", "Maggie" };

for (int i = 0; i <= entriesCount; i++) {

/** Generate a random dog name from the list of common dog names. */

String randomDogName = commonDogNames[rand.nextInt(commonDogNames.length)];

/** First add and then remove the entry from the HashSet. */

addEntry(i, randomDogName);

removeEntry(i);

System.out.printf("Successfuly removed entry for %s with unique id %d.\n", randomDogName, i);

try {

/**

* Sleep before adding & removing new entry so that we can see the memory grow

* in JConsole.

*/

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

让我们运行main方法,看看我们的程序是如何在几秒钟内崩溃的

public static void main(String[] args) {

DogShelter shelter = new DogShelter();

shelter.addAndRemoveRandomEntries(1000);

}

如何使用JConsole监控Java应用程序的内存使用

在运行main方法之后,打开JConsole并将其连接到本地Java进程。

将JConsole连接到本地Java进程之后,如果单击Memory选项卡,您将看到如下所示的窗口。让我们深入讨论一下内存工具的每个特性。

堆VS非堆空间

在JConsole窗口的右下角,我们可以看到几个代表当前内存使用百分比的条形图。内存空间分为堆空间和非堆空间。

堆空间

使用new关键字创建的对象会落在堆空间中。在一个典型的Java程序中,由于不断地创建和删除新对象,我们需要一种机制来恢复未使用对象的内存。垃圾收集器负责实际释放这些未使用对象的内存,为了有效地执行此过程,堆内存被划分为3个不同的空间。

1. Eden 伊甸园空间-新创建的对象出现在伊甸园空间中。

2. Survivor 幸存者空间–伊甸园空间中在垃圾收集中幸存的对象被提升到幸存者空间。

3. Old Gen–在幸存者空间中的垃圾收集中存活很长时间的对象被提升为Old Gen或Tenured Generation。

GC在老年代空间中运行的频率较低,因为老年代的对象生活在该空间中。而GC在Eden空间上运行得更频繁,因为新对象更容易被取消引用。堆内存的这种分离允许有效的内存管理。

非堆空间

非堆空间分为元空间、代码缓存和压缩类空间。

Metaspace存储描述用户类的元数据。所以所有与类相关的数据,比如静态方法和原语类型变量都存储在这里。在Java8之前,使用PermGen代替元空间。与PermGen相比,元空间具有以下优点:

元空间使用本机内存,而不是像PermGen那样使用堆。

元空间不再具有固定的大小,它会自动增加到某个限制(可以使用JVM参数指定)。而PermGen的大小是固定的。

当内存达到MaxMetaspaceSize时,会触发垃圾回收以删除未使用的类定义和装入器。

代码缓存空间用于存储本机代码,以便Just-In-Time(JIT)编译器更快地执行Java程序。

了解JConsole内存图表

在JConsole窗口的顶部,您可以看到有多个内存图表。让我们分析所有堆内存图表,并比较有无内存泄漏的Java应用程序中的内存使用情况。需要注意的是,下面显示的内存图表模式是特定于这个特定Java应用程序的。

堆内存图表

堆内存图表显示了在Eden空间、幸存者空间和老年代空间中使用的组合内存。堆内存图表本身足以监视Java应用程序的内存,并可能检测内存泄漏。

内存泄漏

在程序执行过程中,随着内存泄漏操作的不断重复,堆内存不断增长。

如果内存泄漏的操作重复多次,程序最终将崩溃并出现OutOfMemoryError。

内存泄漏已修复

堆内存的使用随着操作的多次重复而增加,并且一旦GC触发,未使用的对象所使用的内存就会被回收。

如果将完全相同的操作重复多次,我们可以看到,在GC循环完成后,内存使用量几乎在同一个位置(~10mb)增加。

Eden 伊甸园空间图

内存泄漏

直到时间14:16,新的狗的物体从伊甸园空间被分配了记忆。

有趣的是,在14:16之后,新的Dog对象直接从老年代空间分配内存。

对于这种行为的一种可能解释是,JVM标识在GC循环完成后所有Dog对象都会在老年代空间中登陆。因此,JVM直接从老年代分配内存,跳过了将对象从Eden空间转移到幸存者空间,最后转移到老年代空间的过程。

内存泄漏已修复

内存图表与堆内存图表几乎相同,因为Dog对象被创建,引用被立即删除,因此它们可以进行垃圾收集。

Survivor 幸存者空间图

内存泄漏

直到时间14:16,在伊甸园空间中幸存下来的狗对象被转移到幸存者空间。

在14:16之后,狗对象直接从老年代空间分配内存,因此幸存者空间的内存下降。

内存泄漏已修复

GC从伊甸园空间本身回收内存,因此狗对象不会转移到幸存者空间,我们几乎一直在使用内存。

老年代空间图

内存泄漏

直到14:16,我们看到,由于狗的对象被从伊甸园空间转移到幸存者空间,最后转移到老年代空间,步幅增加。

14:16之后,我们看到内存几乎持续增长,因为Dog对象直接从老年代空间分配内存。

内存可达2.6GB。

内存泄漏已修复

即使内存图表看起来与内存泄漏的图表相似。然而,我们可以看到老年代空间的内存几乎没有达到6mb。

假设您有一个事件触发了一个覆盖5000多行代码的操作,并且您怀疑它有内存泄漏。检查所有代码并尝试确定您的代码是否存在内存泄漏将是非常具有挑战性和耗时的。但是,如果我们的应用程序使用jconsole可以很容易地找到主要的内存泄漏。

1. 运行Java应用程序并将JConsole连接到它。

2. 重复您怀疑内存泄漏的操作。例如,如果您怀疑向数据库中添加条目的操作可能存在内存泄漏,那么请编写一个测试脚本,该脚本将在一段时间内重复相同的操作,在此期间您可以检测到可能的内存泄漏。

3. 每隔几分钟单击JConsole窗口右上角的“perform gc”。例如,当您的操作在后台连续运行时,您可以请求在10分钟内每分钟执行一次GC。

4. 分析如下所示的结果,以确定Java应用程序是否存在内存泄漏。

结果分析

要分析结果,请在GC操作完成后连接所有点(从步骤3开始)。

如果连接的直线/曲线正在增加或具有正斜率,则会发生内存泄漏。

但是,如果连接的直线/曲线不增加,则可能没有内存泄漏。

步骤4中的行将与内存泄漏的大小成比例增加。如果内存泄漏很小,则曲线将缓慢增加。但是,如果内存泄漏很大,则线将以更大的斜率增加。因此,如果内存泄漏以千字节为单位,则使用此技术可能无法看到它。

java 导致内存泄露的情况_JConsole定位内存泄漏相关推荐

  1. c++内存泄露:使用性能监视器进行内存泄露的确认

    对于服务器程序,在不停的运作中,如何去测试是否存在内存泄露,如果你的开发环境是在windows下,那么我们就可以使用windows自带的性能监视器来观察程序的性能. 使用此例子: // Test.c ...

  2. android的内存泄露有几种,Android中几种有可能会导致内存泄露的情况

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 1.Static静态成员导致的内存泄露 将占用大量内存空间的变量声明为static静态类型.当Activity被销毁的时 ...

  3. SDDC-SDK 库内存泄露导致ESP32收不到任何报文记录以及修复,附带cjson可能导致内存泄露的情况

    项目场景: 之前为了方便 SDDC 协议使用,我自己写了一个 SDDC 的 SDK, 具体详见:同人逼死官方系列!基于sddc 协议的SDK框架 sddc_sdk_lib 解析 和 同人逼死官方系列 ...

  4. 既然有 GC 机制,为什么还会有内存泄露的情况

    理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是 Java 被广泛使用于服务器端编程的一个重要原因). 然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被 GC 回收 ...

  5. Android 内存优化实操,定位内存问题

    文章目录 一.内存泄漏定位 1.观察法: 2.使用内存分析工具 2-1.收集内存快照 2-2.hprof文件转换 2-3.Mat分析内存 二.内存抖动 三.优化内存空间 1.减少不必要的内存开销 2. ...

  6. chrome内存泄露(一)、内存泄漏分析工具

    内存分析使用的工具包括chrome任务管理器.chrome时间轴(低版本是Timeline,高版本对应performance).chrome memory(低版本是chrome profiles,主要 ...

  7. Linux内存管理内存映射以及通过反汇编定位内存错误问题

    提到C语言,我们知道C语言和其他高级语言的最大的区别就是C语言是要操作内存的! 我们需要知道--变量,其实是内存地址的一个抽像名字罢了.在静态编译的程序中,所有的变量名都会在编译时被转成内存地址.机器 ...

  8. linux 内存泄露 工具,Linux Kernel模块内存泄露分析

    1.通过free 看 剩余内存 # free total used free shared buffers Mem: 2065866752 1268113408 797753344 0 9060352 ...

  9. java避免内存泄露_Java防止非静态内部类内存泄漏

    内存泄漏 一个不会被使用的对象,因为另一个正在使用的对象持有该对象的引用,导致它不能正常被回收,而停留在堆内存中,从而导致内存泄漏. 最坏的情况下,由于大量的内存泄漏,最终导致jvm的内存耗尽,致使程 ...

最新文章

  1. python 只循环目录_Python面试题目,掌握他们令你更上一层楼!附答案
  2. java 折线图_java折线图的数据写入方法
  3. python 打开文件-Python 读文件
  4. App-V 4.6 SP1系列之五包加速器的制作
  5. mac pdf去水印_今天才知道,Word、PDF文档去水印这么简单!一键水印说拜拜
  6. 【Linux】一步一步学Linux——dircolors命令(239)
  7. 盐城大数据产业园人才公寓_岳西大数据产业园规划设计暨建筑设计方案公布,抢先一睹效果图...
  8. 1g的树莓派4b能做什么_树莓派4B系统安装及配置
  9. SpringBoot 统一异常处理最佳实践 -- 拓展篇
  10. matlab非参数检验,非参数检验及matlab实现
  11. 运用极域电子教室控制其他学生端(不需要教师端)
  12. iphone和mac互传文件_华为手机连接苹果电脑文件传输教程:一键文件互传、备份和恢复...
  13. 【洛谷】3957 跳房子
  14. Python爬取腾讯招聘信息
  15. 利用Python创建Excel条形图
  16. 不同手机型号图文预览_微信编辑器预览一般以多大的手机尺寸为准?
  17. 题目:A派生出子类B,B派生出子类C,并且在Java源代码中有如下声明,问以下哪个说法是正确的?()
  18. Python快速编程入门#学习笔记02# |第十章 :Python计算生态与常用库(附.小猴子接香蕉、双人乒乓球小游戏源码)
  19. Python就业行情和前景分析之一
  20. 贾立平是中科学院计算机所博士,贾立平是哪个大学的博士曝光 贾立平秀恩爱不忘考博士...

热门文章

  1. java凸多边形寻路_云风的 BLOG
  2. 资源分享 | WSDM2020推荐系统论文打包下载
  3. winhex数据恢复linux,使用winhex对已删除文件进行恢复
  4. QueryPerformanceCounter计算时间
  5. 1月外贸新规,请注意查收
  6. mysql的exists解析_mysql中关于exists的深入讲解
  7. css font-family css 字体 css 字体样式
  8. 这个双十一,小米手机销量为何不给力?
  9. seaborn 画图工具的基本使用
  10. 6篇关于撒谎600字优秀作文,心田花开整理