前言
你如何定義效能?
當被問及應用程式的效能時,大部分開發人員會假定他們需要測量某些速度值,比如每秒交易數,或者處理了多少吉位元組(GB)資料……,要在儘可能短的時間裡完成大量工作。
如果你是應用程式架構師,那你可能會測量更廣泛的指標。與按直線邏輯執行的程式相比,你或許更關注資源利用率。你可能更重視服務間連線的效能,而不是服務本身的效能。如果你要為公司做出業務決策,應用程式的效能很多時候不是用時間而是用美元來計算的。你可能會與開發人員和架構師爭論資源分配,權衡 DevOps 的成本和完成公司工作所需要的時間。
有些時候,甚至是大部分時候,“好的”編碼模式盛行:小方法會恰當內聯,介面和型別檢查成本變低,JIT 編譯器生成的原生程式碼緊湊又高效。但是其他時候,考慮到編譯器和 CPU 的限制,我們需要手動調整程式碼,改變抽象和架構。有些時候,物件幾乎是沒什麼成本的,都不用考慮我們會消耗記憶體頻寬和垃圾收集週期。其他時候,我們要處理 TB甚至更大規模的資料集,這時候即使是最好的垃圾收集器和記憶體子系統,也要承受很大壓力。
而現在,效能問題的答案是瞭解你的工具。通常這意味著你不但要了解Java 語言是如何工作的,還要知道 JVM 類庫、記憶體、編譯器、垃圾收集器和應用程式執行所在的硬體是如何互動的。在我從事 JRuby 專案的工作中,我學到一個有關 JVM 的不變的真理:所有的效能問題都沒有單一的解決方案,而是有很多解決方案。技巧就是找到那些方案,並把最能滿足要求的拼湊起來。
學習如何平衡應用程式的設計和可用的資源,如何監控和調優JVM,如何利用比老舊的類庫和模式更高效的最新 Java 技術,如何讓Java 執行如飛!
對Java開發人員而言,這是一個激動人心的時刻,從來沒有這麼多機會在 Java 平臺上構建高效、響應式的應用程式。讓我們開始吧。
本篇文章將給大家分享Java效能最佳化實踐:JVM調優策略、工具與技巧,因為篇幅過多隻能給大家展現出來部分的內容,希望大家能夠理解與喜歡!!
首先看目錄其次,看主要內容第1章明確最佳化與效能;最佳化 Java 或其他語言程式碼的效能經常被視作一種暗黑藝術。效能分析有種神秘感,人們常常將其看作孤獨的駭客在絞盡腦汁、深思熟慮之後練就的手藝。(孤獨的駭客也是好萊塢最喜歡的關於計算機和操作人員的電影橋段之一。)畫面是這樣的:一個人能夠深入瞭解某個系統,提出神奇的解決方案,使計算機執行得更快。
影像中經常夾雜這種不幸但常見的情況:軟體團隊沒那麼重視效能。進而出現的場景是,只有當系統已經陷入麻煩時,團隊才會加以分析。所以也就需要效能“英雄”來救場了。不過現實情況有點不同。
事實是,效能分析是堅實的經驗主義和軟性的人類心理學的奇異組合。重點在於,一方面是可觀測指標的絕對數字,另一方面是終端使用者和干係人如何看待這些數字。本文其餘部分的主題就是如何解決這一明顯的悖論。
第2章JVM概覽;Java 無疑是地球上最大的技術平臺之一,根據 Oracle 的資料,該平臺擁有大約 900 萬到 1000 萬的開發人員。按照設計,很多開發人員不需要了解平臺底層的複雜機制。這就導致一種情況,只有當客戶抱怨效能時,他們才會遇到這些細節問題。
不過對於關心效能的開發人員而言,理解 JVM 技術棧的基礎非常重要。瞭解 JVM 技術可使開發人員編寫出更好的軟體,併為調查與效能相關的問題提供必要的理論背景。
本章將介紹 JVM 如何執行 Java,為後面章節更深入地探索這些主題打下基礎。特別是第 9 章會深入介紹位元組碼。讀者可以選擇現在閱讀本章,也可以在理解了其他主題之後結合第 9 章一起重讀。
本章簡要介紹了 JVM 的整體結構。雖然我們只能觸及一些最重要的主題,但事實上,這裡提到的幾乎每個主題背後都有豐富完整的內容,值得進一步研究。
第3章硬體與作業系統;在過去的 20 年裡,處理器設計和現代硬體發生了巨大的變化。在摩爾定律和工程上的限制(特別是記憶體速度相對較慢)的驅動下,處理器設計的進步已經變得讓人有點難以理解。快取未命中率已經成為衡量一個應用程式效能最明顯的領先指標。
在 Java 領域,JVM 的設計允許它使用額外的處理器核心,甚至對於單執行緒應用程式程式碼也是如此。這意味著與其他環境相比,Java 應用程式已經從硬體趨勢中獲得了明顯的效能優勢。
隨著摩爾定律的消逝,人們的注意力再次轉向軟體的相對效能上。注重效能的工程師至少需要了解現代硬體和作業系統的基本要點,以確保他們能夠充分利用硬體,而不是反其道而行之。
本章的後半部分將概述一些可能會困擾效能測試或團隊的常見反模式,並闡釋如何重構的解決方案,以防止它們成為團隊的問題。
當評估效能結果時,一定要以恰當的方式處理資料,避免陷入不科學、主觀的思考中。本章介紹了一些測試型別、測試最佳實踐以及效能分析中伴生的反模式。
下一章將研究底層的效能測量手段、微基準測試的陷阱,以及一些用於處理從 JVM 中測得的原始結果的統計技術。
第5章微基準測試與統計;本章將考慮直接測量 Java 效能數字的具體細節。JVM 的動態特性意味著效能數字往往比許多開發人員預期的要更難處理。因此,網際網路上出現了許多不準確或帶有誤導性的效能數字。
本章的一個主要目標是確保你意識到這些可能的陷阱,並且只生成你和其他人可以信賴的效能數字。特別需要注意的是,對小塊 Java 程式碼的測量(微基準測試)非常微妙且難以正確完成,這也是本章將要探究的主要內容,同時我們還會介紹效能工程師應該如何正確使用它。
標記和清除收集;物件在 HotSpot 內部的執行時表示;弱分代假說;HotSpot 的記憶體子系統例項;並行收集器;分配及其核心作用。下一章將討論垃圾收集的調優、監控和分析。有些主題在本章已經出現過,特別是分配,以及過早晉升等特殊效應,這些內容對於接下來的目標和主題也特別重要,經常回來參考本章可能會很有幫助。
第7章垃圾收集高階話題;上一章介紹了 Java 垃圾收集的基本理論。以此為起點,本章將進一步研究現代 Java 垃圾收集器的理論。這個領域有很多不可避免的權衡可以指導工程師如何選擇收集器。
首先,本章將介紹並深入瞭解 HotSpot JVM 提供的其他收集器,其中包括停頓時間超短、通常為併發的收集器(CMS)和現代通用收集器(G1)。
此外,還將考慮一些比較少見的收集器,包括:
ShenandoahC4均衡(balanced)收集器遺留的 HotSpot 收集器並非所有這些收集器都在 HotSpot 虛擬機器中使用,我們還將討論另外兩個虛擬機器的收集器: IBM J9(IBM 的一款 JVM,之前是閉源的,目前正在逐步開源)和 Azul Zing(一款專有的 JVM),我們在 2.6 節曾介紹過。
第8章垃圾收集日誌、監控、調優及工具;本章介紹了垃圾收集調優藝術的一些皮毛。這裡演示的技術大多是針對個別收集器的,但也有一些普遍適用的基本技術。本章還介紹了一些處理垃圾收集日誌的基本原則以及一些有用的工具。
第9章JVM上的程式碼執行;JVM 的初始程式碼執行環境是位元組碼直譯器。本章探討了直譯器的基礎知識,因為要正確理解 JVM 的程式碼執行,掌握位元組碼如何工作的知識是必不可少的。此外,我們還介紹了 JIT 編譯的基本理論。
然而,對於大多數效能工作而言,JIT 編譯的程式碼的行為遠比直譯器的任何方面重要。下一章將在本章介紹的基礎上,深入研究 JIT 編譯的理論與實踐。
對於許多應用程式而言,本章所演示的針對程式碼快取的簡單調優技術已經足夠了。但對於那些對效能特別敏感的應用程式來說,可能還需要對JIT 行為進行更深入地探索。下一章將介紹一些對要求更嚴格的應用程式進行調優的工具和技術。
第10章理解即時編譯;本章將深入介紹 JVM 中 JIT 編譯器的內部工作方式。大部分內容直接適用於 HotSpot,不過並不保證和其他 JVM 實現一致。
我們曾經提到過,與 JIT 編譯相關的科學研究已經相當深入,不只是JVM,很多現代程式設計環境也用到了 JIT。因此,很多 JIT 技術也適用於其他 JIT 編譯器。
因為這個主題非常抽象,而且技術上也很複雜,所以需要一些工具來幫我們理解 JVM 的內部工作方式,並將其以視覺化方式表現出來。本章將使用的主要工具是 JITWatch,我們會先加以介紹。之後會解釋具體的JIT 最佳化和特性,並演示如何透過 JITWatch 觀察該技術及其效果。
最後我們探討了另外兩個與平臺級別關係密切的應用程式效能方面的考慮:終結化和方法控制代碼。雖然很多開發人員在日常工作中並不會遇到這兩個概念,但對於關注效能的工程師來說,瞭解和認識它們可以充實自己的技術工具箱。
第12章併發效能技術;在迄今為止的計算歷史上,軟體開發人員通常以順序格式編寫程式碼。程式設計語言和硬體一般只提供一次處理指令的能力。許多情況下,人們享受到了所謂的“免費午餐”就是購買最新的硬體來提高應用程式的效能。晶片上可用的電晶體數量的增加帶來了處理指令效能更好、更強的處理器。
許多讀者都曾遇見過這樣的情況:將軟體搬到一個更大或更新的機器上就能解決容量問題,而不用花錢去查詢底層問題或考慮不同的程式設計正規化。
對於希望使用多執行緒來改進應用程式效能之前應該考慮哪些主題,本章只觸及了一些表面內容。在將單執行緒應用程式轉變為併發設計的時候,應該:
確保能夠準確測量線性處理的效能;應用一個變化,並測試效能是否真的得到了提高;確保效能測試易於重新執行,特別是當系統處理的資料大小可能發生變化時。第13章剖析;在程式設計師群體中,剖析(profiling)這個術語的使用並不是非常統一。事實上,可能的剖析方法有很多種,其中最常見的有以下兩種:
執行分配本章將涵蓋這兩個主題。首先重點關注執行剖析,我們會藉著這個主題來介紹可用於剖析程式的工具。之後會介紹記憶體剖析,看一看各種工具是如何提供這種能力的。
我們將探討對於 Java 開發人員和效能工程師而言,瞭解剖析器的一般操作方式是多麼重要。因為剖析器有可能扭曲應用程式的行為,並表現出明顯的偏差。
執行剖析是效能剖析的領域之一,在這個領域中,這些偏差就會凸顯出來。謹慎的效能工程師會意識到這種可能性,並透過各種方式來彌補,包括使用多種工具進行剖析,以瞭解真正發生的情況。
對於效能工程師而言,同樣重要的是要解決自己的認知偏差,不要致力於挖掘符合自己預期的效能行為。在第 4 章中遇到的反模式和認知陷阱就是我們訓練自己避免這些問題的一個很好的開始。
第14章高效能日誌和訊息系統;日誌記錄是所有生產級應用程式中不可或缺的一部分,所使用的日誌記錄器的型別對整體的應用程式效能有非常大的影響。當涉及日誌記錄時,重要的是要將應用程式當作一個整體而不只是執行日誌記錄語句來考慮,同時注意它對其他 JVM 子系統(如執行緒使用和垃圾收集)的影響。
本章包含一些低延遲庫的簡單例子,從最底層開始,一直到一個完整的訊息系統實現。顯然,應該把低延遲系統的目的和目標應用於整個軟體棧,從最底層的佇列一直到更高層次的應用。低延遲、高吞吐量的系統需要大量的思考、經驗和控制,這裡討論的許多開源專案是基於大量豐富的經驗構建起來的。如果你需要建立一個新的低延遲系統,只要能從底層設計目標一直堅持到頂層應用程式,這些專案都將為你節省幾天甚至幾周的開發時間。
本章開始提出的一個問題是 Java 和 JVM 可以在多大程度上應用於高吞吐量的應用程式。使用任何語言編寫低延遲、高吞吐量的應用程式都是非常困難的,但是在所有可用的語言中,Java 提供了最好的工具和生產效率。此外,Java 和 JVM 確實增加了另一個抽象層次,我們需要對其進行管理,並在某些情況下加以規避。同時,考慮硬體、JVM 效能和更底層的問題也非常重要。
第15章Java9以及Java的未來方向;自第一個版本釋出以來,Java 已經發生了很大的變化,即從一開始並沒有被明確設計為一種高效能語言,到現在已經成為這樣的語言。即使Java 已擴充套件到很多新應用領域,但是核心的 Java 平臺、社群和生態系統仍然保持著健康和活力。
像 Project Metropolis 和 Graal 等大膽的新舉措,正在重新塑造核心虛擬機器。invokedynamic 也讓 Java 走出了其演進的舒適區,為下一個十年重新改造自己。Java 已經表明它不怕進行大膽的更改,比如增加值型別以及重新解決泛型存在的複雜問題。
Java/JVM 效能是一個非常有活力的領域,本章中我們看到了效能在很多領域中仍在取得進步。還有很多其他的專案我們沒有時間提及,包括Java/ 原生程式碼互動(project panama)和新的垃圾收集器(如 Oracle 的ZGC)。