Java进程的内存占用[译] Part 1 - Andrei Pangin

7 篇文章 0 订阅

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

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

在这里插入图片描述
JVM 选项“-Xmx4g”是否意味着该进程将最多消耗 4 GB 的 物理内存?当然不是。还有什么可以占用虚拟内存以及占用多少虚拟内存?

当在共享环境或资源有限的容器中运行 Java 时,这个问题变得尤为重要。很多地方都会发生内存消耗:从代码和库到JVM和操作系统。虽然 Java 内存泄漏通常很容易从堆转储中发现,但native memory泄漏就很难搞懂了。

在本次会议期间,我们将讨论哪些结构会影响 JVM 占用空间。我们将研究native memory泄漏的真实案例,并探索可用于内存分配分析的工具。

Andrei Pangin【应该是俄罗斯人】 领导 Odnoklassniki 社交网络的开发,专注于高性能 Java 服务器。他之前从事 HotSpot JVM 方面的工作,这成为他最喜欢的主题和专业领域。 Andrei 也是 Stack Overflow 上的顶级#JVM 回答者和 Async-profiler 的作者。

容器化应用

![在这里插入图片描述](https://img-blog.csdnimg.cn/a79c435efe2147fb9c3b3ee46cee19f0.pn

之前我们是一个Java应用放在一个服务器,很方便管理,但是从资源使用的角度看,并不高效。所以我们开始构建自己的云服务器,让多个应用程序共享一个服务器的资源,但是资源隔离成了问题,怎样避免一个程序占用了所有资源,其他程序饿死的情况发生?

容器可以解决这个问题,通过设置resources配置即可。
在这里插入图片描述
但是到了Java,它自己就有设置内存的方式,即 -Xmx4G,我们就可以认为Java将使用最多4G的物理内存,当然JVM本身也可能用掉一些额外的不太多的内存,用于各类其他用途。这只是我们期望的情况。如果一切都这么顺利,我就没必要演讲了。

你会发现系统突然过来,Kill掉了Java进程,虽然不是很常见,但因为它达到了资源配额上限,所以被kill掉了。

Container support?

在这里插入图片描述
不过没事,Java不是有了新选项:-XX:+UseContainerSupport吗?那我就升级下JDK,加上这个选项,问题解决!

不好意思,并没有!事实上,这个选项其实没啥效果,仅仅是让Java进程能够看到容器里的CPU数量,和用于计算默认堆内存的容器内存大小。JVM甚至不会满足于这两个限制,所以问题还是有,内存杀手开始了表演,操作系统出来洗地。
在这里插入图片描述
事实上,很多人都在问这个问题,为什么Java用了更多资源以及如何限制,所以这让我有了做这个演讲的动力。

Java进程的内存占用

在这里插入图片描述
我们一起来找下答案。一般最简单的方式是用top命令,从这里可以看出java是内存使用率最高的进程。
在这里插入图片描述
在这里,VIRT显示20.747g,看起来很吓人,但其实这不是实际内存占用,这只是进程的地址空间的总量;
在这里插入图片描述
RES或者RSS(Residence set size)才是实际物理内存占用,只有9.022g。

pmap

在这里插入图片描述
但是,这9个G是怎么来的呢?在Linux,有一个很有用的工具,pmap命令,可以用来展示Java进程完整的内存映射。这里我们只讲Linux系统,其他操作系统的Java也差不多。

内存映射是一个很长的表格,展示了进程拥有的每一个地址空间范围,这其中我们能看到地址空间范围的长度和对应的RSS内存大小占用。有一些区域是映射给了磁盘上的jar文件或者so共享库文件,其他是匿名的,代表的是内存而不是文件。
在这里插入图片描述
高亮的这一行,是一个4GB的地址范围,很显然这是Java 堆,那其他的呢?

JVM运行时

在这里插入图片描述
为了搞清楚其他内存消耗的源头,我们需要先了解下JVM同志的工作职责,很显然,JVM同志不可能只是用来收集垃圾的,还做了很多其他工作,比如加载类、编译字节码、管理线程等等,这些活动都需要额外的内存。

Native Memory Tracking

在这里插入图片描述
好在JVM是可以告诉我们他用了多少内存,通过启用功能:Native Memory Tracking(简称NMT,-XX:NativeMemoryTracking=[summary|detail],JDK7开始提供),就可以了!不过,为什么这个功能不是默认启用的?因为凡事皆有代价,通过其文档可以看到,启用后会有额外的5~10%的性能开销,另外,对每个已分配的本地内存快,NMT会向所有malloc内存加上额外2个字的header信息。

另外要注意的是,NMT只关注JVM分配的内存,例如说你使用了第三方的JNILibrary,那么这个Library使用的内存,NMT是跟踪不了的。

当这个选项开启后,你可以使用**jcmd PID VM.native_memory [detail]**命令,随时查看NMT报告。

NMT 概要

在这里插入图片描述
NMT报告包含多个部分,每个部分展示了每个JVM子系统的细节。
在这里插入图片描述
顶部的Total committed,就是当前JVM所使用的大小。
在这里插入图片描述
内存可以有两种统计方式:1. 系统分配器:malloc实现,2:JVM直接向操作系统申请内存,通过mmap系统调用实现。

JVM运行时 - Java堆

在这里插入图片描述
最明显的部分莫过于Java堆内存,因为Java对象放在这里。然而还有一部分叫:GC,为啥它会在这?

Java堆和GC

在这里插入图片描述
拿G1举例,Java堆被分割为了一个个的Region,这些Region可能是老年代、Eden、Survivor,所有这些区的大小是通过-Xmx控制的。但是为了管理Java堆,GC算法本身会需要额外的内存,而这些内存是不被Xmx所限制的。

例如:
1. 遍历对象以找到可以访问到的对象,GC使用其他的结构:off-heap,即非堆结构:标记位图(Mark bitmap)标记了它所访问的对象;
2. 遍历算法本身需要一些内存,称为:Mark stacks(标记栈);
3. 最后,最重要的也是最大的部分,是Remembered sets(RSets),包含了Region引用,另外不能直接设置RSets的大小,但是间接的,可以设置Region大小(-XX:G1HeapRegionSize)来影响RSets的大小。

GC内存开销

在这里插入图片描述
不同的GC算法需要使用不同的结构,但是多数情况下,它们都需要额外的内存开销,我这里做了一个实验,测量它们的开销。

另外不同的JDK版本、GC参数、应用程序也会产生不通的结果。但这样做的目的在于,确认了GC会产生额外的内存开销,以便我们之后在分配内存时,提前准备好一部分额外内存给GC使用。

堆大小

在这里插入图片描述
在Java里面,我们知道-Xmx设置堆内存最大值,-Xms设置内存最小值,当这两个值设置为一样,理所当然的,Java进程是不是正好就占用这么多内存呢?通过top命令可以看到,java进程的RES值很小,不到4GB。那么,为何?

这是因为操作系统在物理内存中获取内存页时,其获取方式是惰性的,一开始,并没有使用堆内存,所以并没有消耗物理内存,但是这种惰性访问,会导致在运行时出现Page Fault,这肯定是非常不好的。
在这里插入图片描述
这里有一个JVM选项:-XX:+AlwaysPreTouch,可以占用规定的堆内存大小。如果我们是在生产应用中,即Xms等于Xmx,可以看到进程的确占用了4G的物理内存。
在这里插入图片描述
我发现很多开发者误以为-Xms选项是最小堆内存大小的含义,但其实不是,即使你把-Xmx设置为和-Xms一样,堆内存大小仍然会调整,甚至会低于Xms。如果你真的不希望这样,那么可以选择关闭:-XX:-AdaptiveSizePolicy。

Adaptive size policy

在这里插入图片描述
AdaptiveSizePolicy是默认启用的,它允许堆内存进行扩大和缩小。有两个选项用于主动的调整其扩大和缩小:-XX:MinHeapFreeRatio、-XX:MaxHeapFreeRatio。

然而AdaptiveSizePolicy是很有用的,因为它可以让JVM归还内存给操作系统。对于一些应用会很有用,举例说桌面应用程序,比如你在运行IDE,或者编译一个大的项目,IDE用了很多内存,没关系,当它闲置的时候,再把内存归还回来就行了!

分配和归还内存听起来很厉害,所以我设置了另外的实验,看看不同的GC算法在committed内存方面各有什么特点,我的测试使用最大堆2G,初始化堆32M,测试将会加载很多数据,接着在GC中被丢弃。

各GC的归还内存对比

在这里插入图片描述
首先是Parallel GC,完全没有归还内存给操作系统,committed都没降下去。
在这里插入图片描述
CMS 可以归还内存,但也仅仅是在调用了几次System.gc()后才实现。
在这里插入图片描述
G1 在commit内存时非常激进,进程启动后的使用量很大,但是却能在Full GC后立刻归还所有内存。而到了JDK12,有了一个新特性,可以让G1不只在Full GC后才归还,而是常规的concurrent cycle(并发处理周期)也能做到。
在这里插入图片描述
不过,在JDK12之前,能够做到在concurrent cycle(并发处理周期)就归还内存的垃圾回收器,只有Shenandoah GC,这里特指完全不需要Full GC的情况下。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值