<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                如今,Docker 等容器早已不是新生事物,正在逐步成為日常開發、部署環境的一部分。Java 能否無縫地運行在容器環境,是否符合微服務、Serverless 等新的軟件架構和場景,在一定程度上也會影響未來的技術棧選擇。當然,Java 對 Docker 等容器環境的支持也在不斷增強,自然地,Java 在容器場景的實踐也逐漸在面試中被涉及。我希望通過專欄今天這一講,能夠幫你能做到胸有成竹。 今天我要問你的問題是,Java 程序運行在 Docker 等容器環境有哪些新問題? ## 典型回答 對于 Java 來說,Docker 畢竟是一個較新的環境,例如,其內存、CPU 等資源限制是通過 CGroup(Control Group)實現的,早期的 JDK 版本(8u131 之前)并不能識別這些限制,進而會導致一些基礎問題: * 如果未配置合適的 JVM 堆和元數據區、直接內存等參數,Java 就有可能試圖使用超過容器限制的內存,最終被容器 OOM kill,或者自身發生 OOM。 * 錯誤判斷了可獲取的 CPU 資源,例如,Docker 限制了 CPU 的核數,JVM 就可能設置不合適的 GC 并行線程數等。 從應用打包、發布等角度出發,JDK 自身就比較大,生成的鏡像就更為臃腫,當我們的鏡像非常多的時候,鏡像的存儲等開銷就比較明顯了。 如果考慮到微服務、Serverless 等新的架構和場景,Java 自身的大小、內存占用、啟動速度,都存在一定局限性,因為 Java 早期的優化大多是針對長時間運行的大型服務器端應用。 ## 考點分析 今天的問題是個針對特定場景和知識點的問題,我給出的回答簡單總結了目前業界實踐中發現的一些問題。 如果我是面試官,針對這種問題,如果你確實沒有太多 Java 在 Docker 環境的使用經驗,直接說不知道,也算是可以接受的,畢竟沒有人能夠掌握所有知識點嘛。 但我們要清楚,有經驗的面試官,一般不會以純粹偏僻的知識點作為面試考察的目的,更多是考察思考問題的思路和解決問題的方法。所以,如果有基礎的話,可以從操作系統、容器原理、JVM 內部機制、軟件開發實踐等角度,展示系統性分析新問題、新場景的能力。畢竟,變化才是世界永遠的主題,能夠在新變化中找出共性與關鍵,是優秀工程師的必備能力。 今天我會圍繞下面幾個方面展開: * 面試官可能會進一步問到,有沒有想過為什么類似 Docker 這種容器環境,會有點“欺負”Java?從 JVM 內部機制來說,問題出現在哪里? * 我注意到有種論調說“沒人在容器環境用 Java”,不去爭論這個觀點正確與否,我會從工程實踐出發,梳理問題原因和相關解決方案,并探討下新場景下的最佳實踐。 ## 知識擴展 首先,我們先來搞清楚 Java 在容器環境的局限性來源,**Docker 到底有什么特別**? 雖然看起來 Docker 之類容器和虛擬機非常相似,例如,它也有自己的 shell,能獨立安裝軟件包,運行時與其他容器互不干擾。但是,如果深入分析你會發現,Docker 并不是一種完全的**虛擬化**技術,而更是一種輕量級的**隔離**技術。 ![](https://img.kancloud.cn/a0/69/a069a294d32d7778f3410192221358fb_733x382.png) 上面的示意圖,展示了 Docker 與虛擬機的區別。從技術角度,基于 namespace,Docker 為每個容器提供了單獨的命名空間,對網絡、PID、用戶、IPC 通信、文件系統掛載點等實現了隔離。對于 CPU、內存、磁盤 IO 等計算資源,則是通過 CGroup 進行管理。如果你想了解更多 Docker 的細節,請參考相關[技術文檔](https://medium.freecodecamp.org/a-beginner-friendly-introduction-to-containers-vms-and-docker-79a9e3e119b)。 Docker 僅在類似 Linux 內核之上實現了有限的隔離和虛擬化,并不是像傳統虛擬化軟件那樣,獨立運行一個新的操作系統。如果是虛擬化的操作系統,不管是 Java 還是其他程序,只要調用的是同一個系統 API,都可以透明地獲取所需的信息,基本不需要額外的兼容性改變。 容器雖然省略了虛擬操作系統的開銷,實現了輕量級的目標,但也帶來了額外復雜性,它限制對于應用不是透明的,需要用戶理解 Docker 的新行為。所以,有專家曾經說過,“幸運的是 Docker 沒有完全隱藏底層信息,但是不幸的也是 Docker 沒有隱藏底層信息!” 對于 Java 平臺來說,這些未隱藏的底層信息帶來了很多意外的困難,主要體現在幾個方面: 第一,容器環境對于計算資源的管理方式是全新的,CGroup 作為相對比較新的技術,歷史版本的 Java 顯然并不能自然地理解相應的資源限制。 第二,namespace 對于容器內的應用細節增加了一些微妙的差異,比如 jcmd、jstack 等工具會依賴于“/proc//”下面提供的部分信息,但是 Docker 的設計改變了這部分信息的原有結構,我們需要對原有工具進行[修改](https://bugs.openjdk.java.net/browse/JDK-8179498)以適應這種變化。 **從 JVM 運行機制的角度,為什么這些“溝通障礙”會導致 OOM 等問題呢?** 你可以思考一下,這個問題實際是反映了 JVM 如何根據系統資源(內存、CPU 等)情況,在啟動時設置默認參數。 這就是所謂的[Ergonomics](https://docs.oracle.com/javase/10/gctuning/ergonomics.htm#JSGCT-GUID-DB4CAE94-2041-4A16-90EC-6AE3D91EC1F1)機制,例如: * JVM 會大概根據檢測到的內存大小,設置最初啟動時的堆大小為系統內存的 1/64;并將堆最大值,設置為系統內存的 1/4。 * 而 JVM 檢測到系統的 CPU 核數,則直接影響到了 Parallel GC 的并行線程數目和 JIT complier 線程數目,甚至是我們應用中 ForkJoinPool 等機制的并行等級。 這些默認參數,是根據通用場景選擇的初始值。但是由于容器環境的差異,Java 的判斷很可能是基于錯誤信息而做出的。這就類似,我以為我住的是整棟別墅,實際上卻只有一個房間是給我住的。 更加嚴重的是,JVM 的一些原有診斷或備用機制也會受到影響。為保證服務的可用性,一種常見的選擇是依賴“-XX:OnOutOfMemoryError”功能,通過調用處理腳本的形式來做一些補救措施,比如自動重啟服務等。但是,這種機制是基于 fork 實現的,當 Java 進程已經過度提交內存時,fork 新的進程往往已經不可能正常運行了。 根據前面的總結,似乎問題非常棘手,那我們在實踐中,**如何解決這些問題呢?** 首先,如果你能夠**升級到最新的 JDK 版本**,這個問題就迎刃而解了。 * 針對這種情況,JDK 9 中引入了一些實驗性的參數,以方便 Docker 和 Java“溝通”,例如針對內存限制,可以使用下面的參數設置: ~~~ -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap ~~~ 注意,這兩個參數是順序敏感的,并且只支持 Linux 環境。而對于 CPU 核心數限定,Java 已經被修正為可以正確理解“–cpuset-cpus”等設置,無需單獨設置參數。 * 如果你可以切換到 JDK 10 或者更新的版本,問題就更加簡單了。Java 對容器(Docker)的支持已經比較完善,默認就會自適應各種資源限制和實現差異。前面提到的實驗性參數“UseCGroupMemoryLimitForHeap”已經被標記為廢棄。 與此同時,新增了參數用以明確指定 CPU 核心的數目。 ~~~ -XX:ActiveProcessorCount=N ~~~ 如果實踐中發現有問題,也可以使用“-XX:-UseContainerSupport”,關閉 Java 的容器支持特性,這可以作為一種防御性機制,避免新特性破壞原有基礎功能。當然,也歡迎你向 OpenJDK 社區反饋問題。 * 幸運的是,JDK 9 中的實驗性改進已經被移植到 Oracle JDK 8u131 之中,你可以直接下載相應[鏡像](https://store.docker.com/images/oracle-serverjre-8),并配置“UseCGroupMemoryLimitForHeap”,后續很有可能還會進一步將 JDK 10 中相關的增強,應用到 JDK 8 最新的更新中。 但是,如果我暫時只能使用老版本的 JDK 怎么辦? 我這里有幾個建議: * 明確設置堆、元數據區等內存區域大小,保證 Java 進程的總大小可控。 例如,我們可能在環境中,這樣限制容器內存: ~~~ $ docker run -it --rm --name yourcontainer -p 8080:8080 -m 800M repo/your-java-container:openjdk ~~~ 那么,就可以額外配置下面的環境變量,直接指定 JVM 堆大小。 ~~~ -e JAVA_OPTIONS='-Xmx300m' ~~~ * 明確配置 GC 和 JIT 并行線程數目,以避免二者占用過多計算資源。 ~~~ -XX:ParallelGCThreads -XX:CICompilerCount ~~~ 除了我前面介紹的 OOM 等問題,在很多場景中還發現 Java 在 Docker 環境中,似乎會意外使用 Swap。具體原因待查,但很有可能也是因為 Ergonomics 機制失效導致的,我建議配置下面參數,明確告知 JVM 系統內存限額。 ~~~ -XX:MaxRAM=`cat /sys/fs/cgroup/memory/memory.limit_in_bytes` ~~~ 也可以指定 Docker 運行參數,例如: ~~~ --memory-swappiness=0 ~~~ 這是受操作系統[Swappiness](https://en.wikipedia.org/wiki/Swappiness)機制影響,當內存消耗達到一定門限,操作系統會試圖將不活躍的進程換出(Swap out),上面的參數有顯式關閉 Swap 的作用。所以可以看到,Java 在 Docker 中的使用,從操作系統、內核到 JVM 自身機制,需要綜合運用我們所掌握的知識。 回顧我在專欄第 25 講 JVM 內存區域的介紹,JVM 內存消耗遠不止包括堆,很多時候僅僅設置 Xmx 是不夠的,MaxRAM 也有助于 JVM 合理分配其他內存區域。如果應用需要設置更多 Java 啟動參數,但又不確定什么數值合理,可以試試一些社區提供的[工具](https://github.com/cloudfoundry/java-buildpack-memory-calculator),但要注意通用工具的局限性。 更進一步來說,對于容器鏡像大小的問題,如果你使用的是 JDK 9 以后的版本,完全可以使用 jlink 工具定制最小依賴的 Java 運行環境,將 JDK 裁剪為幾十 M 的大小,這樣運行起來并不困難。 今天我從 Docker 環境中 Java 可能出現的問題開始,分析了為什么容器環境對應用并不透明,以及這種偏差干擾了 JVM 的相關機制。最后,我從實踐出發,介紹了主要問題的解決思路,希望對你在實際開發時有所幫助。 ## 一課一練 關于今天我們討論的題目你做到心中有數了嗎?今天的思考題是,針對我提到的微服務和 Serverless 等場景 Java 表現出的不足,有哪些方法可以改善 Java 的表現?
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看