云计算
作者: kelvinjin2009来源:程序员
原文链接:
33558 www.tech ug.com/post/Java-and-docker-memory-limits.html
Java和Docker不是天然的朋友。 Docker可以设置内存和CPU限制,但无法自动检测到Java。 使用Java的Xmx标记(复杂/重复)或新的实验性JVM标记可以解决此问题。
虚拟化不一致
Java和Docker的组合并不是完美的匹配,最初离完美的匹配还有相当大的距离。 对于初学者来说,JVM的所有构想都是虚拟机可以运行程序,而无需考虑底层硬件。
那么,将我们的Java APP打包成JVM,然后将其整个塞进docker容器中,能给我们带来什么好处呢? 大多数情况下,你只是在复制JVMs和Linux容器,除了浪费更多的内存外,没有任何好处。 我觉得这个样子很傻。
但是,Docker可以将您的程序、设置、特定的JDK、Linux设置和APP应用服务器以及其他工具放在一起。 从DevOps/Cloud的角度来看,这种完整的容器具有更高水平的封装。
问题1 :内存
大多数产品级APP应用程序仍然使用Java 8(或更早的版本),这可能会导致问题。 Java8(update131之前的版本)无法很好地与Docker一起工作。 问题是在你的机器上,JVM的可用内存和CPU数量不是Docker可用的内存和CPU数量。
例如,如果您限制Docker容器只能有100MB的内存,但是,旧版本的Java无法识别此限制。 Java看不到这个限制。 JVM需要更多的内存,远远超过了这个限制。 如果内存使用过多,Docker将采取行动,杀死容器中的进程! JAVA进程被杀。 很明显,这不是我们想要的。
要解决此问题,必须为Java指定最大内存限制。 在早期版本的Java(8u131及更低版本)中,必须通过将容器设置为-Xmx来限制堆大小。 我觉得这不太正确。 我不想两次定义这些限制,也不想在容器中定义这些限制。
幸运的是,我们现在有了更好的方法来解决这个问题。 从Java 9之后( 8u131 ),JVM添加了以下标记:
- xx:unlockexperimentalvmoptions-xx:usecgroupmemorylimitforheap
这些标志强制JVM检查Linux的cgroup配置,Docker通过cgroup实现最大内存设置。 现在,如果您的APP应用程序达到了Docker设置的限制(例如500MB ),JVM将看到此限制。 JVM将尝试GC操作。 如果仍然超过内存限制,JVM将做它应该做的事情,并抛出OutOfMemoryException。 也就是说,JVM可以看到Docker的这些设置。
从Java 10开始(请参见以下测试),这些体验标志位在缺省情况下处于启用状态。 也可以使用-XX: UseContainerSupport启用。 可以通过设置-XX:-UseContainerSupport来禁止这些行为。
问题2 :处理器
第二个问题类似,但与CPU有关。 这意味着JVM将检查硬件并检测CPU的数量。 优化运行时间以使用这些CPUs。 但在同样的情况下,这里还有另一个不一致之处。 Docker可能不允许使用所有这些CPUs。 遗憾的是,这在Java 8或Java 9中没有修复,但在Java 10中已修复。
从Java 10开始,可用的CPU计算通过使用UseContainerSupport以各种方式(缺省)解决了此问题。
Java和Docker内存处理测试
作为一个有趣的练习,让我们来验证和测试Docker如何使用几个不同的JVM版本/标志,甚至是不同的JVM来处理内存不足。
首先,创建一个测试APP应用程序,它只“吃”内存,而不释放内存。
javaimportjava.util.ArrayList; importjava.util.List; publicclassMemEat{
publicstaticvoidmain ( string [ ] args ) {
Listl=newArrayList (;
while (真)。
byteb[]=newbyte[1048576];
l.add(b );
Runtimert=Runtime.getRuntime (;
system.out.println(&; #039; freememory:&; #039; rt.freememory );
}
}
在启动Docker容器并执行此APP应用程序后,您可以看到发生了什么。
测试Java 8u111
首先,从具有旧Java 8版本的容器( update 111 )开始。
壳牌
docker run-m100m-it Java:open JDK-8 u111/bin/bash
编译并运行MemEat.java文件。
壳牌
javacMemEat.java
Java meme at . free memory:67194416 free memory:66145824 free memory:65097232 killed
正如所料,Docker杀死了我们的Java进程。 不是我们想要的(! 请参阅。 也可以看到输出。 Java认为还需要分配大量的内存。
可以通过使用-Xmx标志为Java提供最大内存来解决此问题。
壳牌
javacMemEat.java
jva-xmx 100 Mme meat . free memory:1155664 free memory:1679936 free memory:2204208 free memory:1315752 exceptioninthread & amp #039; m亚马逊
atmemeat.main(memeat.Java:8 ) )。
提供自己的内存限制后,进程正常停止,JVM了解正在执行的限制。 但是,问题是你现在设定了两次这些内存限制,一次是Docker,一次是JVM。
测试Java 8u144
如上所述,通过添加新标志来修复问题,JVM现在可以遵循Docker提供的设置。 可以使用新版本的JVM进行测试。
壳牌
docker run-m100m-itadoptopenjdk/open JDK8/bin/bash
撰写本文时,此OpenJDK Java镜像的版本为Java 8u144。)
然后再次编译并运行不带标志的MemEat.java文件。
壳牌
javacMemEat.java
Java meme at . free memory:67194416 free memory:66145824 free memory:65097232 killed
仍然有同样的问题。 但是,让我们现在提供一下上述实验性的标识:
壳牌
javac MemEat.java
Java-xx:unlockexperimentalvmoptions-xx:usecgroupmemorylimitforheapmemeat
……
free memory: 1679936
free memory: 2204208
free memory: 1155616
free memory: 1155600
扩展& amp; #039; main&; #039; Java.lang.out of memory error:javaheapspace
atmemeat.main(memeat.Java:8 ) )。
这次我没有告诉你JVM的限制是什么。 我只是告诉JVM检查正确的限制设置。 现在感觉好多了。
测试Java 10u23
一些人在评论和Reddit中表示,Java 10通过将实验标志设置为新的缺省值来解决所有问题。 可以通过禁用此标志来关闭此行为:-XX:-UseContainerSupport。
我测试那个的时候,那个一开始不起作用。 撰写本文时,AdoptAJDK OpenJDK10镜像与jdk-10 23打包在一起。 这个JVM显然不理解UseContainerSupport标志,这个过程仍然被Docker杀死。
壳牌
docker run-m100m-itadoptopenjdk/open JDK 10/bin/bash
我测试了代码(我也手动提供过所需的标志)。
壳牌
javacMemEat.java
Java meme at . free memory:96262112 free memory:94164960 free memory:92067808 free memory:89970656 killed
Java-xx:usecontainersupportmemeat
unrecognizedvmoption&; #039; usecontainersupport&; #039; error:couldnotcreatethejavavirtualmachine.error:afatalexceptionhasoccurred
测试4:Java10u46(nightly ) )。
我决定尝试最新的adopt a JDK open JDK 10 nightly内部版本。 它包含的是Java 10 46的版本,而不是Java 10 23。
壳牌
docker run-m100m-itadoptopenjdk/open JDK 10:nightly/bin/bash
但是,此ngithly内部版本存在一个问题,即导出的PATH指向旧的Java 10 23目录而不是10 46,需要更正此问题。
壳牌
export path=$ path:/opt/Java/open JDK/JDK-1046/bin/javacmemeat.Java
Java meme at . free memory:3566824 free memory:2796008 free memory:1480320 exceptioninthread & amp; #039; main&; #039; Java.lang.outofmemory
atmemeat.main(memeat.Java:8 ) )。
成功! 即使不指定标志,Java 10也能正确检测Dockers内存限制。
测试OpenJ9
我最近也在尝试OpenJ9。 这个免费的备用JVM已经从IBM J9开放源代码化,现在由Eclipse维护。
请在我的下一篇博文( 3358 royvanrijn.com/blog/2018/05/openj9- JVM-shootout/)中阅读openj9的详细内容。
运行速度快,内存管理非常好,性能出色,经常可以节约多达30-50%的微服务器内存。 由此,spring boot APP应用程序几乎可以定义为“micro”,其执行时间仅为100-200mb而不是300mb。 我打算尽快写一篇关于这方面的文章。
但令人惊讶的是,OpenJ9还没有用于Java 8/9/10的cgroup内存限制的类似标志( backported )的选项。 如果将以前的测试用例应用于最新的AdoptAJDK OpenJDK 9 OpenJ9 build :
壳牌
docker run-m100m-itadoptopenjdk/open JDK9- openj9/bin/bash
添加OpenJDK标记(在OpenJ9中被忽略的标记)。
壳牌
jva-xx:unlockexperimentalvmoptions-xx:usecgroupmemorylimitforheapmemeat . free memory:83988984 free memory:82940400
ops,JVM再次被Docker杀害了。
我希望立即向OpenJ9添加类似的选项。 因为我想在生产环境中运行此选项,而不是指定两次最大内存。 Eclipse/IBM正在努力解决这个问题,已经提出了issues,并向issues提出了宣传。
更新: (不推荐使用Hack ) ) ) ) )。
用有点难看/黑客的方法解决这个问题的方法是使用下面的组合标志。
壳牌
Java-xmx ` cat/sys/fs/cgroup/memory.limit _ in _ bytes ` meat. free memory:3171536 free memory:2127027 #039; syssing Etail&; #039; Java/Lang/outofmemoryerror&; #039; at 2018/05/1514:04:26-please wait.JVM dump 032 ijvmrequestedsystemdumpusing & amp; #039; core.20180515.140426.125.0001.DMP & amp; #039; inresponsetoaneventjvmdump 010 isystemdumpwrittento/core.20180515.140426.125.0001.dmpjvmrequestedheapdumpdump 032 #039; //heap dump.20180515.140426.125.0002.PhD & amp; #039; inresponsetoaneventjvmdump 010 iheapdumpwrittento/heap dump.20180515.140426.125.0002.phdjvmrequestedjavadump 032 #039; //jva core.20180515.140426.125.0003.txt & amp; #039; inresponsetoaneventjvmdump 010 ijavadumpwrittento/javacore.20180515.140426.125.0003.txtjvmrequestedsnapdump 032 #039; //snap.20180515.140426.125.0004.TRC & amp; #039; inresponsetoaneventjvmdump 010 isnapdumpwrittento/snap.20180515.140426.125.0004.trcjvmdump 013 iprocesseddumpevent & AAP #039; 系统和地图; #039;详细信息& amp; #039; Java/Lang/outofmemoryerroor
atmemeat.main(memeat.Java:8 ) )。
在这种情况下,堆大小限制为分配给Docker实例的内存,并应用于旧的JVM和OpenJ9。 这当然是错误的,因为容器本身和堆外的JVM的其他部分也使用了内存。 但它似乎在起作用,显然Docker在这种情况下很松散。 有些bash大神可能会制作更好的版本,从其他流程的字节中扣除一部分。
无论如何,请不要这样做。 它可能不能正常工作。
测试6:openj9(nightly ) )。
建议使用OpenJ9的最新nightly版本。
壳牌
docker run-m100m-itadoptopenjdk/open JDK9- openj9: nightly/bin/bash
最新的OpenJ9夜晚版本。 有两个东西。
另一个有问题的PATH参数需要首先解决这个问题
JVM支持新的徽标UseContainerSupport,如Java 10所示
壳牌
export path=$ path:/opt/Java/open JDK/JDK-9.0.412/bin/javacmemeat.Java
jva-xx:usecontainersupportmemeat . free memory:5864464 free memory:4815880 free memory:3443712 free memory:2391032 jvmdury #039; Java/Lang/outofmemoryerror&; #039; at 2018/05/1521:32:07-please wait.JVM dump 032 ijvmrequestedsystemdumpusing & amp; #039; core.20180515.213207.62.0001.DMP & amp; #039; inresponsetoaneventjvmdump 010 isystemdumpwrittento/core.20180515.213207.62.0001.dmpjvmrequestedheapdumpump 032 #039; //heap dump.20180515.213207.62.0002.PhD & amp; #039; inresponsetoaneventjvmdump 010 iheapdumpwrittento/heap dump.20180515.213207.62.0002.phdjvmrequestedjavadump 032 #039; //jva core.20180515.213207.62.0003.txt & amp; #039; inresponsetoaneventjvmdump 010 ijavadumpwrittento/jva core.20180515.213207.62.0003.txtjvmrequestedsnapdumpdump 032 #039; //snap.20180515.213207.62.0004.TRC & amp; #039; inresponsetoaneventjvmdump 010 isnapdumpwrittento/snap.20180515.213207.62.0004.trcjvmdump 013 iprocessedumpevent & ammamp #039; 系统和地图; #039;详细信息& amp; #039; Java/lang/outofmemoryerror
TADAAA,正在修复!
奇怪的是,在OpenJ9中缺省情况下未启用此标志。 与Java 10相同。 再一次:确保您已经测试了这是您想要在Docker容器中运行Java的内容。
结论
简单来说,请注意资源限制的不一致。 测试你的内存设置和JVM标志,不要假设任何事情。
在Docker容器中运行Java时,请设置Docker内存限制,并确保在JVM中也设置了限制,或者JVM可以理解这些限制。
如果无法升级Java版本,请使用-Xmx设置自己的限制。
对于Java 8和Java 9,请更新为最新版本后使用。
- xx:unlockexperimentalvmoptions-xx:usecgroupmemorylimitforheap
对于Java 10,请确保支持" UseContainerSupport "“更新为最新版本”。
对于OpenJ9,强烈建议使用-Xmx设置限制,但不久将出现支持UseContainerSupport标志的版本。
详情请访问云服务器、域名注册、虚拟主机的问题,请访问西部数码代理商官方网站: www.chenqinet.cn