实例解读:如何减少Docker中的Java内存消耗

简介:

最近,我所在的团队面临着部署微服务(Java+SpringMVC in Docker on AWS)的问题。主要问题是,很多非常轻巧的应用程序消耗了太多的内存。因此,我们经过多方尝试找到了在Docker中关于Java内存消耗的问题,并通过重构和迁移到Spring Boot实现了减少消耗的方法。本文,我将和大家分享这整个过程,希望能够对大家有所帮助。

在部署之前,我们要估计应用程序消耗多少内存。为此,我们制定了一个清晰简单的方程来找到RSS:

RSS = Heap size + MetaSpace + OffHeap size

这里OffHeap由线程堆栈,缓冲区,库(* .jars)和JVM代码组成。

Resident Set Size是当前分配给进程使用的RAM的数量。它包括代码、数据和共享库。

让我们根据本地Java VisualVM值找到它:

20170818040405610.jpg 20170818040405504.jpg 20170818040405238.jpg

RSS = 253(Heap) + 100(Metaspace) + 170(OffHeap) + 52*1(Threads) = 600Mb (max avarage)

所以,我们得出结果:大概600Mb就够了。我们选择了一个t2.micro AWS实例(具有1Gb RAM)进行部署,并开始部署应用程序。首先,通过JVM选项提供有关内存配置的一些信息:

如何减少Docker中的Java内存消耗

此外,作为应用程序的基础图像,我们选择了 ,因为我们发现它是 Jetty Java *.wars中最轻量级的图像之一。

正如前文所提到的600Mb就足够了,所以启动了一个容器,其容量如下:

docker run -m 600m

那么你觉得这样会发生什么?我们的集装箱会由于内存不足被DD(Docker daemon)杀死。这个容器是在本地启动的,具有完全相同的参数,通过逐步增加容器的内存限制,我们达到了700…我开玩笑的,我们有850mb。

经过观察和阅读有用的文章,我们决定进行测量。结果是非常奇怪和有争议的。

Heap Size与我们本地相同:

20170818040405150.jpg

  但Docker显示了一些疯狂的统计数据:

如何减少Docker中的Java内存消耗

  发生了什么事?情况变得非常混乱...

我们花了很多时间去搞清楚这些数据为什么会有争议,在搜索过程中发现我们并不是个例。在查找了很多资料,分析了 应用之后,我们几乎找到了答案。大部分额外的内存都是被用于存储编译的类及其元数据。

那么有关JavaVM / Docker统计数据的争议数字呢?事实证明,Java VisualVM不了解OffHeap的内容,因此,使用此工具调查Java应用程序的内存消耗会非常棘手。此外,了解您使用的JVM选项也是至关重要。在我看来,指定-Xmx=512m告诉JVN分配512mb Heap,但是它并不告诉JVM整个内存使用要限制在512mb,所以就会出现代码缓存和其它非Heap数据占用内存直至超过。所以,要指定总内存需要使用XX:MaxRAM参数。请注意,使用MaxRam = 512m,你的Heap约为250mb,这时就要注意应用程序JVM选项。

星期一早上,寻找灵丹妙药...

NMT和Java VisualVM Memory Sampler 可以帮助我们发现内部核心框架在内存中多次重写的依赖,并且重复的数量等于微服务中子模块的数量。如果想要更好的掌握这一点,首先要说明一下我们的“微服务”结构:

20170818040405369.jpg

这是NMT(在本地机器上)的一个模块快照(具有73MB加载的类元数据,42MB线程和37MB的代码,包括lib):

20170818040405955.jpg

据我所知,构建这样的应用程序是一个很大的错误。首先,每个* .war已经作为一个单独的应用程序部署在Jetty servlet容器中,这是非常奇怪的。因为根据定义,微服务应该是一个部署应用程序(部署单元)。其次,Jetty在内存中分别保存每个* .war的所有必需库,即使所有这些库具有相同的版本。因此,DB连接、核心框架的各种基本功能等都会在内存中重复。

一般的解决方案就是重构,让应用成为真正的微服务器。此外,我们可能还需要一整套的Jetty,但是一般人听到它的报价就能望而却步。圈内有句著名的评论“不要在Jetty中部署应用程序,而是要在应用程序中部署Jetty。

我们决定尝试使用嵌入式Jetty的Spring Boot,因为它是独立应用程序中最常用的工具,尤其是在我们的案例中。配置很少,没有XML,每个Spring框架都有优势,还有很多插件,自动配置。除此之外,网上还有大量的实用教程和文章,这些都让Spring Boot看起来是个不错的选择。

由于我们不再需要单独的Jetty应用程序服务器,所以我们将基础Docker的图像更改为轻量级的openjdk。

openjdk:8-jre-alpine

根据新的要求重构了应用程序之后,我们得到了类似下图的东西:

20170818040405422.jpg

  现在一切都准备好了,我们来测试一下。

先从Java VirtualVM进行测试:

20170818040405569.jpg 20170818040405833.jpg 20170818040406783.jpg

从数据中可以看出,虽然新版本有了一些改进,但是与之前的应用程序相比变化并没有那么大:

如何减少Docker中的Java内存消耗

  然后,我们再来看看Docker的统计数据:

如何减少Docker中的Java内存消耗

  结果很明显,我们已经减少了内存消耗。

结论

对我们团队来说,这是一个很有意思的挑战。找到导致错误或者某件事情的根本原因,会让你在特定领域中看得更深更广。网上的资源非常丰富,如果你下定决心去查找答案,那么就一定会找得到。另外,从这次事件中也认识到,不要完全信任Java VisualVM的内存消耗预计。

对于技术的学习和性能的提高,我有三个更多和大家分享,阅读更多,改进更多,调查更多。另外,避免常规,尽可能自动化,这样你可以更有效的进行工作。


本文转自d1net(转载)

相关文章
|
3天前
|
存储 Java
JAVA中的变量:深入解析与实例
JAVA中的变量:深入解析与实例
19 3
|
3天前
|
监控 Java 编译器
Java的内存模型与并发控制技术性文章
Java的内存模型与并发控制技术性文章
13 2
|
4天前
|
存储 算法 Java
Java的内存模型与垃圾回收机制
Java的内存模型与垃圾回收机制
|
5天前
|
Java
【Java探索之旅】内部类 静态、实例、局部、匿名内部类全面解析
【Java探索之旅】内部类 静态、实例、局部、匿名内部类全面解析
5 0
|
5天前
|
存储 Java 编译器
Java方法的基本内存原理与代码实例
Java方法的基本内存原理与代码实例
13 0
|
6天前
|
前端开发 Java Docker
【分享】记一次项目迁移(docker java | docker python)
该项目是一个前端Vue3和后端Python+Java的应用,原本部署在CentOS7服务器上通过宝塔面板管理。由于服务器即将到期,计划迁移到另一台使用OpenCloudOS和1Plane的服务器。在尝试构建Docker镜像时,首先为Java应用创建Dockerfile,成功构建并运行。对于Python应用,也创建了Dockerfile,并处理了依赖包的安装。在迁移过程中遇到Java项目加载验证码失败的问题,原因是缺少字体配置。通过在宿主机安装fontconfig并将相关字体文件复制到镜像中解决了问题。最后,前端Vue应用作为静态文件运行,如果使用反代理,需要进行相应配置。
25 1
|
6天前
|
存储 缓存 监控
Java的内存管理
Java的内存管理
16 0
|
7天前
|
存储 Java 开发者
深入理解Java虚拟机:JVM内存模型解析
【5月更文挑战第27天】 在Java程序的运行过程中,JVM(Java Virtual Machine)扮演着至关重要的角色。作为Java语言的核心执行环境,JVM不仅负责代码的执行,还管理着程序运行时的内存分配与回收。本文将深入探讨JVM的内存模型,包括其结构、各部分的作用以及它们之间的相互关系。通过对JVM内存模型的剖析,我们能够更好地理解Java程序的性能特征,并针对性地进行调优,从而提升应用的执行效率和稳定性。
|
7天前
|
Java
<Java SE> 5道递归计算,创建数组,数组遍历,JVM内存分配...
<Java SE> 5道递归计算,创建数组,数组遍历,JVM内存分配
33 2
|
10天前
|
存储 分布式计算 Java
深入探究JAVA编程语言:概念、应用与实例分析
**JAVA**是广泛应用的高级编程语言,以其易学性、跨平台能力和高效的性能著称。它采用面向对象编程,强调封装、继承和多态,且具备平台无关性、内置安全性和多线程支持。JAVA广泛应用于Web开发(如JSP、Servlet)、移动应用(Android开发)、大数据处理(Hadoop、Spark)和桌面应用。通过一个计算两数之和的简单示例,展示了JAVA的易读性和面向对象特性,帮助读者理解JAVA在实际开发中的运用。