為什么要“除夕”,原來是內(nèi)存爆了!
傳說古代有一只四角四足的怪獸:名叫夕。因冬天大雪導(dǎo)致夕沒東西吃,所以夕經(jīng)常到附近的村里找吃的,因其身體龐大、脾氣暴躁、兇猛異常,給村民帶來了很大的災(zāi)難。
后來有一位聰明的孩子,他叫做年,教給大家除掉“夕”的方法:用爆竹,輕則趕走它,重則傷它。每年臘月三十,夕都會來村里,村名就守著夜,放著鞭炮趕走夕。除夕由此而來。
”我們把“夕”想象成一個不斷吃機器內(nèi)存的 Java 程序,就稱它為 年獸吧。掌管 Java 虛擬機內(nèi)存的就是“年”,我們稱它為年哥吧。
年獸的地盤
年哥管理的地盤主要分為五大區(qū):堆、方法區(qū)、虛擬機棧、本地方法棧、程序計數(shù)器。如下圖所示。另外大家可以把圖中的線程想象成村民,而堆是作為村民共享使用的區(qū)域。
運行時數(shù)據(jù)區(qū)
堆又可以進行細分,分為新生代和老年代,新生代和老年代的比例是 1:2,而新生代又可以進行細分,分為伊甸園(Eden)區(qū)和兩個 Survivor, 其中 Eden 區(qū)大小和Survivor 區(qū)大小是 8:1。
如下圖所示,年獸和村民都是共享堆內(nèi)存這塊地盤的,管理員年哥是管理堆內(nèi)存的。其中的數(shù)字 1、8、20 分別代表占用內(nèi)存的份數(shù)。
共享堆區(qū)
年獸的胃口
年獸的胃口是村民的幾百倍,年獸假扮村民逃過了管理員年哥的檢查,年哥對于這種大胃王都是直接分配到老年代去的,因為大胃王需要連續(xù)的內(nèi)存給它吃,而新生代的碎片比較多不滿足條件。在 Java 的世界中,最典型的大胃王就是大對象:如很長的字符串,或者元素數(shù)量很龐大的數(shù)組。
如下圖所示,村民分配到新生代吃內(nèi)存,年獸被直接分配到老年代。
年獸被直接分配到老年代
大量年獸入侵
年獸嘗到甜頭后,就開始不斷地呼叫它的親戚朋友,大量年獸被分配到了老年代,直接導(dǎo)致老年代的內(nèi)存空間不足了,如下圖所示:
大量年獸入侵
代碼演示
我們用代碼來演示下年獸入侵:
- 創(chuàng)建了 3 個年獸,都占用 10 MB 內(nèi)存。
- public class SpringFestivalOOM {
- public static void main(String[] args) {
- // 年獸1/2/3,都占用 10 MB 內(nèi)存
- byte[] nianShou1 = new byte[10 * 1024 * 1024];
- byte[] nianShou2 = new byte[10 * 1024 * 1024];
- byte[] nianShou3 = new byte[10 * 1024 * 1024];
- }
- }
- 編譯這段程序。
- javac SpringFestivalOOM.java
- 執(zhí)行這段程序,同時設(shè)置堆內(nèi)存最大為 20 MB。
- java -Xms20M -Xmx20M SpringFestivalOOM
因為 3 個年獸占用的內(nèi)存 30 MB 大于堆的最大內(nèi)存 20 MB,所以拋出堆內(nèi)存溢出異常,如下圖所示:
堆內(nèi)存溢出異常
這個時候年哥和村民才發(fā)現(xiàn),原來有這么多年獸占了我們的地盤,趕快消滅它們!
打走年獸
村民們和年哥湊到一塊,討論了下該如何解決這個問題,究其原因就是年獸太多了,要減少他們呼朋喚友來吃內(nèi)存。
放到我們的 Java 世界中,就是減少大對象的頻繁創(chuàng)建。
我們程序員經(jīng)常出現(xiàn)本地寫完代碼后沒什么問題,到線上后就出問題,很可能的原因就是線上環(huán)境的數(shù)據(jù)量大,很容易出現(xiàn)大對象的頻繁創(chuàng)建,比如大型促銷活動時,短時間內(nèi)需要創(chuàng)建大量訂單數(shù)據(jù),而訂單數(shù)據(jù)又比較復(fù)雜,有很多字段,可能會占用大量的內(nèi)存空間,最終導(dǎo)致頻繁觸發(fā)垃圾回收,而垃圾回收時又會出現(xiàn) Stop the world 現(xiàn)象,應(yīng)用程序的性能就降下來了。
守歲
在除夕晚上,都會進行“守歲”,村民們齊聚一堂吃著年夜飯,一起等待除夕的鐘聲。等到天亮再拜訪親戚鄰居。
而守歲這個過程只能待在家里,不能做其他事情,所以可以看成是垃圾回收時,其他線程不能工作,也就是 Stop the world 的由來。
如下圖所示,除夕之前,村民可以去其他地方活動,除夕夜就只能待在家里守歲了,到了第二天早上就可以串門拜年了。
守歲
總結(jié)
本篇通過除夕的故事來講解 Java 中垃圾回收機制,因故事較為簡單,所以并沒有對垃圾回收算法進行深入講解,本篇只能算作垃圾回收的入門,希望能給大家?guī)硪欢▎l(fā)作用,對 JVM 很熟的同學(xué)就當(dāng)學(xué)習(xí)下除夕的來歷吧~
- 村民作為小對象使用堆區(qū)的新生代,年獸作為大對象直接使用堆區(qū)的老年代。
- 除夕當(dāng)晚,大量年獸入侵老年代,導(dǎo)致堆區(qū)內(nèi)存不足,觸發(fā)垃圾回收機制。
- 守歲就是待在家里守著過新年,而垃圾回收時,又會停止其他線程,也就是 Stop the world。
- 避免代碼中頻繁復(fù)制或創(chuàng)建大對象是必須做的事情,以免上線后出現(xiàn)問題。
- 除夕也代表著辭舊迎新,這不正是執(zhí)行垃圾回收嗎?
本文轉(zhuǎn)載自微信公眾號「悟空聊架構(gòu)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系悟空聊架構(gòu)公眾號。