如何分析Java性能優(yōu)化中的垃圾回收機(jī)制-創(chuàng)新互聯(lián)

這篇文章將為大家詳細(xì)講解有關(guān)如何分析Java性能優(yōu)化中的垃圾回收機(jī)制,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

成都創(chuàng)新互聯(lián)公司提供高防服務(wù)器租用、云服務(wù)器、香港服務(wù)器、資陽托管服務(wù)器

★JVM 的內(nèi)存空間

  在 Java 虛擬機(jī)規(guī)范中,提及了如下幾種類型的內(nèi)存空間:

◇棧內(nèi)存(Stack):每個線程私有的。
◇堆內(nèi)存(Heap):所有線程公用的。
◇方法區(qū)(Method Area):有點(diǎn)像以前常說的“進(jìn)程代碼段”,這里面存放了每個加載類的反射信息、類函數(shù)的代碼、編譯時常量等信息。
◇原生方法棧(Native Method Stack):主要用于 JNI 中的原生代碼,平時很少涉及。

★垃圾回收機(jī)制簡介

  其實(shí) Java 虛擬機(jī)規(guī)范中并未規(guī)定垃圾回收的相關(guān)細(xì)節(jié)。垃圾回收具體該怎么搞,完全取決于各個 JVM 的設(shè)計者。所以,不同的 JVM 之間,GC 的行為可能會有一定的差異。下面咱拿 SUN 官方的 JVM 來簡單介紹一下 GC 的機(jī)制。

◇啥時候進(jìn)行垃圾回收?

  一般情況下,當(dāng) JVM 發(fā)現(xiàn)堆內(nèi)存比較緊張、不太夠用時,它就會著手進(jìn)行垃圾回收工作。但是大伙兒要認(rèn)清這樣一個殘酷的事實(shí):JVM 進(jìn)行 GC 的時間點(diǎn)是無法準(zhǔn)確預(yù)知的。因為 GC 啟動的時刻會受到各種運(yùn)行環(huán)境因素的影響,隨機(jī)性太大。
  雖說咱們無法準(zhǔn)確預(yù)知,但如果你想知道每次垃圾回收執(zhí)行的情況,還是蠻方便的??梢酝ㄟ^ JVM 的命令行參數(shù)“-XX:+PrintGC”把相關(guān)信息打印出來。
  另外,調(diào)用 System.gc() 只是建議 JVM 進(jìn)行 GC。至于 JVM 到底會不會真的去做,只有天曉得。所以,通常不建議自己手動調(diào)用 System.gc(),還是讓 JVM 自行決定比較好。另外,使用 JVM 命令行參數(shù)“-XX:+DisableExplicitGC”可以讓 System.gc() 不起作用。

◇誰來負(fù)責(zé)垃圾回收?

  一般情況下,JVM 會有一個或多個專門的垃圾回收線程,由它們負(fù)責(zé)清理回收垃圾內(nèi)存。

◇如何發(fā)現(xiàn)垃圾對象?

  垃圾回收線程會從“根集(Root Set)”開始進(jìn)行對象引用的遍歷。所謂的“根集”,就是正在運(yùn)行的線程中,可以訪問的【引用變量】的集合(比如所有線程當(dāng)前函數(shù)的參數(shù)和局部變量、當(dāng)前類的成員變量等等)。垃圾回收線程先找出被根集直接引用的所有對象(不妨叫集合1),然后再找出被集合1直接引用的所有對象(不妨叫集合2),然后再找出被集合2直接引用的所有對象......如此循環(huán)往復(fù),直到把能遍歷到的對象都遍歷完。
  凡是從“根集”通過上述遍歷可以到達(dá)的對象,都稱為可達(dá)對象或有效對象;反之,則是不可達(dá)對象或失效對象(也就是垃圾)。

◇如何清理/回收垃圾?

  通過上述階段,就把垃圾對象都找出來。然后垃圾回收線程會進(jìn)行相應(yīng)的清理和回收工作,包括:把垃圾內(nèi)存重新變?yōu)榭捎脙?nèi)存、進(jìn)行內(nèi)存的整理以消除內(nèi)存碎片、等等。這個過程會涉及到若干算法,限于篇幅,咱就不深入聊了。

◇分代

  早期的 JVM 是不采用分代技術(shù)的,所有被 GC 管理的對象都存放在同一個堆里面。這么做的缺點(diǎn)比較明顯:每次進(jìn)行GC都要遍歷所有對象,開銷很大。其實(shí)大部分的對象生命周期都很短(短命對象),只有少數(shù)對象比較長壽;在這些短命對象中,又只有少數(shù)對象占用的內(nèi)存空間大;其它大量的短命對象都屬于小對象(很符合二八原理)。
  有鑒于此,從 JDK 1.2 之后,JVM 開始使用分代的垃圾回收(Generational Garbage Collection)。JVM 把 GC 相關(guān)的內(nèi)存分為“年老代”(Tenured)和“年輕代”(Nursery)、“持久代”(Permanent,對應(yīng)于 JVM 規(guī)范的“方法區(qū)”)?!敬蟛糠帧繉ο笤趧倓?chuàng)建時,都位于“年輕代”。如果某對象經(jīng)歷了幾輪 GC 還活著(大齡對象),就把它移到“年老代”。另外,如果某個對象在創(chuàng)建時比較大,可能就直接被丟到年老代。經(jīng)過這種策略,使得年輕代總是保存那些短命的小對象。在空間尺寸上,“年輕代”相對較小,而“年老代”相對較大。
  因為有了分代技術(shù),JVM 的 GC 也相應(yīng)分為兩種——主要收集(Major Collection)和次要收集(Minor Collection)。“主要收集”同時清理年老代和年輕代,因此開銷很大,不常進(jìn)行;“次要收集”僅僅清理年輕代,開銷很小,經(jīng)常進(jìn)行。

★GC對性能會有啥影響?

  剛才介紹了GC的大致原理,那GC對性能會造成哪些影響捏?主要有如下幾個方面:

◇造成當(dāng)前運(yùn)行線程的停頓

  早期的 GC 比較弱智。在它工作期間,所有其它的線程都被暫停(以免影響垃圾回收工作)。等到 GC 干完活,其它線程再繼續(xù)運(yùn)行。所以,早期 JDK 的 GC 一旦開始工作,整個程序就會陷入假死狀態(tài),失去各種響應(yīng)。
  經(jīng)過這些年的技術(shù)改進(jìn)(包括采用分代技術(shù)),從 JDK 1.4 開始,GC 已經(jīng)比較精明了。在它干活期間,只是偶爾暫停一下其它線程的運(yùn)行(從長時間假死變?yōu)闀簳r性休克)。

◇遍歷對象引用的開銷

  試想如果JVM中的對象很多,那遍歷完所有可達(dá)對象肯定是比較費(fèi)勁的工作,這個開銷可不小。

◇清理和回收垃圾的開銷

  遍歷完對象引用之后,對垃圾的清理和回收也有較大的開銷。這部分開銷可能包括復(fù)制內(nèi)存塊、更新對象引用等等。

★幾種收集器

◇兩個性能指標(biāo)

  因為今天聊的是性能的話題,必然會提到衡量 GC 性能的兩個重要指標(biāo):吞吐量(Throughput)和停頓時間(Pause Time)。吞吐量這個詞不是很直觀,解釋一下:就是 JVM【不用于】GC 的時間占總時間的比率。“吞吐量”是越大越好,“停頓時間”是越小越好。
  不同的應(yīng)用程序?qū)@兩個指標(biāo)的關(guān)注點(diǎn)不一樣(后面具體會說),也就是所謂的“眾口難調(diào)”。很多 JVM 廠商為了迎合“眾口”,不得不提供多種幾種垃圾收集器供使用者選擇。不同的收集器,采用的收集策略是不一樣的,下面具體介紹。

◇串行收集器(Serial Collector)

  使用命令行選項“-XX:+UseSerialGC”指定。
  這種收集器是最傳統(tǒng)的收集器。它使用單線程進(jìn)行垃圾回收,對于“單 CPU 機(jī)器”比較合適。另外,小型應(yīng)用或者對上述兩個指標(biāo)沒有特殊要求的,可以使用串行收集器。

◇并行收集器(Parallel Throughput Collector)

  顧名思義,這種收集器使用多個線程進(jìn)行垃圾回收以達(dá)到高吞吐量。垃圾回收線程的數(shù)量通過命令行選項“-XX:ParallelGCThreads=n”指定??梢栽O(shè)置該數(shù)值以便充分利用“多CPU 或 多核”。
  當(dāng)使用命令行選項“-XX:+UseParallelGC”時:它會針對年輕代使用多個垃圾回收線程,對年老代依然使用單個線程的串行方式。此選項最早在JDK 1.5引入。
  當(dāng)使用命令行選項“-XX:+UseParallelOldGC”時:它針對年輕代和年老代都使用多個垃圾回收線程的方式。不過此選項從 JDK 1.6 才開始引入。

◇并發(fā)收集器(Concurrent Low Pause Collector)

  使用命令行選項“-XX:+UseConcMarkSweepGC”指定。
  這種收集器優(yōu)先保證程序的響應(yīng)。它會盡量讓垃圾回收線程和應(yīng)用自身的線程同時運(yùn)行,從而降低停頓時間。此選項從JDK 1.4.1開始支持。

◇增量收集器(Incremental Collector)

  自從 JDK 1.4.2 以來,SUN 官方就停止維護(hù)該收集器了。所以俺就節(jié)省點(diǎn)口水,不多說了。

★如何降低GC的影響?

◇盡量減少堆內(nèi)存的使用

  由于 GC 是針對存儲在堆內(nèi)存的對象進(jìn)行的。咱們?nèi)绻诔绦蛑袦p少引用對象的分配(也就相應(yīng)降低堆內(nèi)存分配),那對于提高 GC 的性能是很有幫助滴。上次“ 字符串過濾實(shí)戰(zhàn) ”的帖子給出了一個例子,示范了如何通過降低堆內(nèi)存的分配次數(shù)來提升性能。

◇設(shè)置合適的堆內(nèi)存大小

  JVM 的堆內(nèi)存是有講究的,不能太大也不能太小。如果堆內(nèi)存太小,JVM 老是感覺內(nèi)存不夠用,可能會導(dǎo)致頻繁進(jìn)行垃圾回收,影響了性能;如果堆內(nèi)存太大,以至于操作系統(tǒng)的大部分物理內(nèi)存都被 JVM 自個兒霸占了,那可能會影響其它應(yīng)用程序甚至操作系統(tǒng)本身的性能。
  另外,年輕代的大?。ɑ蛘哒f“年輕代”與“年老代”的比值)對于 GC 的性能也有明顯影響。如果年輕代太小,可能導(dǎo)致次要收集很頻繁;如果年輕代太大,導(dǎo)致次要收集的停頓很明顯。
  JVM 提供了若干和堆內(nèi)存大小相關(guān)的命令行選項,具體如下:
------------------------------
-Xms  設(shè)置初始堆內(nèi)存
-Xmx  設(shè)置大堆內(nèi)存
-Xmn  設(shè)置年輕代的大小
-XX:NewRatio=n  設(shè)置年輕代與年老代的比例為“n”
-XX:NewSize=n  設(shè)置年輕代大小為“n”
------------------------------
  一般情況下,JVM 的默認(rèn)參數(shù)值已經(jīng)夠用。所以沒事兒別輕易動用上述選項。如果你非調(diào)整不可,一定要做深入的性能對比測試,保證調(diào)整后的性能確實(shí)優(yōu)于默認(rèn)參數(shù)值。

◇吞吐量和停頓的取舍

  前面提到了不同應(yīng)用的眾口難調(diào)。常見的口味有兩種:(1)看重吞吐量,對停頓時間無所謂;(2)側(cè)重于停頓時間。
  對于某些在后臺的、單純運(yùn)算密集型的應(yīng)用,屬于第一種。比如某些科學(xué)計算的應(yīng)用。這時候建議使用并行收集器。
  對于涉及用戶 UI 交互的、實(shí)時性要求比較高、程序需要快速響應(yīng)的,屬于第二種。比如某些桌面游戲、某些電信交換系統(tǒng)。這時候建議使用并發(fā)收集器。

關(guān)于如何分析Java性能優(yōu)化中的垃圾回收機(jī)制就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

網(wǎng)站名稱:如何分析Java性能優(yōu)化中的垃圾回收機(jī)制-創(chuàng)新互聯(lián)
文章源于:http://bm7419.com/article42/geshc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站改版網(wǎng)頁設(shè)計公司、用戶體驗、品牌網(wǎng)站設(shè)計、電子商務(wù)網(wǎng)站策劃

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

綿陽服務(wù)器托管