Java 内存泄漏分析和对内存设置

为了判断 Java 中是否有内存泄漏,我们首先必须了解 Java 是如何管理内存的。下面我们先给出一个简单的内存泄漏的例子,在这个例子中我们循环申请 Object 对象,并将所申请的对象放入一个 HashMap 中,如果我们仅仅释放引用本身,那么 HashMap 仍然引用该对象,所以这个对象对 GC 来说是不可回收的。

JVM 可以自动回收垃圾,但它只能回收满足条件的垃圾,有时需要们确保条件的满足。如果程序中,存在越来越多不在影响程序未来执行的对象(也就是不再需要的对象),而且这些对象和根对象之间存在引用路径,那么就发生了内存泄漏。

内存泄漏常发生在如下场景:

  • 全局容器类,对象不再需要时,忘记从容器中 remove
  • 像 Runnable 对象等被 Java 虚拟机自身管理的对象,没有正确的释放渠道。Runnable 对象必须交给一个 Thread 去 run,否则该对象就永远不会消亡

1、Java 对象的 Size

在 64 位的平台上,Java 对象的占用内存如下

1.1对象及其引用

为了说明对象和引用,我们先定义一个简单的类

Person p1 = new Person() 包含如下几个动作

1、右边的 new Person 在堆空间分配一块内存,创建一个 Person 类对象

2、末尾的 () 意味着创建对象之后,立即调用构造函数,进行初始化

3、左边的 Person p1 创建了一个引用变量,所谓引用变量,就是后来用于指向 Person 类示例的引用

4、= 符号使刚刚创建的对象引用指向刚刚创建的对象

上面的代码如下所示:

如果再将对象赋值给 p2 的话,变成下面这样的

执行 p2 = new Person() 之后变成

1.2虚拟机垃圾自动回收机制

垃圾自动回收做两件事情:

1、标记垃圾

2、清除垃圾

标记过程现在主要使用 根可达性 分析(还有引用计数法等),清除之后可能会有一些小的内存快,所有还有压缩的过程。

下图中的灰色对象表示可以被回收的对象(根不可达)

哪些对象可以成为根呢? http://help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fgcroots.html&cp=37_2_3

  • 没有被任何外部对象引用的栈上的对象
  • 静态变量
  • JNI handler 包括全局和局部
  • 系统 Class
  • 存活着的监视器

2 内存泄漏的症状

2.1 为什么会发生 OOM 问题?

内存不足会有三种情况:

  • 对内存不足
  • 本地内存不足
  • Perm 内存不足

发生 OOM 的时候,可以检查如下几个方面:

  • 应用程序的缓存功能
  • 大量长期活动对象
  • 对内存泄漏
  • 本地内存泄漏

2.2 内存泄漏的症状

内存泄漏一般会有如下几个症状:

  • 系统越来越慢,并且有 CPU 使用率过高
  • 运行一段时间后,OOM
  • 虚拟机 core dump

3 内存泄漏的定位和分析

内存泄漏的分析并不复杂,但需要耐心,一般内存泄漏只能事后分析,而重现问题需要耐心。

3.1 对内存泄漏定位

当出现 java.lang.OutOfMemoryError: Java Heap Space 异常,就表示堆内存不足了。堆内存不足的原因有如下几种:

  • 堆内存设置太小
  • 内存泄漏
  • 设计不足,缓存了多余的数据
  • 如果怀疑有内存泄漏,可以添加 -verbose:gc 参数后重现启动 Java 进程,输出大致如下:

怀疑内存泄漏后,我们通过 Full GC 日志进一步确认,检查 Full GC 后的可用内存是否持续增大。步骤如下:

  • 获取系统稳定后的 GC 日志(不稳定的日志不可靠)
  • 过滤 FullGC 日志,可能会有如下两种情况
  • FullGC 后内存使用量持续增长,一直到设置的堆内存最大值,基本可以确定内存泄漏
  • 内存使用量增长后又回落,出于一个动态平衡区间,基本排除内存泄漏
  • GC 日志只能帮忙找到是否有泄漏,找出内存泄漏的地方,需要依赖一些其他的工具
  • JProfile
  • OptimizedIt
  • JProbe
  • JConsole
  • -Xrunhprof

3.2 本地内存泄漏的定位

GC 日志无异常,但 Java 进程使用内存逐渐增大,并且无停止上涨的趋势。本地内存泄漏的原因有如下几个:

  • JNI 调用中出现内存泄漏(JNI 调用出现内存泄漏,可以使用 C/C++ 内存泄漏分析方法定位)
  • JDK bug
  • 操作系统问题

本地内存泄漏可能伴有如下异常

上面这个异常可能的原因有:

  • 创建的线程过多,可打印总线程数查看
  • swap 分区不足
  • 堆内存过大,本地内存不足

3.3 Perm 区内存不足定位

出现 java.lang.OutOfMemoryError: PermGen space Perm ,说明 Perm 区内存不足

  • 依赖注入,没有卸载
  • Perm 区太小

本文分享自微信公众号 - JAVA烂猪皮(gp1106701116)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-06-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏斑斓

一个完整的TDD演练案例(一)

实现猜数字的游戏。游戏有四个格子,每个格子有一个0到9的数字,任意两个格子的数字都不一样。你有6次猜测的机会,如果猜对则获胜,否则失败。每次猜测时需依序输入4个...

29730
来自专栏Python中文社区

flask 项目搭建及配置分享

作者:Tom .Lee,GitHub ID:tomoncle ,Web and cloud computing developer, Java, Golang,...

23640
来自专栏生信技能树

终于看到了一个完整的mutect2使用脚本

https://gatkforums.broadinstitute.org/gatk/discussion/9183/how-to-call-somatic-s...

30010
来自专栏Java爬坑系列

【Java入门提高篇】Day26 Java容器类详解(八)HashSet源码分析

  前面花了好几篇的篇幅把HashMap里里外外说了个遍,大家可能对于源码分析篇已经讳莫如深了。

12640
来自专栏斑斓

一个完整的TDD演练案例(二)

在编写第二个测试时,由于测试样本与之前的测试完全不一样,之前的简单实现就不能满足新增的测试了。事实上,测试就是要去验证实现逻辑,这其中最重要的测试目标就是分支。...

16920
来自专栏PHP在线

3种web会话管理的方式

http是无状态的,一次请求结束,连接断开,下次服务器再收到请求,它就不知道这个请求是哪个用户发过来的。当然它知道是哪个客户端地址发过来的,但是对于我们的应用来...

23930
来自专栏斑斓

面向接口设计与角色接口

问题:在做项目的时候,是不是所有包含非静态方法的类,都要写一个接口?是因为这样的目的是为了解耦,然后通过DI注入实现吗?

12310
来自专栏架构师之旅

我用了7年时间成长为阿里Java架构师,你呢?

合格的程序员对于明确分配的任务会完成的很好,但是大部分情况下“架构”这个词意味着架构师并不会涉及太多细节,架构图和代码实现之间总还是有些距离,你无法保证所有人都...

19060
来自专栏PHP在线

2018最新PHP学习路线整合

PHP是一种通用开源脚本语言。语法吸收了C语言、Java和Perl的特点,利于学习,使用广泛,主要适用于Web开发领域。

50850
来自专栏程序员宝库

Javascript 将 HTML 页面生成 PDF 并下载

最近碰到个需求,需要把当前页面生成 pdf,并下载。弄了几天,自己整理整理,记录下来,我觉得应该会有人需要 :)

19530

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励