Java最叼的地方就在于它的垃圾回收兔港,同時(shí)也是Java語言相比C++ 需要程序員手動(dòng)管理內(nèi)存的語言的最大優(yōu)勢(shì)乳绕,首先還是得先聊清楚JVM的內(nèi)存模型
JVM 內(nèi)存模型
![image.png](https://upload-images.jianshu.io/upload_images/2717496-d3429171c7e130e2.png&originHeight=716&originWidth=1148&size=193631&status=done&width=574?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
注意鲸阔,java8及之后的JVM內(nèi)存模型與之前的有了區(qū)別疯趟,比較大的改動(dòng)就是移除了方法區(qū)晦譬,原本的方法區(qū)是屬于堆內(nèi)內(nèi)存,而元空間則直接使用的物理內(nèi)存习勤,也就是說元數(shù)據(jù)區(qū)的大小不再依賴于堆內(nèi)存大小踪栋。主要原因還是目前的程序中普遍存在很多運(yùn)行時(shí)生成的class對(duì)象,導(dǎo)致原本的方法區(qū)已經(jīng)不夠用图毕,容易觸發(fā) 方法區(qū)gc
- 程序計(jì)數(shù)器
- 一塊較小的內(nèi)存區(qū)域夷都,存儲(chǔ)線程信息。主要是為了線程切換后能恢復(fù)到正確的執(zhí)行位置予颤,每個(gè)線程都有一個(gè)獨(dú)立的程序計(jì)數(shù)器囤官,互不干擾,唯一一個(gè)不會(huì)拋出 OOM異常的區(qū)域
- 虛擬機(jī)棧
- 與程序計(jì)數(shù)器類似蛤虐,也是線程私有党饮。虛擬機(jī)棧的內(nèi)存模型非常重要 ,理解java的線程模型務(wù)必要理解虛擬機(jī)棧的內(nèi)存模型驳庭。這里簡單介紹一下劫谅,虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型,每個(gè)方法在執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀(stack frame)用來存儲(chǔ) 局部變量表嚷掠,操作數(shù)棧捏检,動(dòng)態(tài)鏈接,方法出口等信息不皆。局部變量表用于存儲(chǔ) 基本數(shù)據(jù)類型贯城,對(duì)象引用(不絕對(duì),開啟逃逸分析的話霹娄,也會(huì)在棧上分配內(nèi)存)
- 會(huì)拋出兩種異常
- 超過虛擬機(jī)規(guī)定的最大棧深度能犯,拋出
StackOverflowError
OutOfMemryError stack
- 超過虛擬機(jī)規(guī)定的最大棧深度能犯,拋出
- 本地方法棧
- 與虛擬機(jī)棧類似,僅僅是執(zhí)行的是Native方法
- 堆
- 堆是java內(nèi)存空間最大的一塊犬耻,后面也會(huì)著重畫下重點(diǎn)踩晶,并且堆是線程不安全的,每個(gè)線程公用堆內(nèi)存枕磁,幾乎所有的對(duì)象都在堆上分配內(nèi)存渡蜻,但也不絕對(duì)(比如上面提到的棧上分配)。當(dāng)前主流的虛擬機(jī)基本都把堆劃分為兩個(gè)區(qū)域 1.新生代 2.老年代 其中新生代计济,又可分為
Eden茸苇,F(xiàn)rom Survivor ,To Survivor
垃圾回收也是主要在這快區(qū)域 - 會(huì)拋出
OutOfMemryError Heap space
- 堆是java內(nèi)存空間最大的一塊犬耻,后面也會(huì)著重畫下重點(diǎn)踩晶,并且堆是線程不安全的,每個(gè)線程公用堆內(nèi)存枕磁,幾乎所有的對(duì)象都在堆上分配內(nèi)存渡蜻,但也不絕對(duì)(比如上面提到的棧上分配)。當(dāng)前主流的虛擬機(jī)基本都把堆劃分為兩個(gè)區(qū)域 1.新生代 2.老年代 其中新生代计济,又可分為
- 元空間
- 與Java堆一樣沦寂,也是線程共享的區(qū)域学密。主要存儲(chǔ)虛擬機(jī)加載的類信息,在java7之前我們又把它叫做方法區(qū)传藏,當(dāng)時(shí)常量池也存在這里(現(xiàn)在已經(jīng)在堆內(nèi)存中)腻暮。垃圾收集在這個(gè)區(qū)域比較少出現(xiàn)
- 會(huì)拋出
OutOfMemryError PermGen space
對(duì)象的內(nèi)存結(jié)構(gòu)
這里主要介紹下對(duì)象頭信息<br />這里要理解的是彤守,對(duì)象頭是一個(gè)可變的長度,并且存的數(shù)據(jù)再每種狀態(tài)下都是不同的哭靖,下表詳列了每種狀態(tài)下對(duì)象頭所存儲(chǔ)的信息<br />
![image.png](https://upload-images.jianshu.io/upload_images/2717496-17d915420c21bbeb.png&originHeight=422&originWidth=1740&size=214926&status=done&width=870?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
這里跟java鎖升級(jí)優(yōu)化有關(guān)
垃圾回收
著重講一下垃圾回收<br />在講垃圾回收之前遗增,先說一下什么樣的對(duì)象會(huì)被垃圾回收?<br />目前主要有兩種方法判斷 對(duì)象是不是可被回收
- 引用計(jì)數(shù)
- 可達(dá)性分析
引用計(jì)數(shù)比較好理解款青,每有一個(gè)對(duì)象被持有引用加1,當(dāng)引用計(jì)數(shù)為0時(shí)霍狰,就通知垃圾收集器回收抡草。這樣子的算法比較簡單,但有循環(huán)引用的問題蔗坯,循環(huán)應(yīng)用的對(duì)象并不會(huì)被回收康震。實(shí)際上JVM也不是用的引用計(jì)數(shù)法
可達(dá)性分析理解起來相對(duì)抽象一下,但是目前主流的虛擬機(jī)都是用的可達(dá)性分析算法宾濒。算法的核心就是 挑選 GC Root 做可達(dá)性分析腿短,當(dāng)GC Root不可達(dá)該對(duì)象時(shí),說明該對(duì)象要被回收绘梦,所以該算發(fā)的核心就是選擇合適的GC Root 橘忱。可用作GC Root的對(duì)象主要有
- 虛擬機(jī)棧中引用的對(duì)象
- 方法區(qū)中靜態(tài)屬性引用的對(duì)象
- 常量引用的對(duì)象
- 本地方法棧應(yīng)用的對(duì)象
引用分為三種類型
- 強(qiáng)引用 不用多說
- 軟引用
- 系統(tǒng)將要發(fā)生內(nèi)存溢出之前會(huì)被回收
- 弱引用
- 只要垃圾回收器 工作了就會(huì)被回收
- 虛引用
- 沒什么用卸奉。钝诚。。
垃圾收集算法
標(biāo)記清除
顧名思義榄棵,就是標(biāo)記了再清除<br />缺點(diǎn):效率低 會(huì)產(chǎn)生內(nèi)存碎片
復(fù)制清除
為了解決效率問題凝颇,引出了復(fù)制算法。主要原理就是將內(nèi)存分為兩塊疹鳄,每次只使用一塊拧略,當(dāng)一塊用完了,就將還存活著的對(duì)象復(fù)制到另一塊內(nèi)存中瘪弓,然后將剩下的全部清除垫蛆。<br />這個(gè)算法優(yōu)點(diǎn)就是 不會(huì)產(chǎn)生內(nèi)存碎片,效率高腺怯,但是空間利用率不高(新生代用的就是復(fù)制算法月褥,不同的是新生代將內(nèi)存分為3塊 Eden,F(xiàn)rom Survivor 瓢喉,To Survivor)比例默認(rèn)是 8:1:1
標(biāo)記整理
標(biāo)記整理算法和標(biāo)記清除類型宁赤,不同的是 清除之后,會(huì)進(jìn)行內(nèi)存整理栓票,以減少內(nèi)存碎片
分代收集
分代收集嚴(yán)格來說并不是一種收集算法决左,僅僅只是一種思想愕够。它把內(nèi)存分成各個(gè)區(qū)域,每個(gè)區(qū)域都使用合適的算法去做垃圾回收佛猛,如新生代使用復(fù)制算法惑芭,老年代使用標(biāo)記整理<br /><br />
垃圾收集器
Serial 收集器
新生代單線程收集器(復(fù)制清除算法),收集的時(shí)候會(huì) stop the world继找,一般都不怎么用了遂跟,除了在一些 Java桌面應(yīng)用的CLient端還有用外
Parnew 收集器
新生代多線程收集器,serial 收集器的多線程版本婴渡,其他跟serial收集器完全一樣幻锁。清理的時(shí)候也會(huì) stop the world。一般用來搭配使用 CMS 收集器
Parallel Scavenge 收集器
和 Parnew 收集器非常相似边臼,都是新生代多線程收集器哄尔,但是Parallel Scavenge與其他收集器的關(guān)注點(diǎn)不一樣,它不關(guān)注 停頓時(shí)間點(diǎn)(stop the world)柠并,而關(guān)注吞吐量
Serial Old 收集器
serial 收集器的老年代版本岭接,單線程收集器,使用標(biāo)記整理算法臼予,不怎么用鸣戴。用來做CMS的后備
CMS
基于標(biāo)記清除粘拾。CMS 是目前公司使用的 老年代收集器葵擎,也是大多數(shù)Web應(yīng)用所采用的收集器。原因在于CMS收集器是以最短回收停頓時(shí)間為目前的收集器半哟,對(duì)于用戶體驗(yàn)來說會(huì)比較友好。<br />CMS 收集器包含四個(gè)步驟
- 初始標(biāo)記 (stop the world 但是時(shí)間很短)
- 并發(fā)標(biāo)記
- 重新標(biāo)記 stop the world
- 并發(fā)清除
缺點(diǎn)
- 對(duì)CPU敏感
- 無法處理浮動(dòng)垃圾几缭,重新標(biāo)記的時(shí)候也會(huì)產(chǎn)生垃圾
- 內(nèi)存碎片 標(biāo)記清除算法的通病
G1收集器
目前最屌的了
- 停頓時(shí)間力求最短
- 標(biāo)記整理
- 不需要配合其他收集器年栓,一個(gè)就可以搞定 新生代 和老年代
- 可預(yù)測(cè)的停頓 (某抓??這是)
回收策略
年輕代 MinorGC<br />老年代 MajorGC<br />Full Gc 會(huì)至少伴隨一次 MinorGC
- 大對(duì)象直接進(jìn)入老年代
- 長期存活對(duì)象進(jìn)入老年代 (默認(rèn)是15次 -XX:MaxTenuingThrehold = 15 設(shè)置)
-
空間分配擔(dān)保
- 在發(fā)生MinorGC前备禀,虛擬機(jī)會(huì)檢查老年代最大用連續(xù)空間是否大于新生代所有對(duì)象總空間洲拇,如果這個(gè)條件成立,則可以確保這次MinorGC是安全的曲尸。如果不成立虛擬機(jī)則會(huì)檢查 HandelPromotionFailure 設(shè)置值是否允許擔(dān)保失敗赋续。如果允許則繼續(xù)檢查老年代最大可用空間是否大于歷次新生代晉升到老年代對(duì)象的平均大小,如果大于队腐,則盡管有風(fēng)險(xiǎn)也會(huì)進(jìn)行一次MinorGC。如果小于或則不允許擔(dān)保失敗奏篙,則直接進(jìn)行 Full GC
<br />
虛擬機(jī)監(jiān)測(cè)工具
jps
- jps -v 輸出啟動(dòng)詳細(xì)信息
- jps -l 輸出主類全名
- jps -m 輸出main函數(shù)
- jps -q 輸出 進(jìn)程id
下面的命令都會(huì)用到 jps 查出的進(jìn)程id(vmid)
jstat
用于監(jiān)視虛擬機(jī)運(yùn)行狀態(tài)
- jstat -class vmid 監(jiān)測(cè)類裝載耗時(shí)
- jstat -gc vmid 監(jiān)測(cè)堆狀況 GC時(shí)間等
jinfo
查詢配置信息
jmap
生成內(nèi)存快照
- jmap -dump:format=b,file=heapdump.phrof vmid 導(dǎo)出dump文件柴淘,也可以加系統(tǒng)參數(shù)
-XX:+HeapDumpOnOutOfMemoryError
或則通過-XX:+HeapDumpOnCtrlBreak
在程序運(yùn)行時(shí)按Crtl+Break生成 dump文件
jhat
分析 dump文件的工具,不過有點(diǎn)難用
jstack
打印線程堆棧
- jstack vmid
一般分析堆棧異常時(shí)用到秘通,常用的命令組合是
1.找出占用CPU占用最高的線程id
top -bn1 -H -p <pid>
2.將找的線程id轉(zhuǎn)換成16進(jìn)制
printf "%x \n" <tid>
3.通過jstack定位問題代碼
jstack -l <pid> | grep -A10 <tid>
- visualvm
這個(gè)比較好用为严,也很強(qiáng)大
有些時(shí)候,這些工具真的用不到肺稀。但并不代表第股,你不需要去了解這些