短短几年内,容器就改变了软件行业的面貌。也许您已经到了在容器中运行Java的地步。那很棒!不幸的是,关于容器化Java应用程序的CPU和内存使用率,还有一些事情要注意,我将在下面概述。
本文假定总体上熟悉Java和容器。如果您需要更多背景知识,请查看部分(或全部)参考资料。
Heap Space
如果有一个关于在容器中运行Java的关键声明,则为以下内容:
不要为在容器中运行的任何JAVA程序手动设置JVM堆空间。相反的是,设置容器限制。
为什么?
首先,设置容器限制可以实现容器(cgroups)的最基本目标,即隔离进程集合的资源使用。例如,当您通过JVM参数手动分配堆空间时,您完全忽视了容器的限制。
它允许轻松调整容器资源分配。需要更多内存吗?达到容器极限。需要更少吗?缩小尺寸。这为自动处理相应地扩展容器(例如k8s垂直容器自动缩放器)打开了大门,而无需手动调整JVM参数。
如果在容器编排环境(例如Kubernetes)中运行,则容器限制对于节点运行状况和调度都将变得极为重要。调度程序将使用这些限制来找到合适的节点来运行容器,并确保相等的负载分布在各个节点上。如果通过JVM参数设置内存使用率,则此信息对于调度程序不可用,因此调度程序不知道如何有效地分散容器的负载。
如果未设置容器限制,并且Java在没有显式设置任何JVM内存标志的容器中运行,则JVM会自动将最大堆设置为运行它的节点上RAM的25%。例如,如果您的容器在64 GB的节点上运行,则JVM进程堆空间可以最大为16 GB。如果您在一个节点上运行10个容器(由于自动扩展,这种情况很常见),那么您突然会急需160 GB的RAM。这是一场随时可能发生的灾难。
你能为这个做什么?
设置容器内存(和CPU)限制。仅依靠资源请求(软限制)是不够的。软限制非常适合帮助调度程序,但是设置硬限制可以使Docker(或您使用的任何容器运行时)分配指定的资源给容器本身,而不再分配更多资源。这也将允许Java(从Java 8u191开始默认为“容器感知”)根据放置在容器本身上的资源限制(而不是容器正在运行的节点)正确分配内存。
关于[Min | Max | Initial] RAMPercentage参数
在较新的Java版本中,引入了以下JVM参数(并将其反向移植到Java 8u191)。
-XX:MinRAMPercentage
-XX:MaxRAMPercentage
-XX:InitialRAMPercentage
我不会详细介绍它们是如何工作的,但是关键要点在于它们可以用于微调JVM堆大小,而无需直接设置堆大小。也就是说,容器仍然可以依靠施加在其上的限制。
那么使用什么正确的值呢?答案是-“取决于”……尤其是对容器施加的限制。
默认情况下,JVM堆获取容器内存的25%。您可以调整初始/最小/最大堆参数来更改此…例如设置-XX:MaxRAMPercentage = 50将允许JVM为堆消耗50%的容器内存,而不是默认的25%。什么时候安全,很大程度上取决于容器必须使用多少内存以及容器中正在运行哪些进程。
例如,如果您的容器正在运行一个Java进程,并为其分配了4 GB的RAM,并且您设置了-XX:MaxRAMPercentage = 50,则JVM堆将获得2 GB。这与通常会获得的1 GB默认值相反。在这种情况下,几乎可以肯定50%是绝对安全的,甚至是最佳的,因为许多可用RAM可能未得到充分利用。但是,假设同一容器仅分配了512 MB RAM。现在设置-XX:MaxRAMPercentage = 50将为堆提供256 MB的RAM,而仅将其余256 MB留给整个容器的其余部分。该内存将需要由容器中运行的所有其他进程以及JVM Metaspace / PermGen等分配共享。在这种情况下,也许50%不太安全。
因此,我提一下几点建议:
除非您想为Java进程压缩一些额外的内存空间,否则不要触摸这些参数。在大多数情况下,25%的默认值是管理内存的安全方法。它可能不是最有效的内存使用方法,但是RAM很便宜,因此最好还是谨慎一点,而不是出于某些未知原因而使JVM进程被OOM杀死。
如果您确实想调整这些,请保守一点。 50%最有可能是一个安全值,可以在大多数情况下避免出现问题,但是,这仍然很大程度上取决于容器的大小(从内存角度)。我不建议您将其设置为75%,除非您的容器具有至少512 MB的RAM(最好是1 GB),并且您对相关应用程序的内存使用情况有很好的了解。
如果您的容器除Java外还运行多个进程,请在调整这些值时格外小心。容器内存在所有这些进程之间共享,并且在这些情况下了解容器的总体使用情况更为复杂。
超过90%的人都可能会自找麻烦。
那Metaspace / PermGen / etc呢?
这超出了本文的范围,但请放心,也可以对此进行调整,但可能不应该这样做。默认的JVM行为适用于大多数用例。如果您发现自己试图解决一个晦涩的内存问题,那么可能是时候考虑摆弄JVM内存这个有些深奥的领域了,但是否则我将避免直接做任何事情。
CPU哪?
这里没什么可做的。从Java 8u191开始,JVM默认情况下是“容器感知”的,并且可以正确解释CPU份额分配。尽管有一些细节值得我们理解,所以除了在这里概述之外,我将指导您阅读这篇出色的文章,详细说明所有这些。
总结
现代Java非常适合在容器环境中运行,但是每个人都应该知道一些不太明显的细节,以确保他们从应用程序中获得最佳性能。我希望这里提供的信息以及出色的参考资料可以帮助您实现这一目标。