Java 8 終於支持 Docker!


Java 8曾經與Docker無法很好地兼容性,現在問題已消失。

請注意:我在本文中使用采用GNU GPL v2許可證的OpenJDK官方docker映像。在Oracle Java SE中,這里描述的docker支持功能在更新191中引入。Oracle在2019年4月更改了Java 8更新的許可證,自Java SE 8 Update 211以來商業使用不再免費。

你是否遇到過在docker中運行的基於JVM的應用程序出現“隨機”故障?或者也許是一些奇怪的死機?兩者都可能是Java 8(仍廣泛使用的)中糟糕的docker支持引起的。

Docker使用控制組(cgroups)來限制資源。在容器中運行應用程序時限制內存和CPU絕對是個好主意――它可以阻止應用程序占用整個可用內存及/或CPU,這會導致在同一個系統上運行的其他容器毫無反應。限制資源可提高應用程序的可靠性和穩定性。它還允許為硬件容量作好規划。在Kubernetes或DC/OS之類的編排系統上運行容器時尤為重要。

問題

JVM可以“看到”系統上的整個內存和可用的所有CPU核心,並確保與資源一致。它默認情況下將最大堆大小(heap size)設置為系統內存的1/4,並將某些線程池大小(比如針對GC)設置為物理核心數量。不妨舉例說明。

我們將運行一個簡單的應用程序,它消耗盡可能多的內存(可在該網站上找到):

img

我們在擁有64GB內存的系統上運行,所以不妨檢查默認的最大堆大小:

如上所述,它是物理內存的1/4即16GB。如果我們使用docker cgroups限制內存,會發生什么?不妨檢查一下:

img

JVM進程被殺死了。由於它是一個子進程――容器本身幸存下來,但通常當java是容器(PID 1)內的唯一進程時,容器會崩潰。

不妨深入看看系統日志:

img

img

像這樣的故障調試起來可能很難――應用程序日志中沒有任何內容。在AWS ECS之類的托管系統上尤其困難重重。

CPU怎么樣?不妨再次檢查,運行一個顯示可用處理器數量的小程序:

不妨在一個cpu編號設置為1的docker容器中運行它:

不好,這個系統上的確有12個CPU。因此,即使可用處理器的數量限制為1,JVM也會嘗試使用12――比如說,GC線程數量由該公式設置:

在擁有N個硬件線程(N大於8)的機器上,並行收集器使用N的固定分數作為垃圾收集器線程的數量。如果N的值很大,該分數約5/8。如果N的值低於8,使用的數字是N。

在我們的情況下:

解決方案

OK,我們現在意識到了這個問題。有解決方案嗎?幸運的是,有!

新的Java版本(10及以上版本)已經內置了docker支持功能。但有時升級不是辦法,比如說如果應用程序與新JVM不兼容就不行。

好消息:Docker支持還被向后移植到Java 8。不妨檢查標記為8u212的最新openjdk映像。我們將內存限制為1G,並使用1個CPU:docker run -ti --cpus 1 -m 1G openjdk:8u212-jdk。

內存:

它是256M,正好是已分配內存的1/4。

CPU:

正如我們想要的那樣。

此外,還有幾個新的設置:

它們允許微調堆大小――這些設置的含義在StackOverflow的這個優秀答案中已得到了解釋。請注意:他們設置的是百分比,而不是固定值。正因為如此,改變Docker內存設置不會破壞任何東西。

如果由於某種原因不想要看到新的JVM行為,可以使用-XX:-UseContainerSupport來關閉。

總結

為基於JVM的應用程序設置正確的堆大小極其重要。如果使用最新的Java 8版本,你可以依賴安全(但非常保守)的默認設置。不需要在docker入口點中使用任何變通辦法,也不需要再將Xmx設置為固定值。

使用JVM愉快!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM