1. 程式人生 > >Docker面對Java將不再尷尬:Java 10為Docker做了特殊優化

Docker面對Java將不再尷尬:Java 10為Docker做了特殊優化

【編者的話】在過去的幾年中,Docker一直是非常受歡迎的容器技術,而原因也很簡單。將基於JVM的應用程式容器化部署,可以為應用程式提供一致的開發、部署環境以及零耦合的環境隔離。

但不幸的是,目前的JVM在 Linux容器內執行事務並不那麼簡單。因此,為了優化一些問題,Java 9和10做了很多非常必要的改進,這裡我們重點說三點。

堆(Heap)大小

預設情況下,在64位的伺服器中,JVM通常將最大堆大小設定為實體記憶體的1/4。而在容器化環境中,這確實沒有什麼意義,因為你通常擁有很多可以執行多個JVM的大記憶體的伺服器。如果你在不同的容器中執行10個JVM,並且每個JVM最終都使用了1/4的RAM,那麼你將面臨過度使用機器RAM的窘境,並且有可能最終導致虛擬記憶體耗盡——結局就是使用者將離你而去。

這還會抵消容器的另一項重要優勢,即構建及測試的容器映象必然能夠在生產環境中擁有同樣的執行效果。在較小的物理主機上的映象環境中,一個容器可以很容易地正常工作,但在生產環境較大的主機上可能會因為超出容器的任何記憶體限制而被核心殺死。

對此有各種解決方法,例如包括一個JAVA_OPTIONS環境變數,可以從容器外部設定堆大小(或-XX:MaxRam)。但是,這會讓事情變得混亂,因為你需要多次複製關於容器限制的資訊——一次在容器中,一次為JVM。當然,你也可以編寫JVM啟動指令碼,從proc檔案系統中提取正確的記憶體限制。但都不會讓你優雅的解決問題。

在Linux上隔離容器的主要機制是通過控制組(CGroups),這些機制允許(除其他外)限制資源到一組程序。 使用Java 10,JVM將讀取容器CGroup中的記憶體限制和使用情況,並使用它來初始化最大記憶體,從而消除對這些變通辦法中的任何一種的需求。

可用的CPU

預設情況下,Docker容器可以無限制地訪問系統上的所有CPU。將利用率限制在一定比例的CPU time(使用CPU份額)或系統的各個CPU範圍(使用cpusets)是 可能的也是常見的

不幸的是,與堆大小一樣,Java 8中的JVM大多不知道用於限制容器內CPU利用率的各種機制。這可能會導致在具有多個核心的大型物理主機上出現問題,因為在容器內執行的所有JVM都會假定它們可以訪問比實際更多的CPU。

這樣做的結果是,JVM的許多部分將根據可用的處理器進行自適應大小調整,例如具有並行性和併發性的JIT編譯器執行緒和ForkJoin池,其大小將錯誤調整,從而產生的執行緒數量大於他們所期望的數量並且這可能會導致過多的上下文切換以及生產中糟糕的效能。許多第三方實用程式,庫和應用程式也使用Runtime.availableProcessors()方法來調整自己的執行緒池或展現類似的行為。

從Java 8u131和Java 9開始,JVM可以理解和利用cpusets來確定可用處理器的大小,而 Java 10則支援CPU共享

從host連線

Attach API允許從另一個JVM程式訪問JVM。它對於讀取目標JVM的環境狀態非常有用,並且在JVM代理中動態載入可以執行額外的監控,分析或診斷任務。由於連線機制與程序名稱空間的互動方式,目前無法將主機上的JVM附加到在Docker容器內執行的JVM。

主流作業系統上的所有程序都有唯一的識別符號PID。Linux還具有PID名稱空間的概念,其中不同名稱空間中的兩個程序可以共享相同的PID。名稱空間也可以巢狀,這個功能用來隔離容器內的程序。

連線機制的複雜性在於容器內部的JVM當前沒有在容器外的PID概念。Java 10通過容器內的JVM在根名稱空間中找到它的PID並使用它來監視JVM,據此來 修復此問題