如何有效管理JVM中的垃圾?
前言
都說(shuō)JVM是大牛們玩的技術(shù),其實(shí)未必,如果面試官和你你談到Java內(nèi)存管理,那么首先,我建議你首先要了解Java垃圾收集的工作原理。 因?yàn)榻?jīng)常在運(yùn)行JAVA應(yīng)用程序時(shí),大多數(shù)開(kāi)發(fā)者是使用JVM自動(dòng)幫你管理GC垃圾回收器(完全不關(guān)注,JVM自動(dòng)完成回收),碼農(nóng)們只關(guān)注業(yè)務(wù)代碼實(shí)現(xiàn),不需要關(guān)注JVM是怎么管理的,對(duì)大家而言,更多人只知道程序正在運(yùn)行中。但是老鐵們,當(dāng)你寫(xiě)的JAVA程序開(kāi)始面臨性能下降時(shí),碼農(nóng)與架構(gòu)師的區(qū)別就來(lái)了,所有的性能問(wèn)題其實(shí)歸根到底就是我們的GC回收效率變低了。
因此,讓我們首先了解什么是JVM GC模型,然后,我們可以看到如何控制它并分析GC日志以查找應(yīng)用程序中發(fā)生的任何差異。
一、什么是自動(dòng)垃圾收集?
自動(dòng)垃圾收集是指對(duì)堆內(nèi)存的查看,并識(shí)別哪些對(duì)象正在使用哪些對(duì)象,以及刪除未使用的對(duì)象的過(guò)程。
首先我們看一下自動(dòng)GC垃圾收集,它的步驟如下:
1.標(biāo)記(Marking)
該過(guò)程的第一步稱為標(biāo)記。其實(shí)就是垃圾收集器識(shí)別哪些內(nèi)存正在使用,哪些內(nèi)存不在使用的地方。
如果必須掃描系統(tǒng)中的所有對(duì)象,將是一個(gè)非常耗時(shí)的過(guò)程。
2.正常刪除(Normal Deletion)
正常刪除是指移除未引用的對(duì)象,留下引用的對(duì)象和指向空閑空間的指針。
3.壓縮刪除(Deletion with Compacting)
要進(jìn)一步提高性能,除了刪除未引用的對(duì)象外,還可以壓縮剩余的引用對(duì)象。
通過(guò)將引用的對(duì)象移動(dòng)到一起,這使得新的內(nèi)存分配更加容易和快速。
二、全自動(dòng)回收管理
當(dāng)正在進(jìn)行垃圾收集時(shí),如果你的應(yīng)用程序在該時(shí)間段內(nèi)沒(méi)有響應(yīng)時(shí),其實(shí)我們的期望是,GC應(yīng)該花費(fèi)最少的時(shí)間來(lái)回收它; 當(dāng)然, 如果花費(fèi)很多時(shí)間,則證明你的應(yīng)用GC設(shè)置是有問(wèn)題滴。
我們來(lái)看看下面的JVM內(nèi)存模型,它分為不同的部分。JVM堆內(nèi)存在物理上分為兩部分 - Young Generation(新生代)和Old Generation(老年代)。
1.首先,將所有新的對(duì)象都分配給伊Eden space(伊甸園)。兩個(gè)Survivor Space(幸存者區(qū))都是空的。
2.當(dāng)Eden space(伊甸園)填滿時(shí),會(huì)觸發(fā)一個(gè)小的垃圾收集。
3.引用的對(duì)象被移動(dòng)到第一個(gè)幸存者空間。清除Eden space(伊甸園)時(shí),將刪除未引用的對(duì)象。
4.下次要GC回收時(shí),Eden space(伊甸園)空間也會(huì)發(fā)生同樣的事情。刪除未引用的對(duì)象,并將引用的對(duì)象移動(dòng)到幸存者空間。但是,在這種情況下,它們被移動(dòng)到第二個(gè)幸存者空間(S1)。
5.在較小的GC之后,當(dāng)老化的對(duì)象達(dá)到一定的年齡閾值(在該示例中為8)時(shí),它們從新生代晉升到老年代。
最終,將對(duì)老一代進(jìn)行主要的GC回收,清理和壓縮該空間。
三、如何在Java中調(diào)整垃圾收集的優(yōu)化參數(shù)呢?
垃圾收集是指當(dāng)JVM不再需要對(duì)象時(shí),需要將它回收,釋放內(nèi)存。它包括查找不再使用的對(duì)象,釋放與這些對(duì)象關(guān)聯(lián)的內(nèi)存,并偶爾壓縮堆以防止內(nèi)存碎片。
垃圾收集器使用一個(gè)或多個(gè)線程來(lái)執(zhí)行回收工作。一般來(lái)說(shuō),為了完成跟蹤對(duì)象引用及在內(nèi)存中移動(dòng)對(duì)象的工作,它需要確保應(yīng)用程序線程當(dāng)前沒(méi)有使用這些對(duì)象,如果應(yīng)用程序線程正在使用對(duì)象,GC回收時(shí)會(huì)導(dǎo)致對(duì)象的內(nèi)存位置發(fā)生變化,可能發(fā)生不可預(yù)測(cè)的事情。這就是垃圾收集器在執(zhí)行某些任務(wù)時(shí)必須暫停所有應(yīng)用程序線程的原因。這些暫停有時(shí)被稱為Stop-The-World暫停(吊炸天,全世界都被停止,哈哈)。
3.1調(diào)整堆大小
垃圾收集調(diào)優(yōu)的第一步是**調(diào)整堆的大小。**這是因?yàn)槿绻烟。瑒t會(huì)發(fā)生太多的GC回收回收內(nèi)存次數(shù),這會(huì)降低整體應(yīng)用程序吞吐量。如果堆太大,那么GC回收次數(shù)會(huì)更少,但GC需要很長(zhǎng)的時(shí)間,那么你的系統(tǒng)響應(yīng)時(shí)間指標(biāo)會(huì)受到影響。并行收集器特別容易受到堆大小的影響,因此如果你需要大的堆并且暫停時(shí)間較短,那么你應(yīng)該嘗試使用G1GC收集器。
**備注:**自從Java 9和Shenandoah垃圾收集器被視為還處于“實(shí)驗(yàn)性”階段,不推薦使用并發(fā)標(biāo)記掃描(CMS)收集器。但如果你正在運(yùn)行在線交互式應(yīng)用程序,那么系統(tǒng)會(huì)默認(rèn)選擇G1GC收集器,如果你正在運(yùn)行脫機(jī)批處理應(yīng)用程序,那么并行收集器應(yīng)該是你的首選,這是我給大家的建議。
**堆的大小由兩個(gè)值控制:**使用ms標(biāo)志指定的初始值和使用mx標(biāo)志指定的最大值。
-Xms1g -Xmx8g
堆的初始大小和最大大小,可以由JVM根據(jù)工作負(fù)載自動(dòng)調(diào)整堆大小。如果JVM遇到內(nèi)存壓力并且觀察到GC執(zhí)行次數(shù)過(guò)多,它會(huì)不斷增加堆,直到內(nèi)存壓力消失為止,或直到堆達(dá)到其最大值為止。如果內(nèi)存壓力很低,JVM還可以通過(guò)縮小堆大小來(lái)決定減少暫停時(shí)間。這個(gè)過(guò)程稱為自適應(yīng)大小調(diào)整**,**它不僅可以調(diào)整堆的整體大小,還可以調(diào)整年輕代和老代的大小和比例。
當(dāng)然,如果你想調(diào)整GC行為和大小,**我建議你可以選擇關(guān)閉自適應(yīng)大小調(diào)整。**這可以節(jié)省JVM,這是計(jì)算堆大小所需的一小段時(shí)間。你可以通過(guò)將標(biāo)志設(shè)置UseAdaptiveSizePolicy為false 來(lái)執(zhí)行此操作。
-XX:-UseAdaptiveSizePolicy
此外,將初始堆大小設(shè)置為與最大堆大小相同的值,或?qū)⒊跏夹律笮≡O(shè)置為與最大新生代大小相同的值,這樣操作可以有效地關(guān)閉自適應(yīng)大小調(diào)整。
一般來(lái)說(shuō)堆大小的最大設(shè)置準(zhǔn)則就是**最大堆大小不應(yīng)超過(guò)計(jì)算機(jī)上的物理內(nèi)存量。**如果你運(yùn)行多個(gè)JVM,則最大堆大小的總和不應(yīng)超過(guò)計(jì)算機(jī)的物理內(nèi)存。
3.2調(diào)整GC性能
在G1GC中,調(diào)整參數(shù)MaxGCPauseMillis執(zhí)行以下所有優(yōu)化,以嘗試實(shí)現(xiàn)指定的暫停時(shí)間目標(biāo):
- 調(diào)整堆的大小
- 更快開(kāi)始后臺(tái)處理
- 調(diào)整要提升為舊一代的對(duì)象的期限閾值
- 調(diào)整混合GC循環(huán)期間處理的舊區(qū)域數(shù)
3.3修復(fù)并發(fā)模式失敗
**G1GC是一個(gè)并發(fā)收集器。**這意味著當(dāng)應(yīng)用程序線程仍在運(yùn)行時(shí),垃圾收集進(jìn)程的某些階段可以并發(fā)運(yùn)行。并且由于正在運(yùn)行的應(yīng)用程序可以繼續(xù)產(chǎn)生垃圾,我們可能會(huì)遇到應(yīng)用程序耗盡舊代內(nèi)存而垃圾收集器仍在垃圾收集過(guò)程中的情況。也就是說(shuō),正在運(yùn)行的應(yīng)用程序生成的垃圾比它清理的速度快。**這種情況稱為并發(fā)模式故障,**具體取決于故障發(fā)生的時(shí)間。如果您在GC日志中看到很多這些錯(cuò)誤; 解決方案是增加堆的大小,更早地啟動(dòng)G1后臺(tái)處理,或者通過(guò)使用更多后臺(tái)線程來(lái)加速GC處理。
要更頻繁地執(zhí)行G1后臺(tái)活動(dòng),您可以降低觸發(fā)G1循環(huán)的閾值。這是通過(guò)減少InitiatingHeapOccupancyPercent標(biāo)志的值來(lái)實(shí)現(xiàn)的。
-XX:InitiatingHeapOccupancyPercent=45
默認(rèn)情況下,此標(biāo)志設(shè)置為45。這意味著**當(dāng)堆填充45%時(shí)會(huì)觸發(fā)GC循環(huán)。**減少此值意味著GC會(huì)更早且更頻繁地觸發(fā)。但應(yīng)注意的是,該值不會(huì)設(shè)置為太低而導(dǎo)致GC過(guò)于頻繁發(fā)生的數(shù)字。
要增加后臺(tái)線程數(shù),請(qǐng)使用該ConcGCThreads標(biāo)志。
-XX:ConcGCThreads=4
此標(biāo)志的默認(rèn)值設(shè)置為ParallelGCThreads加2 的值除以4.只要計(jì)算機(jī)上有足夠的CPU可用,就可以增加此值而不會(huì)導(dǎo)致任何性能損失。
四、總結(jié)
如果調(diào)整堆大小并調(diào)整收集器對(duì)你不起作用,**那么你可以嘗試另一個(gè)收集器。如果你仍然沒(méi)有取得好成績(jī),那么你需要考慮調(diào)整應(yīng)用程序代碼本身的問(wèn)題了,好了,寫(xiě)了這么多,希望對(duì)大家有幫助。
到此這篇關(guān)于如何有效管理JVM中的垃圾?的文章就介紹到這了,更多相關(guān)JVM垃圾管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA連接到SQLserver的步驟方法以及遇到的問(wèn)題
java相對(duì)于其他語(yǔ)言(例如c,c++等)連接數(shù)據(jù)庫(kù)要方便得多,下面這篇文章主要給大家介紹了關(guān)于JAVA連接到SQLserver的步驟方法及遇到的問(wèn)題,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06淺談Maven安裝及環(huán)境配置出錯(cuò)的解決辦法
這篇文章主要介紹了淺談Maven安裝及環(huán)境配置出錯(cuò)的解決辦法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09兩個(gè)小例子輕松搞懂 java 中遞歸與尾遞歸的優(yōu)化操作
這篇文章主要介紹了兩個(gè)小例子輕松搞懂 java 中遞歸與尾遞歸的優(yōu)化操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09Java多線程局域網(wǎng)聊天室的實(shí)現(xiàn)
在學(xué)習(xí)了一個(gè)學(xué)期的java以后,搞了一個(gè)多線程的聊天室,熟悉了一下服務(wù)器和客戶機(jī)的操作。感興趣的小伙伴們可以參考一下2021-06-06spring?boot?使用?@Scheduled?注解和?TaskScheduler?接口實(shí)現(xiàn)定時(shí)任務(wù)
這篇文章主要介紹了spring?boot?使用?@Scheduled?注解和?TaskScheduler?接口實(shí)現(xiàn)定時(shí)任務(wù),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06關(guān)于Java兩個(gè)浮點(diǎn)型數(shù)字加減乘除的問(wèn)題
由于浮點(diǎn)數(shù)在計(jì)算機(jī)中是以二進(jìn)制表示的,直接進(jìn)行加減乘除運(yùn)算會(huì)出現(xiàn)精度誤差,想要得到精確結(jié)果,應(yīng)使用BigDecimal類進(jìn)行運(yùn)算2024-10-10