Docker 的 Java 内存斲丧非常怎么办?

手机游戏开发者 2024-10-1 07:54:43 25 0 来自 中国
本文来自于HeapDump性能社区! !有性能题目,上HeapDump性能社区!
近来,我所在的团队在摆设我们的微服务(AWS 上的 Docker 中的 Java+SpringMVC)时碰到了题目,一个非常轻量级的应用却斲丧了太多内存。于是,我们在 Docker 中发现了很多关于 Java 内存的线索,并找到了通过重构和迁移到 Spring Boot 来淘汰其斲丧的办理方法。
这里分享一下整个过程:
在摆设微服务之前,我们惯例要预估下内存,于是订定了一个清晰且简朴的方程式来找到RSS
RSS = Heap size + MetaSpace + OffHeap size这里的OffHeap由线程堆栈、缓冲区、库 (*.jars) 和 JVM 代码本身构成。
Resident Set Size是当前分配给进程并由进程利用的 RAM 量。它包罗代码、数据和共享库。
我们根据当地 Java VisualVM 值来查找:


RSS = 253(Heap) + 100(Metaspace) + 170(OffHeap) + 52*1(Threads) = 600Mb (max avarage)……看完这个,其时以为万事大吉,就等完成了撸串去!
既然大概600Mb就够了。我们就选择了一个 t2.micro AWS 实例(具有 1Gb RAM)举行了摆设。
起首,去JVM 设置了一些和内存相干的设置:
-XX:MinHeapFreeRatio=10 \-XX:MaxHeapFreeRatio=70 \-XX:CompressedClassSpaceSize=64m \-XX:ReservedCodeCacheSize=64m \-XX:MaxMetaspaceSize=256m \-Xms256m \-Xmx750m \然后,选择了 *jetty:9-alpine *作为步伐的底子镜像,毕竟它是 Jetty 中 Java *.wars 最轻量级的镜像之一。
末了,既然 600Mb 就够了,那就得启动一下内存限定的docker容器:
docker run -m 600m迁移变革来了!
由于内存不敷,启动以后,容器就被 DD(Docker 保卫进程)杀死了
很奇怪,** 毕竟这个容器已经 利用完全雷同的参数**在当地启动。
于是我们通过渐渐增长容器的内存限定,我们到达了850Mb。
为什么?百思不得其解!
于是到处找了一些文章,看来别人的一些案例以后,我们决定举行了一些分析,实行定位题目。
效果争议更大了!

  • 堆巨细与我们之前的(当地)启动雷同:

    4.png


  • 但 Docker 表现了一些疯狂的统计数据:


why?题目越来越多了!

我们花了很多时间为这些有争议的数字探求表明,发现并不是只有我们碰到了雷同题目。
在阅读了更多文章,并利用 Native Memory Tracker 分析了步伐以后,得出一个结论:
原来大多数额外内存已用于存储已编译的类及其元数据。你大概会问那JavaVM/Docker 统计数据呢?毕竟证明,Java VisualVM 对 OffHeap 一无所知,因此,利用此工具观察 Java 应用步伐的内存斲丧大概完全没用。
别的,相识你设置的 JVM 参数设置也很紧张。我们发现固然指定 - Xmx=512m告诉 JVM 分配一个512mb的堆,但是它并没有告诉 JVM 将其整个内存利用量限定为512mb
有代码缓存和各种其他堆外数据时,要指定总内存,您应该利用 -XX:MaxRAM参数。请留意,在MaxRam=512m时,您的堆约莫为 250mb。警惕并留意您的应用步伐 JVM 选项。
于是我们继续深入,探求办理方案:
NMT 和 Java VisualVM Memory Sampler 资助我们发现我们的内部核心框架在内存中被多次复制为依赖项。重复的数量即是我们微服务中子模块的数量。为了更好地明白这一点,我想阐明我们的“微服务”布局:

这是 NMT(在我的当地呆板上)为一个模块(加载了73MB的类元数据、42MB线程和37MB的代码,包罗库)的快照:
7.png 据我们所知,以这种方式构建我们的步伐是一个大错误。起首,每个 *.war 都作为一个单独的应用步伐摆设在一个 Jetty servlet 容器中,这很奇怪,起首根据界说,微服务应该是一个用于摆设的应用步伐(摆设单元)。
其次,Jetty 在内存中分别生存每个 *.war 所需的全部库,即使全部这些库都具有雷同的版本。效果,数据库毗连、核心框架中的各种根本功能等都在内存中复制。
一个知识性的办理方案是重构并使我们的应用步伐成为真正的微服务。别的,我们猜疑我们是否必要一整包 Jetty,更况且网上都在告诫:
“不要在 Jetty 中摆设你的步伐,要在你的步伐中摆设 Jetty。”
我们决定实行利用嵌入式 Jetty 的 Spring Boot,由于它似乎是独立应用步伐最常用的工具,尤其是在我们的案例中。很少的设置,没有 XML,每个 Spring 框架的上风和很多(很多)插件,它们会自动设置本身。有大量实用教程和文章展示了怎样在互联网上利用它。
别的,由于我们不再必要单独的 Jetty 应用步伐服务器,我们将底子 Docker 映像更改为简朴的轻量级OpenJDK。
openjdk:8-jre-alpine然后,我们根据新的需求重构了我们的应用步伐。在一天竣事时,我们得到了雷同的东西:

8.png 既然有了想法,那就去试!
题目办理了么?

让我们从我们的 Java VirtualVM 举行丈量。

9.png
10.png 唔。看起来我们做了一些改进,但与从前版本的应用步伐的全部积极和效果相比,并没有那么大:
12.png 但是让我们看一下 Docker 统计数据:

13.png 万岁!我们的内存斲丧减半。
结论

这对我们的团队来说是一个很好且风趣的寻衅。试图找出题目的根因可以让你发现原形,并让你找到特定范畴的知识盲区。
同时要信任社区的气力,不要重复造轮子,你碰到的题目别人也碰到过!你可以在社区找到各类案例和答案。别的,不要完全信任来自 Java VisualVM 的内存斲丧估计,哈哈。
更多案例:
又发现一个导致JVM物理内存斲丧大的Bug(已提交Patch)
一文深度分析Java内存模子
一次 Java 内存走漏排查过程,涨姿势
警惕踩雷,一次Java内存走漏排查实战
分析和办理JAVA 内存泄漏的实战例子
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 16:55, Processed in 0.130808 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表