system.gc 性能_使用这些先进的GC技术提高应用程序性能
system.gc 性能
應用程序性能是我們關注的重點,垃圾收集優化是取得小而有意義的進步的好地方
自動化垃圾收集(與JIT HotSpot編譯器一起)是JVM中最先進,最有價值的組件之一,但是許多開發人員和工程師對垃圾收集(GC),其工作方式以及如何影響應用程序性能的了解都很少。 。
首先,GC還能做什么? 垃圾收集是堆中對象的內存管理過程。 將對象分配給堆時,它們會經歷幾個收集階段–通常相當快,因為??堆中的大多數對象的壽命都很短。
垃圾收集事件包含三個階段-標記,刪除和復制/壓縮。 在第一階段,GC遍歷堆,并將所有內容標記為活動(引用)對象,未引用對象或可用內存空間。 然后刪除未引用的對象,并壓縮剩余的對象。 在分代的垃圾收集中,對象“老化”并通過生活中的3個空間(伊甸園,幸存者空間和保有權(舊)空間)得到提升。 這種移動也作為壓實階段的一部分發生。
但是足夠了,讓我們進入有趣的部分!
了解Java中的垃圾回收(GC)
自動化GC的一大優點是,開發人員實際上不需要了解其工作原理。 不幸的是,這意味著許多開發人員不了解其工作原理。 了解垃圾收集和許多可用的GC,有點像了解Linux CLI命令。 從技術上講,您并不需要使用它們,但是了解和使用它們會對您的生產率產生重大影響。
就像CLI命令一樣,這里有絕對的基礎知識。 ls命令查看父文件夾中的文件夾列表, mv將文件從一個位置移動到另一個位置,等等。在GC中,這些類型的命令等同于知道有多個GC可供選擇,并且GC可能引起性能問題。 當然,還有很多東西要學習(關于使用Linux CLI和垃圾回收)。
了解Java的垃圾回收過程的目的不僅是免費(無聊)的對話入門者,其目的還在于學習如何針對特定環境有效地實現和維護具有最佳性能的正確GC。 知道垃圾回收會影響應用程序性能是基礎,并且有許多先進的技術可以增強GC性能并減少其對應用程序可靠性的影響。
GC性能問題
1.內存泄漏–
通過了解堆結構以及如何執行垃圾回收,我們知道內存使用率逐漸增加,直到發生垃圾回收事件并且使用率回落為止。 被引用對象的堆利用率通常保持穩定,因此下降幅度應大致相同。
發生內存泄漏時,每個GC事件都會清除一小部分堆對象(盡管沒有使用許多留在后面的對象),因此堆利用率將繼續增加,直到堆內存已滿,并且將拋出OutOfMemoryError異常。 原因是GC僅將未引用的對象標記為刪除。 因此,即使不再使用引用的對象,也不會從堆中清除該對象。 有一些有用的編碼技巧可以防止這種情況,稍后我們將介紹。
2.連續的“停止世界”活動–
在某些情況下,垃圾回收可以稱為Stop the World事件,因為當垃圾回收發生時,JVM中的所有線程(因此,在其上運行的應用程序)都將停止以允許GC執行。 在健康的應用程序中,GC執行時間相對較短,并且不會對應用程序性能產生較大影響。
但是,在欠佳的情況下,“世界停止”事件可能會嚴重影響應用程序的性能和可靠性。 如果GC事件需要“停止世界”暫停并需要2秒鐘的時間執行,則該應用程序的最終用戶將遇到2秒的延遲,因為運行該應用程序的線程被停止以允許GC。
當發生內存泄漏時,連續的“停止世界”事件也是有問題的。 由于每次執行GC都會清除較少的堆內存空間,因此剩余內存填滿的時間會更少。 當內存已滿時,JVM會觸發另一個GC事件。 最終,JVM將運行重復的Stop the World事件,從而引起嚴重的性能問題。
3. CPU使用率–
而這全都取決于CPU使用率。 連續GC /“停止世界”事件的主要癥狀是CPU使用率激增。 GC是一項計算量很大的操作,因此可能會花費比CPU功率更多的份額。 對于運行并發線程的GC,CPU使用率可能更高。 為您的應用程序選擇合適的GC將對CPU使用率產生最大的影響,但是還有其他方法可以優化以提高該領域的性能。
從圍繞垃圾收集的性能問題中我們可以了解到,盡管高級GC獲得了(并且它們已經相當先進),但其致命弱點仍然保持不變。 冗余且不可預測的對象分配。 為了提高應用程序性能,僅選擇正確的GC是不夠的。 我們需要知道流程的工作方式,并且需要優化代碼,以使我們的GC不會占用過多的資源或在應用程序中造成過多的暫停。
世代GC
在深入研究不同的Java GC及其對性能的影響之前,了解世代垃圾收集的基礎很重要。 世代GC的基本概念基于這樣的思想,即對堆中某個對象的引用存在的時間越長,將其標記為刪除的可能性就越小。 通過使用具有象征意義的“年齡”標記對象,可以將它們分隔到不同的存儲空間,以使GC進行標記的頻率降低。
將對象分配給堆時,會將其放置在所謂的Eden空間中。 那是對象開始的地方,在大多數情況下,它們是標記為刪除的地方。 在該階段幸存的對象“慶祝生日”,并復制到Survivor空間。 此過程如下所示:
伊甸園和幸存者空間組成了年輕一代。 這是大部分操作發生的地方。 當(如果)年輕一代中的某個對象達到一定年齡時,該對象將被提升到終身(也稱為舊)空間。 根據年齡劃分對象內存的好處是GC可以在不同級別上運行。
次要GC是僅關注年輕一代的集合,實際上完全忽略了Tenured空間。 通常,年輕代中的大多數對象都標記為刪除,并且不需要主或完全GC(包括舊代)來釋放堆上的內存。 當然,必要時會觸發“主要”或“完全” GC。
在此基礎上優化GC操作的一個快速技巧是調整堆區域的大小,以最適合您的應用程序的需求。
收集器類型
有許多可用的GC可供選擇,盡管G1成為Java 9中的默認GC ,但它最初旨在替代低暫停的CMS收集器,因此使用吞吐量收集器運行的應用程序可能更適合保留其當前收集器。 對于Java垃圾收集器,了解操作上的差異以及性能影響方面的差異仍然很重要。
吞吐量收集器
更適合需要針對高吞吐量進行優化的應用程序,并且可以交易更高的延遲來實現。
序列號–
串行收集器是最簡單的收集器,也是您最不可能使用的收集器,因為它主要用于單線程環境(例如32位或Windows)和小型堆。 該收集器可以垂直擴展JVM中的內存使用量,但需要幾個主要/完全GC才能釋放未使用的堆資源。 這導致頻繁的Stop the World暫停,從而使它在所有意圖和目的上都失去了在面向用戶的環境中使用的資格。
平行 -
顧名思義,此GC使用并行運行的多個線程來掃描并壓縮堆。 盡管Parallel GC使用多個線程進行垃圾回收,但它仍在運行時暫停所有應用程序線程。 Parallel收集器最適合需要優化以實現最佳吞吐量并可以忍受更高延遲的應用程序。
低暫停時間收集器
大多數面向用戶的應用程序需要低暫停GC,因此長時間或頻繁的暫停不會影響用戶體驗。 這些GC都是為了優化響應能力(時間/事件)和強大的短期性能。
并發標記掃描(CMS)–
與并行收集器相似,并發標記掃描(CMS)收集器利用多個線程來標記和清除(刪除)未引用的對象。 但是,此GC僅在以下兩個特定實例中啟動“停止世界”事件:
(1)初始化根的初始標記時(舊代對象可以從線程入口點或靜態變量訪問)或main()方法的任何引用,以及更多
(2)當應用程序在算法同時運行時更改了堆的狀態時,迫使它返回并進行最后的修改以確保它標記了正確的對象
G1 –
垃圾第一收集器(通常稱為G1)利用多個后臺線程掃描通過堆將其劃分為多個區域。 它的工作方式是先掃描那些包含最多垃圾對象的區域,然后為其命名(垃圾優先)。
這種策略減少了在后臺線程完成對未使用對象的掃描之前耗盡堆的機會,在這種情況下,收集器將不得不停止應用程序。 G1收集器的另一個優點是它可以在移動過程中壓縮堆,這是CMS收集器僅在完全Stop the World收集期間執行的操作。
改善GC性能
垃圾收集的頻率和持續時間直接影響應用程序的性能,這意味著可以通過減少這些指標來優化GC流程。 有兩種主要方法可以做到這一點。 首先,通過調整年輕人和老年人的堆大小 ,其次,以減少對象分配和提升的速度 。
在調整堆大小方面,它并不像人們期望的那么簡單。 合理的結論是,增加堆大小將減少GC頻率,同時增加持續時間,而減少堆大小將減少GC持續時間,同時增加頻率。
不過,事實是,次要GC的持續時間不取決于堆的大小,而是取決于可以幸存的對象數量。 這意味著對于大多數創建壽命短的對象的應用程序,增加年輕代的大小實際上可以減少GC的持續時間和頻率。 但是,如果增加年輕代的大小會導致需要在幸存者空間中復制的對象顯著增加,則GC暫停將花費更長的時間,從而導致延遲增加。
編寫GC高效代碼的3個技巧
提示1:預測收集容量–
所有標準Java集合以及大多數自定義和擴展的實現(例如Trove和Google的Guava)都使用基礎數組(基于原始或對象的數組)。 由于數組一旦分配就大小不變,因此在許多情況下向集合中添加項目可能會導致丟棄舊的基礎數組,而使用較大的新分配的數組。
即使未提供預期的集合大小,大多數集合的實現也會嘗試優化此重新分配過程,并將其保持在攤銷后的最小值。 但是,通過在構造時為集合提供預期的大小可以達到最佳效果。
提示2:直接處理流–
例如,在處理數據流(例如從文件讀取的數據或通過網絡下載的數據)時,通常會看到以下內容:
byte[] fileData = readFileToByteArray(new File("myfile.txt"));然后可以將得到的字節數組解析為XML文檔,JSON對象或Protocol Buffer消息,以列舉一些常用的選項。
當處理大文件或大小無法預測的文件時,這顯然不是一個好主意,因為在JVM無法實際分配整個文件大小的緩沖區的情況下,這會使我們面臨OutOfMemoryErrors。
解決此問題的更好方法是使用適當的InputStream(在這種情況下為FileInputStream)并將其直接輸入解析器,而無需先將整個內容讀取到字節數組中。 所有主要的庫都公開API以直接解析流,例如:
FileInputStream fis = new FileInputStream(fileName); MyProtoBufMessage msg = MyProtoBufMessage.parseFrom(fis);提示3:使用不可變對象–
不變性有很多優點。 很少受到關注的一個問題是它對垃圾收集的影響。
不變對象是指其對象(在我們的情況下尤其是非原始字段)在構造對象后便無法修改的對象。
不變性意味著不變容器引用的所有對象都是在容器構造完成之前創建的。 用GC的術語來說:容器至少與所保存的最小引用一樣年輕。 這意味著在對年輕一代執行垃圾回收周期時,GC可以跳過位于老一代中的不可變對象,因為它可以確定它們不能引用正在收集的一代中的任何對象。
要掃描的對象越少,意味著要掃描的內存頁面越少,而要掃描的內存頁面就越少,意味著GC周期越短,這意味著GC暫停時間越短,總體吞吐量就越高。
有關更多技巧和詳細示例,請查看這篇文章,其中涵蓋了編寫更多內存有效代碼的深入策略。
***非常感謝OverOps研發團隊的Amit Hurvitz對本文的熱情和見解!
翻譯自: https://www.javacodegeeks.com/2018/08/improve-application-performance-gc.html
system.gc 性能
總結
以上是生活随笔為你收集整理的system.gc 性能_使用这些先进的GC技术提高应用程序性能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ASML 进驻北海道,为日本晶圆代工企业
- 下一篇: 过世模玩爱好者“千寺狐”登场《赛博朋克