Java进程的内存占用[译] Part 6 - AndreiPangin

7 篇文章 0 订阅

原视频链接:不存在的网站,翻译此视频并发布已获得原作者同意。

Java进程的内存占用[译] Part 6 - AndreiPangin

在这里插入图片描述
所幸,还有一些替代的分配器可用,常见的有:
1. jemalloc,来自于FreeBSD
2. tcmalloc,来自Google perftools
3. mimalloc,最近微软公布的他们全新的malloc替代品,他们声称超越了jemalloc和tcmalloc,但我们生产环境还没用过,还不好说
但是我们在生产环境用了的是jemalloc,表现非常不错,用jemalloc替代标准malloc的方式也很简单,你只需要设置一个环境变量:LD_PRELOAD=/usr/lib64/libjemalloc.so,搞定!这一行配置,神奇的帮我们把之前例子中的RSS占用,从7G降低到了2.8G。所以能看到,jemalloc在处理碎片方面更优秀。
在这里插入图片描述
另外一个jemalloc带来的好处在于,它有一个内置的分配分析器,为了用到这个分析器,我们需要编译jemalloc,加上一些特殊的选项:./configure --enable-prof,然后,我们可以设置另一个环境变量:export MALLOC_CONF="prof:true,prof_prefix:jeprof.out,lg_prof_interval:30"启用malloc 分析器,这里我设置了分析文件的文件名、分析的间隔,lg_prof_interval:30的含义是内存每增加 1GB(2^30),就生成一个分析文件。

最后这行命令jeprof --svg /path/to/java jeprof.out.* > out.svg用于将收集的样本数据可视化,不过我们可以从实践中更好的了解它。
在这里插入图片描述
在这里插入图片描述
这是一段非常简单的Java代码示例,只是一个服务器,接收HTTP请求,然后找到给定名称的图片文件,然后从resources中加载后,以ByteArray的形式返回,没什么花哨的。
在这里插入图片描述
好的,开始运行这个代码,然后查看RES占用大小。
在这里插入图片描述
RES开始增长…
在这里插入图片描述
增长…
在这里插入图片描述
继续增长…
在这里插入图片描述
它的RES会一直增长,直到程序崩溃。
回头看代码,好像是有内存溢出,但是这么简单的代码,看起来不像是有溢出的可能。好的,我们开始使用jemalloc profiler。首先,配置LD_PRELOAD变量、MALLOC_CONF变量启用程序的分析采样,然后重新运行测试。
在这里插入图片描述
在这里插入图片描述
运行一段时间后(15秒以上),现在它已经收集到了样本文件,并生成了svg图形报告。
在这里插入图片描述
就是这个。这是本地内存的内存占用情况,不是java的占用情况。
在这里插入图片描述

能看到有很多已分配的内存,比如G1收集器,但没有类的信息,所以这些不是我们需要关注的。
在这里插入图片描述
可能这个inflateBackEnd是最大的一块了。
在这里插入图片描述
它是来自于 Java util zip inflater,但是上面那段简单的代码中,没有用到这个东西,嗯…
在这里插入图片描述
所以不幸的是,jemalloc并没告诉我们是谁调用了zip inflater,不过这个图提示了一个地址,因为jemalloc无法和Java 线程栈追踪协同,它知道本地库的信息,所以展示的是本地函数操作符的名称,而不是Java代码。

好了,那你们知道哪个分析工具可以和本地栈追踪(Stack trace)、Java栈追踪协同吗?是async-profiler。
在这里插入图片描述
取消设置LD_PRELOAD:unset LD_PRELOAD
在这里插入图片描述

然后重新运行程序。
在这里插入图片描述
现在用async-profiler分析应用程序,-d 5表示运行分析5秒,-e malloc表示malloc事件,-f malloc.svg表示导出报告到malloc.svg图形文件。
在这里插入图片描述
报告是这样的效果,有人看过火焰图吗,这个就是。这个图基本展示了malloc调用的堆栈跟踪,
在这里插入图片描述
能看到许多JVM本身调用的malloc,使用了黄色块,比如G1、Compiler等等。
在这里插入图片描述
绿色是Java代码,
在这里插入图片描述
这里能看到UnixFileSystem.getBooleanAttributes()频繁调用malloc,有点奇怪。malloc的分析报告的问题在于:它并非总是能展示出真正的本地内存泄露源,因为通过malloc分配的内存可能会被立即释放掉,那你就看不见了,所以这样分析也没啥用。
在这里插入图片描述

但是回想一下,malloc如何增长提交的内存,使用的是另外一个系统调用:mprotect,所以我们改变方向,分析下mprotect。
在这里插入图片描述
在这里插入图片描述
重新运行程序,再次使用async-profiler,使用相同的命令,但使用mprotect作为事件替代malloc事件。运行一段时间后,打开svg文件:
在这里插入图片描述
现在我们能看到inflater调用了mprotect,
在这里插入图片描述
jemalloc也告诉我们是和inflater有关,所以这个可以确信的,现在给了我们java程序路径,
在这里插入图片描述
这个infater是从我们的代码中创建的,所以没错,确实是我们自己的代码(GetImage),
在这里插入图片描述
用的是getResourceAsStream方法,
在这里插入图片描述
所以基本上是这一段代码造成了泄露。我们用getResourceAsStream拿到inputstream,问题是我们从来没关闭过它,这是本地内存泄露中,一个很常见的问题:即通过输入流导入zip文件时,没有关闭流。
在这里插入图片描述
在这里插入图片描述
好的,接着展示另一个简答的测试, 没什么花哨的,只读了下文件,如果异常了写下日志。
在这里插入图片描述
在这里插入图片描述
开始运行,进程的内存RES开始增长,最终停止在1.3G。
在这里插入图片描述
但是系统剩余内存不断减少,但它没说是Java进程使用的,因为RES没增长,
在这里插入图片描述
但是肯定跟Java进程有关,因为当我们把Java停了后,系统空闲内存不再下降了,
在这里插入图片描述
哪里出问题了?我们可以看到其实增长了的一直都是cache大小,系统的页面缓存甚至不属于进程,它是一个共享的概念,但不知为何我们的小程序消耗了越来越多的页面缓存,怎么找到问题在哪呢?没错,还是找async-profiler消防员。
在这里插入图片描述
在这里插入图片描述

另外,为了拿到一个特定的操作系统跟踪点,你能通过命令:perf list查看可用的跟踪点清单,我们找一个有关page cache相关的跟踪点,
在这里插入图片描述
在操作系统中,有个特别的跟踪点,恰好是当一个新页加入到页面缓存:filemap:mm_filemap_add_to_page_cache,那么让我们用async-profiler,分析下这个跟踪点。
在这里插入图片描述
在这里插入图片描述
与之前完全相同的命令,但是现在分析的事件改为了filemap:mm_filemap_add_to_page_cache
在这里插入图片描述
打开SVG文件,查看火焰图,展示了哪里的页面被加入到了页面缓存,从上到下,表示了整个栈,顺序依次是内核(橙)、系统编码(红)、Java代码(绿),而Java中的代码,是logger出现了问题。
在这里插入图片描述
在这里插入图片描述
本例中,是这段代码,因为我忘记配置logger了,没有日志的滚动,所以当这个报错一次次出现,不断写入日志,而日志文件从没有关闭,直到文件关闭前,我写入的所有东西都会放在cache中。所以解决方案很简单,启用logger滚动即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值