(JavaSE高級)六霞赫、JVM 基礎(chǔ)知識

1. 既然有 GC 機(jī)制县恕,為什么還會有內(nèi)存泄露的情況

理論上 Java 因?yàn)橛欣厥諜C(jī)制(GC)不會存在內(nèi)存泄露問題(這也是 Java 被廣泛使用于服務(wù)器端編程的一個(gè)重要原因)构眯。然而在實(shí)際開發(fā)中趁舀,可能會存在無用但可達(dá)的對象,這些對象不能被 GC 回收案疲,因此也會導(dǎo)致內(nèi)存泄露的發(fā)生封恰。
例如 hibernate 的 Session(一級緩存)中的對象屬于持久態(tài),垃圾回收器是不會回收這些對象的褐啡,然而這些對象中可能存在無用的垃圾對象诺舔,如果不及時(shí)關(guān)閉(close)或清空(flush)一級緩存就可能導(dǎo)致內(nèi)存泄露。
下面例子中的代碼也會導(dǎo)致內(nèi)存泄露备畦。

1. import java.util.Arrays;
2. import java.util.EmptyStackException;
3. public class MyStack<T> {
4. private T[] elements;
5. private int size = 0;
6. private static final int INIT_CAPACITY = 16;
7. public MyStack() {
8. elements = (T[]) new Object[INIT_CAPACITY];
9. }
10. public void push(T elem) {
11. ensureCapacity();
12. elements[size++] = elem;
13. }
14. public T pop() {
15. if(size == 0)throw new EmptyStackException();
16. return elements[--size];
17. }
18. private void ensureCapacity() {
19. if(elements.length == size) {
20. elements = Arrays.copyOf(elements, 2 * size + 1);
21. }
22. }
23. }

上面的代碼實(shí)現(xiàn)了一個(gè)棧(先進(jìn)后出(FILO))結(jié)構(gòu)低飒,乍看之下似乎沒有什么明顯的問題,它甚至可以通過你編寫的各種單元測試懂盐。然而其中的 pop 方法卻存在內(nèi)存泄露的問題褥赊,當(dāng)我們用 pop 方法彈出棧中的對象時(shí),該對象不會被當(dāng)作垃圾回收允粤,即使使用棧的程序不再引用這些對象崭倘,因?yàn)闂?nèi)部維護(hù)著對這些對象的過期引用(obsoletereference)。在支持垃圾回收的語言中类垫,內(nèi)存泄露是很隱蔽的司光,這種內(nèi)存泄露其實(shí)就是無意識的對象保持。如果一個(gè)對象引用被無意識的保留起來了悉患,那么垃圾回收器不會處理這個(gè)對象残家,也不會處理該對象引用的其他對象,即使這樣的對象只有少數(shù)幾個(gè)售躁,也可能會導(dǎo)致很多的對象被排除在垃圾回收之外坞淮,從而對性能造成重大影響,極端情況下會引發(fā) Disk Paging (物理內(nèi)存與硬盤的虛擬內(nèi)存交換數(shù)據(jù))陪捷,甚至造成 OutOfMemoryError回窘。

2. Java 中為什么會有 GC 機(jī)制呢?

Java 中為什么會有 GC 機(jī)制呢市袖?
? 安全性考慮啡直;-- for security.
? 減少內(nèi)存泄露;-- erase memory leak in some degree.
? 減少程序員工作量。-- Programmers don't worry about memory releasing.

3. 對于 Java 的 GC 哪些內(nèi)存需要回收

內(nèi)存運(yùn)行時(shí) JVM 會有一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)來管理內(nèi)存酒觅。它主要包括 5 大部分:程序計(jì)數(shù)器(Program Counter
Register)撮执、虛擬機(jī)棧(VM Stack)续捂、本地方法棧(Native Method Stack)槐臀、方法區(qū)(Method Area)、堆(Heap).
而其中程序計(jì)數(shù)器鼎天、虛擬機(jī)棧颜凯、本地方法棧是每個(gè)線程私有的內(nèi)存空間谋币,隨線程而生,隨線程而亡装获。例如棧中每一個(gè)棧幀中分配多少內(nèi)存基本上在類結(jié)構(gòu)確定是哪個(gè)時(shí)就已知了瑞信,因此這 3 個(gè)區(qū)域的內(nèi)存分配和回收都是確定的,無需考慮內(nèi)存回收的問題穴豫。
但方法區(qū)和堆就不同了,一個(gè)接口的多個(gè)實(shí)現(xiàn)類需要的內(nèi)存可能不一樣逼友,我們只有在程序運(yùn)行期間才會知道會創(chuàng)建哪些對象精肃,這部分內(nèi)存的分配和回收都是動(dòng)態(tài)的,GC 主要關(guān)注的是這部分內(nèi)存帜乞。
總而言之司抱,GC 主要進(jìn)行回收的內(nèi)存是 JVM 中的方法區(qū)和堆;

3. Java 的 GC 什么時(shí)候回收垃圾黎烈?

在面試中經(jīng)常會碰到這樣一個(gè)問題(事實(shí)上筆者也碰到過):如何判斷一個(gè)對象已經(jīng)死去习柠?
很容易想到的一個(gè)答案是:對一個(gè)對象添加引用計(jì)數(shù)器。每當(dāng)有地方引用它時(shí)照棋,計(jì)數(shù)器值加 1资溃;當(dāng)引用失效時(shí),計(jì)數(shù)器值減 1.而當(dāng)計(jì)數(shù)器的值為 0 時(shí)這個(gè)對象就不會再被使用烈炭,判斷為已死溶锭。是不是簡單又直觀。然而符隙,很遺憾趴捅。這種做法是錯(cuò)誤的!為什么是錯(cuò)的呢霹疫?事實(shí)上拱绑,用引用計(jì)數(shù)法確實(shí)在大部分情況下是一個(gè)不錯(cuò)的解決方案,而在實(shí)際的應(yīng)用中也有不少案例丽蝎,但它卻無法解決對象之間的循環(huán)引用問題猎拨。比如對象 A 中有一個(gè)字段指向了對象 B,而對象 B 中也有一個(gè)字段指向了對象 A,而事實(shí)上他們倆都不再使用迟几,但計(jì)數(shù)器的值永遠(yuǎn)都不可能為 0消请,也就不會被回收,然后就發(fā)生了內(nèi)存泄露类腮。

所以臊泰,正確的做法應(yīng)該是怎樣呢?
在 Java蚜枢,C#等語言中缸逃,比較主流的判定一個(gè)對象已死的方法是:可達(dá)性分析(Reachability Analysis).
所有生成的對象都是一個(gè)稱為"GC Roots"的根的子樹。從 GC Roots 開始向下搜索厂抽,搜索所經(jīng)過的路徑稱為引用鏈(Reference Chain)需频,當(dāng)一個(gè)對象到 GC Roots 沒有任何引用鏈可以到達(dá)時(shí),就稱這個(gè)對象是不可達(dá)的(不可引用的)筷凤,也就是可以被 GC 回收了昭殉。無論是引用計(jì)數(shù)器還是可達(dá)性分析,判定對象是否存活都與引用有關(guān)藐守!那么挪丢,如何定義對象的引用呢?
我們希望給出這樣一類描述:當(dāng)內(nèi)存空間還夠時(shí)卢厂,能夠保存在內(nèi)存中乾蓬;如果進(jìn)行了垃圾回收之后內(nèi)存空間仍舊非常緊張,則可以拋棄這些對象慎恒。所以根據(jù)不同的需求任内,給出如下四種引用,根據(jù)引用類型的不同融柬,GC 回收時(shí)也會有不同的操作:

  • 強(qiáng)引用(Strong Reference):Object obj = new Object();只要強(qiáng)引用還存在死嗦,GC 永遠(yuǎn)不會回收掉被引用的對象。
  • 軟引用(Soft Reference):描述一些還有用但非必需的對象丹鸿。在系統(tǒng)將會發(fā)生內(nèi)存溢出之前越走,會把這些對象列入回收范圍進(jìn)行二次回收(即系統(tǒng)將會發(fā)生內(nèi)存溢出了,才會對他們進(jìn)行回收靠欢。)
  • 弱引用(Weak Reference):程度比軟引用還要弱一些廊敌。這些對象只能生存到下次 GC 之前。當(dāng) GC 工作時(shí)门怪,無論內(nèi)存是否足夠都會將其回收(即只要進(jìn)行 GC骡澈,就會對他們進(jìn)行回收。)
  • 虛引用(Phantom Reference):一個(gè)對象是否存在虛引用掷空,完全不會對其生存時(shí)間構(gòu)成影響肋殴。

關(guān)于方法區(qū)中需要回收的是一些廢棄的常量和無用的類囤锉。
1.廢棄的常量的回收。這里看引用計(jì)數(shù)就可以了护锤。沒有對象引用該常量就可以放心的回收了官地。
2.無用的類的回收。什么是無用的類呢烙懦?
A.該類所有的實(shí)例都已經(jīng)被回收驱入。也就是 Java 堆中不存在該類的任何實(shí)例;
B.加載該類的 ClassLoader 已經(jīng)被回收氯析;
C.該類對應(yīng)的 java.lang.Class 對象沒有任何地方被引用亏较,無法在任何地方通過反射訪問該類的方法。

總而言之:

  • 對于堆中的對象掩缓,主要用可達(dá)性分析判斷一個(gè)對象是否還存在引用雪情,如果該對象沒有任何引用就應(yīng)該被回收。而根據(jù)我們實(shí)際對引用的不同需求你辣,又分成了 4 種引用巡通,每種引用的回收機(jī)制也是不同的。
  • 對于方法區(qū)中的常量和類舍哄,當(dāng)一個(gè)常量沒有任何對象引用它扁达,它就可以被回收了。而對于類蠢熄,如果可以判定它為無用類,就可以被回收了炉旷。

4.在開發(fā)中遇到過內(nèi)存溢出么签孔?原因有哪些?解決方法有哪些窘行?

引起內(nèi)存溢出的原因有很多種饥追,常見的有以下幾種:

  1. 內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù)罐盔;
  2. 集合類中有對對象的引用但绕,使用完后未清空,使得 JVM 不能回收惶看;
  3. 代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復(fù)的對象實(shí)體捏顺;
  4. 使用的第三方軟件中的 BUG;
  5. 啟動(dòng)參數(shù)內(nèi)存值設(shè)定的過形忱琛幅骄;

內(nèi)存溢出的解決方案:

  • 第一步,修改 JVM 啟動(dòng)參數(shù)本今,直接增加內(nèi)存拆座。(-Xms主巍,-Xmx 參數(shù)一定不要忘記加。)
  • 第二步挪凑,檢查錯(cuò)誤日志孕索,查看“OutOfMemory”錯(cuò)誤前是否有其它異常或錯(cuò)誤躏碳。
  • 第三步搞旭,對代碼進(jìn)行走查和分析,找出可能發(fā)生內(nèi)存溢出的位置唐断。
    重點(diǎn)排查以下幾點(diǎn):
  1. 檢查對數(shù)據(jù)庫查詢中选脊,是否有一次獲得全部數(shù)據(jù)的查詢。一般來說脸甘,如果一次取十萬條記錄到內(nèi)存恳啥,就可能引起內(nèi)存溢出。這個(gè)問題比較隱蔽丹诀,在上線前钝的,數(shù)據(jù)庫中數(shù)據(jù)較少,不容易出問題铆遭,上線后硝桩,數(shù)據(jù)庫中數(shù)據(jù)多了,一次查詢就有可能引起內(nèi)存溢出枚荣。因此對于數(shù)據(jù)庫查詢盡量采用分頁的方式查詢碗脊。
  2. 檢查代碼中是否有死循環(huán)或遞歸調(diào)用。
  3. 檢查是否有大循環(huán)重復(fù)產(chǎn)生新對象實(shí)體橄妆。
  4. 檢查 List衙伶、MAP 等集合對象是否有使用完后,未清除的問題害碾。List矢劲、MAP 等集合對象會始終存有對對象的
    引用,使得這些對象不能被 GC 回收慌随。
  • 第四步芬沉,使用內(nèi)存查看工具動(dòng)態(tài)查看內(nèi)存使用情況。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阁猜,一起剝皮案震驚了整個(gè)濱河市丸逸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蹦漠,老刑警劉巖椭员,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異笛园,居然都是意外死亡隘击,警方通過查閱死者的電腦和手機(jī)侍芝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來埋同,“玉大人州叠,你說我怎么就攤上這事⌒琢蓿” “怎么了咧栗?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虱肄。 經(jīng)常有香客問我致板,道長,這世上最難降的妖魔是什么咏窿? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任斟或,我火速辦了婚禮,結(jié)果婚禮上集嵌,老公的妹妹穿的比我還像新娘萝挤。我一直安慰自己,他們只是感情好根欧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布怜珍。 她就那樣靜靜地躺著,像睡著了一般凤粗。 火紅的嫁衣襯著肌膚如雪酥泛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天嫌拣,我揣著相機(jī)與錄音揭璃,去河邊找鬼。 笑死亭罪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的歼秽。 我是一名探鬼主播应役,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼燥筷!你這毒婦竟也來了箩祥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤肆氓,失蹤者是張志新(化名)和其女友劉穎袍祖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谢揪,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蕉陋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年捐凭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凳鬓。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茁肠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缩举,到底是詐尸還是另有隱情垦梆,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布仅孩,位于F島的核電站托猩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏辽慕。R本人自食惡果不足惜京腥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鼻百。 院中可真熱鬧绞旅,春花似錦、人聲如沸温艇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勺爱。三九已至晃琳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間琐鲁,已是汗流浹背卫旱。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留围段,地道東北人顾翼。 一個(gè)月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像奈泪,于是被迫代替她去往敵國和親适贸。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容