“
本文主要介紹 JVM和GC解析
如有需要,可以參考
如有幫助担猛,不忘 點(diǎn)贊 ?創(chuàng)作不易幕垦,白嫖無義!
一傅联、JVM內(nèi)存體系
其中方法區(qū)和堆被JVM中多個(gè)線程共享先改,比如類的靜態(tài)常量就被存放在方法區(qū),供類對(duì)象之間共享蒸走。
虛擬機(jī)棧仇奶、本地方法棧、程序計(jì)數(shù)器是每個(gè)線程獨(dú)立擁有的比驻,不會(huì)與其他線程共享该溯。
所以Java在通過new創(chuàng)建一個(gè)類對(duì)象實(shí)例的時(shí)候岛抄,一方面會(huì)在虛擬機(jī)棧中創(chuàng)建一個(gè)對(duì)該對(duì)象的引用,另一方面會(huì)在堆上創(chuàng)建類對(duì)象的實(shí)例狈茉,然后將對(duì)象引用指向該對(duì)象的實(shí)例夫椭。對(duì)象引用存放在每一個(gè)方法對(duì)應(yīng)的棧幀中。
- 虛擬機(jī)棧:虛擬機(jī)棧中執(zhí)行每個(gè)方法的時(shí)候氯庆,都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表蹭秋,操作數(shù)棧,動(dòng)態(tài)鏈接点晴,方法出口等信息感凤。
- 本地方法棧:與虛擬機(jī)棧發(fā)揮的作用相似,相比于虛擬機(jī)棧為Java方法服務(wù)粒督,本地方法棧為虛擬機(jī)使用的Native方法服務(wù)陪竿,執(zhí)行每個(gè)本地方法的時(shí)候,都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表屠橄,操作數(shù)棧族跛,動(dòng)態(tài)鏈接,方法出口等信息锐墙。
- 方法區(qū):它用于存儲(chǔ)已被虛擬機(jī)加載的類信息礁哄,常量,靜態(tài)變量溪北,即時(shí)編譯器編譯后的代碼等數(shù)據(jù)桐绒,方法區(qū)在JDK1.7版本及之前稱為永久代,從JDK1.8之后永久代被移除之拨。
- 堆:堆是Java對(duì)象的存儲(chǔ)區(qū)域茉继,任何new字段分配的Java對(duì)象實(shí)例和數(shù)組,都被分配在了堆上蚀乔,Java堆可使用 - Xms 和-Xmx 進(jìn)行內(nèi)存控制烁竭,從JDK1.7版本之后,運(yùn)行時(shí)常量池從方法區(qū)移到了堆上吉挣。
- 程序計(jì)數(shù)器:指示Java虛擬機(jī)下一條需要執(zhí)行的字節(jié)碼指令派撕。
二、JAVA8之后的JVM
從圖中我們可以看出JAVA8的JVM 用元空間取代了永久代
三睬魂、GC作用域
四终吼、常見垃圾回收算法
引用計(jì)數(shù)法:
JVM的實(shí)現(xiàn)一般不采用這種方式
缺點(diǎn):
- 每次對(duì)對(duì)象賦值時(shí)均要維護(hù)引用計(jì)數(shù)器,且計(jì)數(shù)器本身也有一定的消耗氯哮;
- 較難處理循環(huán)引用衔峰;
復(fù)制算法:
Java 而從GC的角度可以細(xì)分為:新生代(Eden區(qū)、From Survivor區(qū) 和 To Survivor區(qū))和 老年代。
特點(diǎn):
復(fù)制算法不會(huì)產(chǎn)生內(nèi)存碎片垫卤,但會(huì)占用空間。用于新生代出牧。
MinorGC的過程(復(fù)制 --> 清空 --> 互換):
-
復(fù)制: (Eden穴肘、SurvivorFrom 復(fù)制到 SurvivorTo,年齡加1)
首先舔痕,當(dāng)Eden區(qū)滿的時(shí)候會(huì)觸發(fā)第一次GC评抚,把還活著的對(duì)象拷貝到SurvivorFrom區(qū),當(dāng)Eden區(qū)再次觸發(fā)GC的時(shí)候會(huì)掃描Eden區(qū)域和From區(qū)域伯复,對(duì)這兩個(gè)區(qū)域進(jìn)行垃圾回收慨代,經(jīng)過這次回收后還存活的對(duì)象,則直接復(fù)制到To區(qū)域(如果有對(duì)象的年齡已經(jīng)到達(dá)了老年的標(biāo)準(zhǔn)啸如,則復(fù)制到老年代區(qū))侍匙,同時(shí)把這些對(duì)象的年齡加1。 -
清空:(清空Eden叮雳、SurvivorFrom)
清空Eden和SurvivorFrom中的對(duì)象想暗,也即復(fù)制之后有交換,誰空誰是to帘不。 -
互換:(SurvivorTo和SurvivorFrom 互換)
最后说莫,SurvivorTo和SurvivorFrom 互換,原SurvivorTo成為下一次GC是的SurvivorFrom區(qū)寞焙。
標(biāo)記清除法
算法分成標(biāo)記和清除兩個(gè)階段储狭,先標(biāo)記出要回收的對(duì)象,然后統(tǒng)一回收這些捣郊。
特點(diǎn):
不會(huì)占用額外空間辽狈,但會(huì)掃描兩次,耗時(shí)模她,容易產(chǎn)生碎片稻艰,用于老年代
標(biāo)記壓縮法
優(yōu)點(diǎn):
沒有內(nèi)存碎片,可以利用bump
缺點(diǎn):
需要移動(dòng)對(duì)象的成本侈净,用于老年代
原理:
標(biāo)記:與標(biāo)記清除一樣
壓縮:再次掃描尊勿,并往一段滑動(dòng)存活對(duì)象
五、判斷對(duì)象是否可回收
引用計(jì)數(shù)法
Java中畜侦,引用和對(duì)象是有關(guān)聯(lián)的元扔。如果要操作對(duì)象則必須用引用進(jìn)行。
因此旋膳,很顯然的一個(gè)方法就是通過引用計(jì)數(shù)來判斷一個(gè)對(duì)象是否可以回收澎语。簡單來說就是給對(duì)象添加一個(gè)引用計(jì)數(shù)器。每當(dāng)有一個(gè)地方引用它,計(jì)數(shù)器的值加1擅羞,每當(dāng)有一個(gè)引用失效時(shí)尸变,計(jì)數(shù)器的值減1。
任何時(shí)刻計(jì)數(shù)器值為0的對(duì)象就是不可能再被使用的减俏,那么這個(gè)對(duì)象就是可回收對(duì)象召烂。
缺點(diǎn):
很難解決對(duì)象之間相互循環(huán)引用的問題
枚舉根節(jié)點(diǎn)做可達(dá)性分析(根搜索路徑)
所謂GC roots或者說tracing GC 的 根集合 就是一組必須活躍的引用。
基本思路就是通過一系列名為GC Root 的對(duì)象作為起始點(diǎn)娃承,從這個(gè)被稱為GC Roots的對(duì)象開始向下搜索奏夫。
如GC Roots沒有任何引用鏈相連是,則說明此對(duì)象不可用历筝。也即給定一個(gè)集合的引用作為根出發(fā)酗昼,通過引用關(guān)系
哪些可以做GCRoots對(duì)象
- 虛擬機(jī)棧(棧幀中的局部變量區(qū),也叫做局部變量表)
- 方法區(qū)中的類靜態(tài)屬性引用的對(duì)象
- 方法區(qū)中常量引用的對(duì)象
- 本地方法棧中N(Native方法)引用的對(duì)象
六梳猪、JVM的參數(shù)類型
1)標(biāo)配參數(shù)
- java -version
- java -help
2)X參數(shù)
- java -Xint -version :解釋執(zhí)行
- java -Xcomp -version :第一次使用就編譯成本地代碼
- java -Xmixed :混合模式
3)XX參數(shù)
- Boolean類型
-XX:+ 或者 - 某個(gè)屬性值(+:表示開啟麻削,-:表示關(guān)閉)
例子:
-XX: +PrintGCDetails: 開啟打印GC收集細(xì)節(jié)
-XX: -PrintGCDetails: 關(guān)閉打印GC收集細(xì)節(jié)
-XX: +UseSerialGC: 開啟串行垃圾收集器
-XX: -UseSerialGC:關(guān)閉串行垃圾收集器
- KV設(shè)置類型
-XX: 屬性key = 屬性value
例子:
-XX: MetaspaceSize = 128m:設(shè)置元空間大小為128m
-XX:MaxTenuringThreshold = 15:控制新生代需要經(jīng)歷多少次GC晉升到老年代中的最大閾值
- jinfo -查看當(dāng)前運(yùn)行程序的配置
公式:jinfo -flag 配置項(xiàng) 進(jìn)程編號(hào)
例子:
- 查看初始堆大小:
2.查看其他參數(shù)
3.查看使用哪種垃圾回收器
兩個(gè)經(jīng)典參數(shù)
- -Xms 等價(jià)于 -XX: InitialHeapSize
- -Xmx 等價(jià)于 -XX: MaxHeapSize
七舔示、查看JVM默認(rèn)值
- -XX:+PrintFlagsInitial: 查看默認(rèn)初始值
java -XX: +PrintFlagsInitial -version
java -XX: +PrintFlagsInitial
- -XX:+PrintFlagsFinal 查看修改更新
- java -XX:+PrintFlagsFinal
- java -XX:+PrintFlagsFinal -version
- java -XX:+PrintCommandedLineFlags
八碟婆、常用的配置參數(shù)
經(jīng)典案例設(shè)置:
-Xms128m -Xmx4096m -Xss1024k -XX:Metaspacesize=512m -XX:+PrintCommandLineFlags -XX:PrintGCDetails -XX:UseSerialGC
- -Xms
初始化大小內(nèi)存,默認(rèn)為物理內(nèi)存1/64
等價(jià)于 -XX:InitialHeapSize
- -Xmx
最大分配內(nèi)存惕稻,默認(rèn)為物理內(nèi)存1/4
等價(jià)于 -XX:MaxHeapSize
- -Xss
設(shè)置單個(gè)線程的大小竖共,一般默認(rèn)為5112K~1024K
等價(jià)于 -XX:ThreadStackSize
- -Xmn
設(shè)置年輕代大小
- -XX:MetaspaceSize
設(shè)置元空間大小
元空間的本質(zhì)和永久代類似,都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)俺祠,不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中公给,而是使用本地內(nèi)存。因此蜘渣,默認(rèn)情況下淌铐,元空間的大小僅受本地內(nèi)存限制。
- -XX:+PrintGCdetails
輸出詳細(xì)的GC收集日志信息
- -XX:SurvivorRatio
設(shè)置新生代中eden和S0/S1空間的比例
默認(rèn):
-XX:SurvivorRatio=8 --> Eden:S0:S1=8:1:1
修改:
-XX:SurvivorRatio=4 --> Eden:S0:S1=4:1:1
SurvivorRatio值就是設(shè)置eden區(qū)的比例占多少蔫缸,S0/S1相同
- -XX:NewRatio
設(shè)置年輕代與老年代在堆結(jié)構(gòu)的占比
默認(rèn):
-XX:NewRatio=2: 新生代占1腿准,老年代占2,年輕代占整個(gè)堆的1/3
修改:
-XX:NewRatio=4: 新生代占1拾碌,老年代占4吐葱,年輕代占整個(gè)堆的1/5
NewRatio值就是設(shè)置老年代的占比,剩下的1給新生代
- -XX:MaxTenuringThreshold
設(shè)置垃圾最大年齡
-XX:MaxTenuringThreshold=0:設(shè)置垃圾最大年齡校翔。
如果設(shè)置為0的話弟跑,則年輕代對(duì)象不經(jīng)過Survivor區(qū),直接進(jìn)入老年代防症。對(duì)于老年代比較多的應(yīng)用孟辑,可以提高效率哎甲。如果將此值設(shè)置為一個(gè)較大值,則年輕代對(duì)象會(huì)在Survivor區(qū)進(jìn)行多次復(fù)制饲嗽,這樣可以增加對(duì)象在年輕代的存活時(shí)間炭玫,增加年輕代被回收的概論。
九貌虾、強(qiáng)軟弱虛
1)強(qiáng)引用
- 當(dāng)內(nèi)存不足础嫡,JVM開始垃圾回收,對(duì)于強(qiáng)引用的對(duì)象酝惧,就算出現(xiàn)了OOM也不會(huì)對(duì)該對(duì)象進(jìn)行回收,
死都不收
- 強(qiáng)引用是我們最常見的普通對(duì)象引用伯诬,只要還有強(qiáng)引用指向一個(gè)對(duì)象晚唇,就能表明對(duì)象還活著,垃圾收集器不會(huì)碰這種對(duì)象盗似。在Java中最常見的就是強(qiáng)引用哩陕,把一個(gè)對(duì)象賦給一個(gè)引用變量,這個(gè)引用變量就是一個(gè)強(qiáng)引用赫舒。當(dāng)一個(gè)對(duì)象被強(qiáng)引用變量引用時(shí)悍及,它處于可達(dá)狀態(tài),它是不可能被垃圾回收機(jī)制回收的接癌。即使該對(duì)象以后永遠(yuǎn)都不會(huì)被用到心赶,JVM也不會(huì)回收。 因此強(qiáng)引用是造成Java內(nèi)存泄漏的主要原因之一缺猛。
- 對(duì)于一個(gè)普通的對(duì)象缨叫,如果沒有其他的引用關(guān)系,只要超過了引用的作用域或者顯式地將相應(yīng)(強(qiáng))引用賦值為null荔燎,一般就是認(rèn)為可以被垃圾收集(具體看垃圾收集策略)
public static void main(String[] args) {
Object o1 = new Object(); //默認(rèn)為強(qiáng)引用
Object o2 = o1; //引用賦值
o1 = null; //置空 讓垃圾收集
System.gc();
System.out.println(o1); // null
System.out.println(o2); // java.lang.Object@1540e19d
}
復(fù)制代碼
2)軟引用
- 軟引用就是一種相對(duì)強(qiáng)引用弱化了一些的引用耻姥。需要用java.lang.ref.SoftReference類來實(shí)現(xiàn),可以讓對(duì)象豁免一些垃圾收集有咨。
- 系統(tǒng)內(nèi)存充足 -> 不會(huì)回收
- 系統(tǒng)內(nèi)存不足 -> 會(huì)回收
- 軟引用通常用在對(duì)內(nèi)存敏感的程序中琐簇,比如高速緩存就有用到軟引用,內(nèi)存夠用的時(shí)候就保留座享,不夠用就回收
public static void main(String[] args) {
Object o1 = new Object();
SoftReference softReference = new SoftReference(o1);
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(softReference.get());
}
復(fù)制代碼
3)弱引用
- 弱引用需要用java.lang.ref.WeakReference類來實(shí)現(xiàn)婉商,它比軟引用的生存期更短
- 對(duì)于弱引用的對(duì)象,只要垃圾回收機(jī)制一運(yùn)行征讲,不管JVM的內(nèi)存空間是否足夠据某,都會(huì)回收該對(duì)象占用的內(nèi)存。
public static void main(String[] args) {
Object o1 = new Object();
WeakReference weakReference = new WeakReference(o1);
o1 = null;
System.gc();
System.out.println(o1); //null
System.out.println(weakReference.get()); //null
}
復(fù)制代碼
5)虛引用
- 虛引用需要java.lang.ref.PhantomReference類來實(shí)現(xiàn)诗箍。
- 形如虛設(shè)癣籽,它不會(huì)決定對(duì)象的生命周期挽唉。
- 如果一個(gè)對(duì)象持有虛引用,那么它就和沒有任何一樣筷狼,在任何時(shí)候都可能被垃圾回收器回收瓶籽,它不能單獨(dú)使用也不能通過它來訪問對(duì)象,虛引用必須和引用隊(duì)列(ReferenceQueue)聯(lián)合使用埂材。
- 虛引用的主要作用是跟蹤對(duì)象被垃圾回收的狀態(tài)塑顺,僅僅是提供了一種確保對(duì)象被finalize以后,做某些事情的機(jī)制俏险。PhantomReference的get()方法總是返回null严拒,因此無法訪問對(duì)應(yīng)的引用對(duì)象。其意義在于說明一個(gè)對(duì)象已經(jīng)進(jìn)入finalization階段竖独,可以被gc回收裤唠,用來實(shí)現(xiàn)比finalization機(jī)制更靈活的回收操作。
public static void main(String[] args) {
Object o1 = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(o1,referenceQueue);
System.out.println(o1); //java.lang.Object@1540e19d
System.out.println(phantomReference.get()); //null
System.out.println(referenceQueue.poll()); //null
}
復(fù)制代碼
擴(kuò)展:軟弱引用適用場(chǎng)景
假如有一個(gè)引用需要讀取大量的本地圖片
存在問題:
- 如果每次讀取圖片都從硬盤讀取則會(huì)嚴(yán)重影響性能莹痢。
- 如果一次性全部加載到內(nèi)存中有可能造成內(nèi)存溢出种蘸。
解決思路:
用一個(gè)HashMap來保存圖片的路徑和相應(yīng)圖片對(duì)象關(guān)聯(lián)的軟引用之間的映射關(guān)系,在內(nèi)存不足時(shí)竞膳,JVM會(huì)自動(dòng)回收這些緩存圖片對(duì)象所占用的空間航瞭,從而有效地避免了OOM的問題。
Map<String,SoftReference> imgMap = new HashMap<String,SoftReference>()
WeakHashMap:
public static void main(String[] args) {
WeakHashMap<Integer,String> weakHashMap = new WeakHashMap<>();
Integer key = new Integer(1);
weakHashMap.put(key,"測(cè)試1");
System.out.println(weakHashMap); //{1=測(cè)試1}
key=null;
System.out.println(weakHashMap); //{1=測(cè)試1}
System.gc();
System.out.println(weakHashMap+"\t"+weakHashMap.size()); //{} 0
}
復(fù)制代碼
看完不贊坦辟,都是壞蛋