Java和Docker限制的那些事兒-創(chuàng)新互聯(lián)

作者:kelvinjin2009 來源:程序師

成都創(chuàng)新互聯(lián)公司-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比云城網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式云城網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋云城地區(qū)。費(fèi)用合理售后完善,十年實(shí)體公司更值得信賴。

原文鏈接: http://www.techug.com/post/java-and-docker-memory-limits.html

Java和Docker不是天然的朋友。 Docker可以設(shè)置內(nèi)存和CPU限制,而Java不能自動(dòng)檢測(cè)到。使用Java的Xmx標(biāo)識(shí)(繁瑣/重復(fù))或新的實(shí)驗(yàn)性JVM標(biāo)識(shí),我們可以解決這個(gè)問題。

虛擬化中的不匹配

Java和Docker的結(jié)合并不是完美匹配的,最初的時(shí)候離完美匹配有相當(dāng)大的距離。對(duì)于初學(xué)者來說,JVM的全部設(shè)想就是,虛擬機(jī)可以讓程序與底層硬件無關(guān)。

那么,把我們的Java應(yīng)用打包到JVM中,然后整個(gè)再塞進(jìn)Docker容器中,能給我們帶來什么好處呢?大多數(shù)情況下,你只是在復(fù)制JVMs和Linux容器,除了浪費(fèi)更多的內(nèi)存,沒任何好處。感覺這樣子挺傻的。

Java和Docker限制的那些事兒

不過,Docker可以把你的程序,設(shè)置,特定的JDK,Linux設(shè)置和應(yīng)用服務(wù)器,還有其他工具打包在一起,當(dāng)做一個(gè)東西。站在DevOps/Cloud的角度來看,這樣一個(gè)完整的容器有著更高層次的封裝。

問題一:內(nèi)存

時(shí)至今日,絕大多數(shù)產(chǎn)品級(jí)應(yīng)用仍然在使用Java 8(或者更舊的版本),而這可能會(huì)帶來問題。Java 8(update 131之前的版本)跟Docker無法很好地一起工作。問題是在你的機(jī)器上,JVM的可用內(nèi)存和CPU數(shù)量并不是Docker允許你使用的可用內(nèi)存和CPU數(shù)量。

比如,如果你限制了你的Docker容器只能使用100MB內(nèi)存,但是呢,舊版本的Java并不能識(shí)別這個(gè)限制。Java看不到這個(gè)限制。JVM會(huì)要求更多內(nèi)存,而且遠(yuǎn)超這個(gè)限制。如果使用太多內(nèi)存,Docker將采取行動(dòng)并殺死容器內(nèi)的進(jìn)程!JAVA進(jìn)程被干掉了,很明顯,這并不是我們想要的。

為了解決這個(gè)問題,你需要給Java指定一個(gè)大內(nèi)存限制。在舊版本的Java(8u131之前),你需要在容器中通過設(shè)置-Xmx來限制堆大小。這感覺不太對(duì),你可不想定義這些限制兩次,也不太想在你的容器中來定義。

幸運(yùn)的是我們現(xiàn)在有了更好的方式來解決這個(gè)問題。從Java 9之后(8u131+),JVM增加了如下標(biāo)志:

-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap

這些標(biāo)志強(qiáng)制JVM檢查L(zhǎng)inux的cgroup配置,Docker是通過cgroup來實(shí)現(xiàn)大內(nèi)存設(shè)置的?,F(xiàn)在,如果你的應(yīng)用到達(dá)了Docker設(shè)置的限制(比如500MB),JVM是可以看到這個(gè)限制的。JVM將會(huì)嘗試GC操作。如果仍然超過內(nèi)存限制,JVM就會(huì)做它該做的事情,拋出OutOfMemoryException。也就是說,JVM能夠看到Docker的這些設(shè)置。

從Java 10之后(參考下面的測(cè)試),這些體驗(yàn)標(biāo)志位是默認(rèn)開啟的,也可以使用-XX:+UseContainerSupport來使能(你可以通過設(shè)置-XX:-UseContainerSupport來禁止這些行為)。

問題二:CPU

第二個(gè)問題是類似的,但它與CPU有關(guān)。簡(jiǎn)而言之,JVM將查看硬件并檢測(cè)CPU的數(shù)量。它會(huì)優(yōu)化你的runtime以使用這些CPUs。但是同樣的情況,這里還有另一個(gè)不匹配,Docker可能不允許你使用所有這些CPUs??上У氖?,這在Java 8或Java 9中并沒有修復(fù),但是在Java 10中得到了解決。

從Java 10開始,可用的CPUs的計(jì)算將采用以不同的方式(默認(rèn)情況下)解決此問題(同樣是通過UseContainerSupport)。

Java和Docker的內(nèi)存處理測(cè)試

作為一個(gè)有趣的練習(xí),讓我們驗(yàn)證并測(cè)試Docker如何使用幾個(gè)不同的JVM版本/標(biāo)志甚至不同的JVM來處理內(nèi)存不足。

首先,我們創(chuàng)建一個(gè)測(cè)試應(yīng)用程序,它只是簡(jiǎn)單地“吃”內(nèi)存并且不釋放它。

javaimport java.util.ArrayList;import java.util.List;public class MemEat {
    public static void main(String[] args) {
        List l = new ArrayList<>();
        while (true) {
            byte b[] = new byte[1048576];
            l.add(b);
            Runtime rt = Runtime.getRuntime();
            System.out.println( "free memory: " + rt.freeMemory() );
        }
    }}

我們可以啟動(dòng)Docker容器并運(yùn)行這個(gè)應(yīng)用程序來查看會(huì)發(fā)生什么。

測(cè)試一:Java 8u111

首先,我們將從具有舊版本Java 8的容器開始(update 111)。

shell
docker run -m 100m -it java:openjdk-8u111 /bin/bash

我們編譯并運(yùn)行MemEat.java文件:

shell
javac MemEat.java
java MemEat...free memory: 67194416free memory: 66145824free memory: 65097232Killed

正如所料,Docker已經(jīng)殺死了我們的Java進(jìn)程。不是我們想要的(!)。你也可以看到輸出,Java認(rèn)為它仍然有大量的內(nèi)存需要分配。

我們可以通過使用-Xmx標(biāo)志為Java提供大內(nèi)存來解決此問題:

shell
javac MemEat.java
java -Xmx100m MemEat...free memory: 1155664free memory: 1679936free memory: 2204208free memory: 1315752Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at MemEat.main(MemEat.java:8)

在提供了我們自己的內(nèi)存限制之后,進(jìn)程正常停止,JVM理解它正在運(yùn)行的限制。然而,問題在于你現(xiàn)在將這些內(nèi)存限制設(shè)置了兩次,Docker一次,JVM一次。

測(cè)試二:Java 8u144

如前所述,隨著增加新標(biāo)志來修復(fù)問題,JVM現(xiàn)在可以遵循Docker所提供的設(shè)置。我們可以使用版本新一點(diǎn)的JVM來測(cè)試它。

shell
docker run -m 100m -it adoptopenjdk/openjdk8 /bin/bash

(在撰寫本文時(shí),此OpenJDK Java鏡像的版本是Java 8u144)

接下來,我們?cè)俅尉幾g并運(yùn)行MemEat.java文件,不帶任何標(biāo)志:

shell
javac MemEat.java
java MemEat...free memory: 67194416free memory: 66145824free memory: 65097232Killed

依然存在同樣的問題。但是我們現(xiàn)在可以提供上面提到的實(shí)驗(yàn)性標(biāo)志來試試看:

shell
javac MemEat.java
java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap MemEat
...
free memory: 1679936
free memory: 2204208
free memory: 1155616
free memory: 1155600
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at MemEat.main(MemEat.java:8)

這一次我們沒有告訴JVM限制的是什么,我們只是告訴JVM去檢查正確的限制設(shè)置!現(xiàn)在感覺好多了。

測(cè)試三:Java 10u23

有些人在評(píng)論和Reddit上提到Java 10通過使實(shí)驗(yàn)標(biāo)志成為新的默認(rèn)值來解決所有問題。這種行為可以通過禁用此標(biāo)志來關(guān)閉:-XX:-UseContainerSupport。

當(dāng)我測(cè)試它時(shí),它最初不起作用。在撰寫本文時(shí),AdoptAJDK OpenJDK10鏡像與jdk-10+23一起打包。這個(gè)JVM顯然還是不理解UseContainerSupport標(biāo)志,該進(jìn)程仍然被Docker殺死。

shell
docker run -m 100m -it adoptopenjdk/openjdk10 /bin/bash

測(cè)試了代碼(甚至手動(dòng)提供需要的標(biāo)志):

shell
javac MemEat.java
java MemEat...free memory: 96262112free memory: 94164960free memory: 92067808free memory: 89970656Killed
java -XX:+UseContainerSupport MemEat
Unrecognized VM option 'UseContainerSupport'Error: Could not create the Java Virtual Machine.Error: A fatal exception has occurred. Program will exit.

測(cè)試四:Java 10u46(Nightly)

我決定嘗試AdoptAJDK OpenJDK 10的最新nightly構(gòu)建。它包含的版本是Java 10+46,而不是Java 10+23。

shell
docker run -m 100m -it adoptopenjdk/openjdk10:nightly /bin/bash

然而,在這個(gè)ngithly構(gòu)建中有一個(gè)問題,導(dǎo)出的PATH指向舊的Java 10+23目錄,而不是10+46,我們需要修復(fù)這個(gè)問題。

shell
export PATH=$PATH:/opt/java/openjdk/jdk-10+46/bin/javac MemEat.java
java MemEat...free memory: 3566824free memory: 2796008free memory: 1480320Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  at MemEat.main(MemEat.java:8)

成功!不提供任何標(biāo)志,Java 10依然可以正確檢測(cè)到Dockers內(nèi)存限制。

測(cè)試五:OpenJ9

我最近也在試用OpenJ9,這個(gè)免費(fèi)的替代JVM已經(jīng)從IBM J9開源,現(xiàn)在由Eclipse維護(hù)。

請(qǐng)?jiān)谖业南乱黄┪模╤ttp://royvanrijn.com/blog/2018/05/openj9-jvm-shootout/)中閱讀關(guān)于OpenJ9的更多信息。

它運(yùn)行速度快,內(nèi)存管理非常好,性能卓越,經(jīng)??梢詾槲覀兊奈⒎?wù)節(jié)省多達(dá)30-50%的內(nèi)存。這幾乎可以將Spring Boot應(yīng)用程序定義為’micro’了,其運(yùn)行時(shí)間只有100-200mb,而不是300mb+。我打算盡快就此寫一篇關(guān)于這方面的文章。

但令我驚訝的是,OpenJ9還沒有類似于Java 8/9/10+中針對(duì)cgroup內(nèi)存限制的標(biāo)志(backported)的選項(xiàng)。如果我們將以前的測(cè)試用例應(yīng)用到最新的AdoptAJDK OpenJDK 9 + OpenJ9 build:

shell
docker run -m 100m -it adoptopenjdk/openjdk9-openj9 /bin/bash

我們添加OpenJDK標(biāo)志(OpenJ9會(huì)忽略的標(biāo)志):

shell
java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap MemEat...free memory: 83988984free memory: 82940400free memory: 81891816Killed

Oops,JVM再次被Docker殺死。

我真的希望類似的選項(xiàng)將很快添加到OpenJ9中,因?yàn)槲蚁M谏a(chǎn)環(huán)境中運(yùn)行這個(gè)選項(xiàng),而不必指定大內(nèi)存兩次。 Eclipse/IBM正在努力修復(fù)這個(gè)問題,已經(jīng)提了issues,甚至已經(jīng)針對(duì)issues提交了PR。

更新:(不推薦Hack)

一個(gè)稍微丑陋/hacky的方式來解決這個(gè)問題是使用下面的組合標(biāo)志:

shell
java -Xmx`cat /sys/fs/cgroup/memory/memory.limit_in_bytes` MemEat...free memory: 3171536free memory: 2127048free memory: 2397632free memory: 1344952JVMDUMP039I Processing dump event "systhrow", detail "java/lang/OutOfMemoryError" at 2018/05/15 14:04:26 - please wait.JVMDUMP032I JVM requested System dump using '//core.20180515.140426.125.0001.dmp' in response to an eventJVMDUMP010I System dump written to //core.20180515.140426.125.0001.dmpJVMDUMP032I JVM requested Heap dump using '//heapdump.20180515.140426.125.0002.phd' in response to an eventJVMDUMP010I Heap dump written to //heapdump.20180515.140426.125.0002.phdJVMDUMP032I JVM requested Java dump using '//javacore.20180515.140426.125.0003.txt' in response to an eventJVMDUMP010I Java dump written to //javacore.20180515.140426.125.0003.txtJVMDUMP032I JVM requested Snap dump using '//Snap.20180515.140426.125.0004.trc' in response to an eventJVMDUMP010I Snap dump written to //Snap.20180515.140426.125.0004.trcJVMDUMP013I Processed dump event "systhrow", detail "java/lang/OutOfMemoryError".Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  at MemEat.main(MemEat.java:8)

在這種情況下,堆大小受限于分配給Docker實(shí)例的內(nèi)存,這適用于較舊的JVM和OpenJ9。這當(dāng)然是錯(cuò)誤的,因?yàn)槿萜鞅旧砗投淹獾腏VM的其他部分也使用內(nèi)存。但它似乎工作,顯然Docker在這種情況下是寬松的。也許某些bash大神會(huì)做出更好的版本,從其他進(jìn)程的字節(jié)中減去一部分。

無論如何,不要這樣做,它可能無法正常工作。

測(cè)試六:OpenJ9(Nightly)

有人建議使用OpenJ9的最新nightly版本。

shell
docker run -m 100m -it adoptopenjdk/openjdk9-openj9:nightly /bin/bash

最新的OpenJ9夜間版本,它有兩個(gè)東西:

另一個(gè)有問題的PATH參數(shù),需要先解決這個(gè)問題

JVM支持新標(biāo)志UseContainerSupport(就像Java 10一樣)

shell
export PATH=$PATH:/opt/java/openjdk/jdk-9.0.4+12/bin/javac MemEat.java
java -XX:+UseContainerSupport MemEat...free memory: 5864464free memory: 4815880free memory: 3443712free memory: 2391032JVMDUMP039I Processing dump event "systhrow", detail "java/lang/OutOfMemoryError" at 2018/05/15 21:32:07 - please wait.JVMDUMP032I JVM requested System dump using '//core.20180515.213207.62.0001.dmp' in response to an eventJVMDUMP010I System dump written to //core.20180515.213207.62.0001.dmpJVMDUMP032I JVM requested Heap dump using '//heapdump.20180515.213207.62.0002.phd' in response to an eventJVMDUMP010I Heap dump written to //heapdump.20180515.213207.62.0002.phdJVMDUMP032I JVM requested Java dump using '//javacore.20180515.213207.62.0003.txt' in response to an eventJVMDUMP010I Java dump written to //javacore.20180515.213207.62.0003.txtJVMDUMP032I JVM requested Snap dump using '//Snap.20180515.213207.62.0004.trc' in response to an eventJVMDUMP010I Snap dump written to //Snap.20180515.213207.62.0004.trcJVMDUMP013I Processed dump event "systhrow", detail "java/lang/OutOfMemoryError".Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

TADAAA,正在修復(fù)中!

奇怪的是,這個(gè)標(biāo)志在OpenJ9中默認(rèn)沒有啟用,就像它在Java 10中一樣。再說一次:確保你測(cè)試了這是你想在一個(gè)Docker容器中運(yùn)行Java。

結(jié)論

簡(jiǎn)言之:注意資源限制的不匹配。測(cè)試你的內(nèi)存設(shè)置和JVM標(biāo)志,不要假設(shè)任何東西。

如果您在Docker容器中運(yùn)行Java,請(qǐng)確保你設(shè)置了Docker內(nèi)存限制和在JVM中也做了限制,或者你的JVM能夠理解這些限制。

如果您無法升級(jí)您的Java版本,請(qǐng)使用-Xmx設(shè)置您自己的限制。

對(duì)于Java 8和Java 9,請(qǐng)更新到最新版本并使用:

-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap

對(duì)于Java 10,確保它支持’UseContainerSupport’(更新到最新版本)。

對(duì)于OpenJ9(我強(qiáng)烈建議使用,可以在生產(chǎn)環(huán)境中有效減少內(nèi)存占用量),現(xiàn)在使用-Xmx設(shè)置限制,但很快會(huì)出現(xiàn)一個(gè)支持UseContainerSupport標(biāo)志的版本。

網(wǎng)站標(biāo)題:Java和Docker限制的那些事兒-創(chuàng)新互聯(lián)
瀏覽路徑:http://muchs.cn/article42/psdhc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站建設(shè)響應(yīng)式網(wǎng)站、網(wǎng)頁設(shè)計(jì)公司、面包屑導(dǎo)航域名注冊(cè)、服務(wù)器托管

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

營(yíng)銷型網(wǎng)站建設(shè)