- Java性能權威指南(第2版)
- (美)斯科特·奧克斯
- 4222字
- 2022-05-09 14:52:33
1.2 平臺和約定
本書講的是Java性能,它受一些因素的影響:Java本身的版本,以及對應的硬件和軟件平臺。
1.2.1 Java平臺
本書介紹Oracle HotSpot JVM和Java開發(fā)工具包(JDK)的性能優(yōu)化,對應的版本是Java 8和Java 11。這也是我們所熟知的Java標準版(Java SE,Java Standard Edition)。Java運行時環(huán)境(Java Runtime Environment,JRE)是JDK的子集,只包含JVM,但是JDK中的一些工具對性能分析很重要,所以JDK是本書的重點。實際上,本書還會介紹從OpenJDK倉庫衍生出來的其他平臺,包括AdoptOpenJDK項目中的JVM。嚴格意義上,在生產(chǎn)環(huán)境中使用Oracle公司的二進制文件需要得到許可,而AdoptOpenJDK的二進制文件使用的是開源協(xié)議,無須得到許可便能使用。對本書來說,兩者是一樣的,我們都稱之為JDK或Java平臺1。
1二者的差異很小,但的確存在。比如,AdoptOpenJDK版本的Java在JDK 11中包含了新的垃圾回收器。我會在需要時指出這些差異。
這些大版本經(jīng)歷了許多bug修復版本。在我寫這段話的時候,最新的Java 8版本是jdk8u222(222版本),Java 11版本是11.0.5。如果不能使用更新的版本,至少要使用這兩個版本,這很重要,特別是在用Java 8的時候。Java 8的早期版本(大約到jdk8u60)不涉及本書討論的很多重要的性能增強措施和特性,尤其是關于垃圾回收和G1垃圾回收器的。
之所以選擇上述版本的JDK,是因為可以得到Oracle公司的長期支持(LTS)。Java社區(qū)可以自由地發(fā)展自己的支持模式,但是目前他們都遵循了Oracle公司的模式。所以,上述版本在一段時間內(nèi)都可以使用,并會得到免費的維護、更新等支持:Java 8至少可以用到2023年(前期通過AdoptOpenJDK,后期通過延長的Oracle公司支持協(xié)議);Java 11至少可以用到2026年。
至于過渡版本,雖然Oracle公司和整個社區(qū)都已經(jīng)不再支持了,但在討論Java 11的時候必然會涉及Java 9和Java 10引入的特性。有時候,我提及的版本信息會不準確,我可能會說X特性和Y特性最初是由Java 11引入的,而其實它們在Java 9和Java 10中就已經(jīng)使用了。Java 11是首個包含這些特性的LTS版本,而且Java 9和Java 10已經(jīng)不再使用,那么某個特性最初的出現(xiàn)時間就不重要了。同樣,在本書出版時Java 13會發(fā)布,但是本書并不會涵蓋Java 12和Java 13的很多特性。你可以在生產(chǎn)環(huán)境中使用這些版本,但是6個月之后就需要升級到更新的版本了。(當你讀到這里的時候,Java 12可能已經(jīng)不被支持了,如果正在使用Java 13,那它馬上也會被Java 14替代。)我們會粗略地了解這些過渡版本的一些特性,但是因為這些版本在大多數(shù)情況下不會用于生產(chǎn)環(huán)境,所以本書的重點還是Java 8和Java 11。
Java語言規(guī)范還有其他的實現(xiàn),包括開源實現(xiàn)的分支。AdoptOpenJDK提供了其中一個開源實現(xiàn)(Eclipse OpenJ9),剩下的由其他廠商提供。雖然所有平臺必須通過兼容性測試才能使用Java名稱,但是本書討論的內(nèi)容不會擴展到兼容性方面,調(diào)優(yōu)標志尤其如此。所有的JVM實現(xiàn)都有一個或多個垃圾回收器,而在每個廠商的GC實現(xiàn)中,調(diào)優(yōu)標志都是其產(chǎn)品所特有的。因此,雖然本書所述概念適用于任何Java實現(xiàn),但具體的調(diào)優(yōu)標志和建議只適用于HotSpot JVM。
調(diào)優(yōu)標志及其默認值會隨著版本的變化而變化,早期版本的HotSpot JVM也是如此。本書討論的調(diào)優(yōu)標志對Java 8(特別是222版本)和Java 11(特別是11.0.5版本)有效。這些信息在之后的版本中可能會有細微的變化。請隨時查閱版本說明以知曉重要的變化。
在API層面,不同的JVM更具兼容性。盡管如此,在Oracle HotSport Java平臺和其他平臺上,某一特定類的實現(xiàn)可能仍然存在微小的差異。這些類在功能上等效,但是實際的實現(xiàn)方式可能有所不同。幸運的是,這種情況很少見,不太可能對性能產(chǎn)生重大影響。
在本書的剩余內(nèi)容中,Java和JVM特指Oracle HotSpot實現(xiàn)。嚴格意義上,“JVM在第一次執(zhí)行時不編譯代碼”這種說法是錯誤的。有些Java實現(xiàn)在第一次執(zhí)行時的確會編譯代碼,但無論是寫起來還是讀起來,JVM都要比Oracle HotSpot JVM簡單得多。
JVM調(diào)優(yōu)標志
除了少數(shù)例外,JVM接收兩種標志:布爾標志和附帶參數(shù)的標志。
布爾標志使用的語法是:-XX:+FlagName表示開啟,-XX:-FlagName表示關閉。
附帶參數(shù)的標志使用的語法是:-XX:FlagName=something,表示設置FlagName的值為something。其中,something指表示任意值的符號。例如,-XX:NewRatio=N表示NewRatio標志可以設成任意值N(N的含義將是討論的重點)。
本書在介紹每個標志時都會討論其默認值。默認值的設定通常基于兩個因素:JVM所運行的平臺和傳給JVM的其他命令行參數(shù)。如有疑問,可參見3.2.1節(jié)。該節(jié)展示了當給出特定命令時,如何使用-XX:+PrintFlagsFinal標志(默認為false,即“關閉”狀態(tài))來確定特定環(huán)境中特定標志的默認值。根據(jù)運行環(huán)境自動調(diào)優(yōu)標志的過程被稱為自行優(yōu)化(ergonomics)。
從Oracle網(wǎng)站和AdoptOpenJDK網(wǎng)站下載的JVM,被稱作產(chǎn)品版 JVM。當從源碼構(gòu)建JVM時,可以構(gòu)建多個其他版本:調(diào)試版、開發(fā)版等。這些版本通常有其他的附加功能。特別是開發(fā)版,它包含更多的調(diào)優(yōu)標志,讓開發(fā)人員可以嘗試JVM各種算法最微小的操作。本書基本不涉及這些標志。
1.2.2 硬件平臺
本書第1版出版時,硬件環(huán)境和今天不同。那時多核機器很受歡迎,但是32位處理器和單核CPU仍大量使用。現(xiàn)在使用更多的是虛擬機和軟件容器。下面概述這些平臺對本書主題的影響。
01. 多核硬件
如今,幾乎所有的計算機都有多個核心。對于JVM(或任何其他程序)來說,這表現(xiàn)為多個CPU。每個核心往往都應用了超線程技術。超線程是Intel常用的術語,AMD(和其他廠商)則使用同時多線程,一些芯片制造商稱之為核心內(nèi)的硬件線程(hardware strands within a core)。它們指的是同一個東西,本書將這種技術稱為超線程。
對于性能來說,一臺機器的核心數(shù)量很重要。以一臺4核機器為例:(多數(shù)情況下)每個核心都可以獨立于其他核心運行,所以一臺4核機器可以實現(xiàn)單核機器4倍的吞吐量(當然這還取決于軟件情況)。
在大多數(shù)情況下,每個核心會包含兩個硬件或超線程。這些線程并不是相互獨立的,一個核心每次只能運行一個線程。線程有時候會暫停。例如,從主內(nèi)存加載值時線程會暫停,這個過程需要幾個CPU周期。單核心運行單線程時,線程暫停,這些CPU周期就浪費了。單核心運行兩個線程時,核心可以切換并執(zhí)行另一個線程的指令。
所以,如果4核機器開啟超線程,似乎就可以同時執(zhí)行8個線程的指令(盡管從技術上講,每個CPU周期只能執(zhí)行4條指令)。這對于操作系統(tǒng),也就是Java和其他應用程序來說,機器可以表現(xiàn)得像擁有8個CPU一樣。但從性能的角度來講,這些CPU并不等價。如果運行一個CPU密集型任務,它只會使用1個核心;運行兩個CPU密集型任務才會用到第2個核心;以此類推,直到第4個核心。也就是說,獨立運行4個CPU密集型任務可以得到4倍的吞吐量。
如果添加第5個任務,它只能在任何其他任務暫停時運行。該任務的運行時間平均占總時間的20%到40%。額外的任務都面臨這一挑戰(zhàn)。因此,添加第5個任務只能提高約30%的性能。結(jié)論就是,使用超線程技術得到的8個CPU能使機器性能達到單核的五六倍。
你將在后文的幾個章節(jié)中看到這個例子。垃圾回收很大程度上是CPU密集型任務,所以第5章介紹了超線程如何影響垃圾回收算法的并行化。第9章大致討論了如何充分利用Java的多線程能力,你也會看到擴展超線程核心的例子。
02. 軟件容器
這些年,Java部署的最大變化是,應用程序經(jīng)常部署在軟件容器中。這一改變當然不只局限于Java。這是行業(yè)發(fā)展的趨勢,邁向云計算的步伐加快了這一趨勢。
有兩個容器很重要。一個是虛擬機,它在運行的硬件子集上建立了完全隔離的操作系統(tǒng)副本。云計算的基礎是其廠商必須有包含大型機器的數(shù)據(jù)中心。這些機器可能有128個核心,不過由于成本原因,有些機器可能核心數(shù)較少。從虛擬機的角度講,這并不重要,因為虛擬機只可以訪問硬件的子集。因此,給定的虛擬機可能有2個核心(4個CPU,因為它們通常支持超線程技術)和16 GB內(nèi)存。
從Java和其他應用程序的角度講,這個虛擬機和常規(guī)的2核16 GB內(nèi)存的機器的區(qū)別很小。出于本書講解優(yōu)化性能的目的,你把它們當成一樣的就行。
另一個比較重要的容器是Docker。Docker容器只是操作系統(tǒng)中的一個進程(可能受資源限制),因此,運行于其內(nèi)的Java進程不一定知道它運行于其中(即便可以檢測出來)。也正是如此,它對于其他進程的CPU和內(nèi)存的隔離有些不同。你會在本書中看到Java 8的早期版本(192版本之前)和后期版本(包括Java 11)在處理方式上的異同。
虛擬機超額分配
云廠商可以在物理機上對虛擬機進行超額分配。假設物理機有32個核心,云廠商通常會部署8個4核虛擬機,這樣每個虛擬機預期有4個專用核心。
為了省錢,廠商也可以部署16個4核虛擬機。原理是,16個虛擬機不太可能同時處于工作狀態(tài);如果其中一半的虛擬機在工作,就有足夠的物理核心滿足它們;如果太多核心處于工作狀態(tài),它們就會爭奪CPU周期,機器性能就會受到影響。
同樣,云廠商還可以對虛擬機使用的CPU降頻。這就允許虛擬機在突發(fā)情況下使用它所分配的CPU,但是不會長時間維持這個狀態(tài)。這在免費或者試用的產(chǎn)品中經(jīng)常見到,此時你對性能會有不同的預期。
這些對性能的影響很大,并且不局限于影響Java。其影響對于Java和虛擬機中運行的其他程序來說并無區(qū)別。
默認情況下,Docker容器可以自由使用機器上的所有資源,包括所有可用的CPU和內(nèi)存。如果使用Docker僅僅是為了在機器上快速部署單一應用程序,并且機器只運行這一個Docker容器,那沒問題。但其實經(jīng)常需要在機器上運行多個Docker容器,還要限制每個容器的資源。鑒于我們的4核機器有16 GB內(nèi)存,我們可能希望運行兩個Docker容器,每個可獲取2個核心和8 GB內(nèi)存。
配置Docker來達到這個預期很簡單,但在Java層面可能出現(xiàn)復雜的問題。這是因為大量的Java資源是根據(jù)運行JVM的機器的大小自動(或自行)分配的,包括默認的堆大小和垃圾回收器使用的線程數(shù)量。關于垃圾回收器的詳細內(nèi)容會在第5章講到,一些線程池的設置會在第9章講到。
如果你使用的是最新版本的Java 8(192版本或更新的)或者Java 11,那么JVM會像你希望的那樣處理上述問題:如果限制Docker容器只能使用2個核心,本來基于機器CPU數(shù)量設置的值,就會根據(jù)Docker容器的限制2進行調(diào)整。同樣,堆和其他默認基于機器內(nèi)存數(shù)量的設置,也會隨著Docker容器的限制而調(diào)整。
在Java 8的早期版本中,JVM無法得知任何容器的限制:當檢測機器剩余內(nèi)存以確定默認的堆大小時,它會檢測機器的所有內(nèi)存,但我們更希望看到的是允許Docker容器使用的內(nèi)存。同樣,檢查有多少CPU可以用來優(yōu)化垃圾回收器時,它會檢查機器上所有的CPU,而不是只檢查分配給Docker容器的CPU。結(jié)果是,JVM運行狀態(tài)不理想:啟用的線程過多,設置的堆過大。線程過多會導致性能下降,但真正的問題出在內(nèi)存上:堆的最大大小比分配給Docker容器的內(nèi)存還大,當堆增大到這個大小時,Docker容器(以及JVM)會停止運行。
在早期的Java 8版本里,可以手動設置合理的內(nèi)存和CPU數(shù)量。在進行這些優(yōu)化時,我會指出這種情況下需要怎么調(diào)整,但最好還是升級到Java 8的后期版本或者Java 11。
Docker容器給Java提出了另一個難題:Java有豐富的性能問題診斷工具集,而Docker容器里沒有。我們將在第3章進一步討論這個問題。
2可以在Docker中以小數(shù)形式限制CPU數(shù)量,Java會對小數(shù)向上取整。
- Mastering Zabbix(Second Edition)
- DevOps for Networking
- Oracle Database In-Memory(架構(gòu)與實踐)
- JavaScript語言精髓與編程實踐(第3版)
- Koa開發(fā):入門、進階與實戰(zhàn)
- Practical Game Design with Unity and Playmaker
- Django 3.0入門與實踐
- MINECRAFT編程:使用Python語言玩轉(zhuǎn)我的世界
- Mapping with ArcGIS Pro
- JavaScript Unit Testing
- Flutter從0基礎到App上線
- C#程序開發(fā)參考手冊
- Mastering R for Quantitative Finance
- 開發(fā)者測試
- Learning Adobe Muse