JVM

JVM運行時內(nèi)存劃分塌计?程序計數(shù)器(PC寄存器)+虛擬機棧+本地方法棧+堆+方法區(qū)+JDK1.7與1.8區(qū)別
JVM運行時內(nèi)存劃分韵丑?
  1. 程序計數(shù)器

    1. 字節(jié)碼解釋器通過改變程序計數(shù)器來依次讀取指令,從而實現(xiàn)代碼的流程控制导帝,如:順序執(zhí)行守谓、選擇、循環(huán)您单、異常處理斋荞。

    2. 在多線程的情況下,程序計數(shù)器用于記錄當(dāng)前線程執(zhí)行的位置虐秦,從而當(dāng)線程被切換回來的時候能夠知道該線程上次運行到哪兒了平酿。

    注意:程序計數(shù)器是唯一一個不會出現(xiàn) OutOfMemoryError 的內(nèi)存區(qū)域凤优,它的生命周期隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的結(jié)束而死亡蜈彼。

  2. 虛擬機棧

    Java 虛擬機棧也是線程私有的筑辨,它的生命周期和線程相同,描述的是 Java 方法執(zhí)行的內(nèi)存模型幸逆,每次方法調(diào)用的數(shù)據(jù)都是通過棧傳遞的棍辕。

    Java 內(nèi)存可以粗糙的區(qū)分為堆內(nèi)存(Heap)和棧內(nèi)存 (Stack),其中棧就是現(xiàn)在說的虛擬機棧,或者說是虛擬機棧中局部變量表部分还绘。 (實際上痢毒,Java 虛擬機棧是由一個個棧幀組成,而每個棧幀中都擁有:局部變量表蚕甥、操作數(shù)棧哪替、動態(tài)鏈接、方法出口信息菇怀。)

    局部變量表主要存放了編譯期可知的各種數(shù)據(jù)類型(boolean凭舶、byte、char爱沟、short帅霜、int、float呼伸、long身冀、double)、對象引用(reference 類型括享,它不同于對象本身搂根,可能是一個指向?qū)ο笃鹗嫉刂返囊弥羔槪部赡苁侵赶蛞粋€代表對象的句柄或其他與此對象相關(guān)的位置)铃辖。

    Java 虛擬機棧會出現(xiàn)兩種錯誤:StackOverFlowError 和 OutOfMemoryError

    • StackOverFlowError: 若 Java 虛擬機棧的內(nèi)存大小不允許動態(tài)擴展剩愧,那么當(dāng)線程請求棧的深度超過當(dāng)前 Java 虛擬機棧的最大深度的時候,就拋出 StackOverFlowError 錯誤娇斩。

    • OutOfMemoryError: 若 Java 虛擬機堆中沒有空閑內(nèi)存仁卷,并且垃圾回收器也無法提供更多內(nèi)存的話。就會拋出 OutOfMemoryError 錯誤犬第。

    Java 虛擬機棧也是線程私有的锦积,每個線程都有各自的 Java 虛擬機棧,而且隨著線程的創(chuàng)建而創(chuàng)建歉嗓,隨著線程的死亡而死亡丰介。

  3. 本地方法棧

    和虛擬機棧所發(fā)揮的作用非常相似,本地方法棧也是線程私有的,區(qū)別是: 虛擬機棧為虛擬機執(zhí)行 Java 方法 (也就是字節(jié)碼)服務(wù)基矮,而本地方法棧則為虛擬機使用到的 Native 方法服務(wù)。 在 HotSpot 虛擬機中和 Java 虛擬機棧合二為一冠场。

    本地方法被執(zhí)行的時候家浇,在本地方法棧也會創(chuàng)建一個棧幀,用于存放該本地方法的局部變量表碴裙、操作數(shù)棧钢悲、動態(tài)鏈接、出口信息舔株。

    方法執(zhí)行完畢后相應(yīng)的棧幀也會出棧并釋放內(nèi)存空間莺琳,也會出現(xiàn) StackOverFlowError 和 OutOfMemoryError 兩種錯誤。

  4. Java 虛擬機所管理的內(nèi)存中最大的一塊载慈,Java 堆是所有線程共享的一塊內(nèi)存區(qū)域惭等,在虛擬機啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實例办铡,幾乎所有的對象實例以及數(shù)組都在這里分配內(nèi)存辞做。

    Java 堆是垃圾收集器管理的主要區(qū)域,因此也被稱作GC 堆(Garbage Collected Heap).從垃圾回收的角度寡具,由于現(xiàn)在收集器基本都采用分代垃圾收集算法秤茅,所以 Java 堆還可以細分為:新生代和老年代。再細致一點有:Eden 空間童叠、From Survivor框喳、To Survivor 空間(新生代劃分)等。進一步劃分的目的是更好地回收內(nèi)存厦坛,或者更快地分配內(nèi)存五垮。(默認的新生代(Young generation)、老年代(Old generation)所占空間比例為 1 : 2 杜秸,默認新生代空間的分配:Eden : From : To = 8 : 1 : 1

    JDK 8 版本之后方法區(qū)(HotSpot 的永久代)被徹底移除了(JDK1.7 就已經(jīng)開始了)拼余,取而代之是元空間,元空間使用的是直接內(nèi)存亩歹。

    大部分情況匙监,對象都會首先在 Eden 區(qū)域分配,在一次新生代垃圾回收后小作,如果對象還存活亭姥,則會進入 s0 或者 s1,并且對象的年齡還會加 1(Eden 區(qū)->Survivor 區(qū)后對象的初始年齡變?yōu)?1)顾稀,當(dāng)它的年齡增加到一定程度(默認為 15 歲)达罗,就會被晉升到老年代中。對象晉升到老年代的年齡閾值,可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)置粮揉。

    堆這里最容易出現(xiàn)的就是 OutOfMemoryError 錯誤巡李,并且出現(xiàn)這種錯誤之后的表現(xiàn)形式還會有幾種,比如:

    1. OutOfMemoryError: GC Overhead Limit Exceeded : 當(dāng)JVM花太多時間執(zhí)行垃圾回收并且只能回收很少的堆空間時扶认,就會發(fā)生此錯誤侨拦。

    2. java.lang.OutOfMemoryError: Java heap space :假如在創(chuàng)建新的對象時, 堆內(nèi)存中的空間不足以存放新創(chuàng)建的對象, 就會引發(fā)java.lang.OutOfMemoryError: Java heap space 錯誤。(和本機物理內(nèi)存無關(guān)辐宾,和你配置的內(nèi)存大小有關(guān)狱从!)

    3. ......

  5. 方法區(qū)

    方法區(qū)與 Java 堆一樣,是各個線程共享的內(nèi)存區(qū)域叠纹,它用于存儲已被虛擬機加載的類信息季研、常量、靜態(tài)變量誉察、即時編譯器編譯后的代碼等數(shù)據(jù)与涡。雖然 Java 虛擬機規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是它卻有一個別名叫做 Non-Heap(非堆)持偏,目的應(yīng)該是與 Java 堆區(qū)分開來递沪。

    方法區(qū)也被稱為永久代。方法區(qū)和永久代的關(guān)系很像 Java 中接口和類的關(guān)系综液,方法區(qū)是標準款慨,而永久代是方法區(qū)的實現(xiàn)

    相對而言,垃圾收集行為在這個區(qū)域是比較少出現(xiàn)的谬莹,但并非數(shù)據(jù)進入方法區(qū)后就“永久存在”了檩奠。

    JDK 1.8 的時候,方法區(qū)(HotSpot 的永久代)被徹底移除了(JDK1.7 就已經(jīng)開始了)附帽,取而代之是元空間埠戳,元空間使用的是直接內(nèi)存。

堆內(nèi)存分配策略

內(nèi)存分配策略主要有以下幾點:

  • 對象優(yōu)先分配在Eden區(qū)蕉扮,如果Eden區(qū)沒有足夠的空間進行分配時整胃,虛擬機執(zhí)行一次MinorGC。

  • 大對象直接進入老年代(需要大量連續(xù)內(nèi)存空間的對象)喳钟。這樣做的目的是避免在Eden區(qū)和兩個Survivor區(qū)之間發(fā)生大量的內(nèi)存拷貝(新生代采用復(fù)制算法收集內(nèi)存)屁使。

  • 長期存活的對象進入老年代。虛擬機為每個對象定義了一個年齡(Age Count)計數(shù)器奔则,如果對象經(jīng)過了1次Minor GC那么對象會進入Survivor區(qū)蛮寂,之后每經(jīng)過一次Minor GC那么對象的年齡加1,直到達到閥值(默認15次)易茬,對象進入老年區(qū)酬蹋。

  • 動態(tài)判斷對象的年齡。如果Survivor區(qū)中相同年齡的所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象可以直接進入老年代范抓。

  • 空間分配擔(dān)保骄恶。每次進行Minor GC時,JVM會計算Survivor區(qū)移至老年區(qū)的對象的平均大小匕垫,如果這個值大于老年區(qū)的剩余值大小則進行一次Full GC僧鲁,如果小于檢查HandlePromotionFailure設(shè)置,如果true則只進行Monitor GC,如果false則進行Full GC年缎。

GC垃圾回收機制

分代垃圾回收

  • Minor GC:用于清理新生代(Eden)區(qū)域悔捶,Eden區(qū)滿了就會觸發(fā)一次Minor GC铃慷,清理無用對象单芜,將有用對象復(fù)制到"Survivor1","Survivor2"區(qū)中(這兩個區(qū)犁柜,大小空間相同洲鸠,同一時刻Survivor1和Survivor2只有一個在用一個為空)。

  • Major GC:用于清理老年代區(qū)域馋缅。

  • Full GC:用于清理新生代扒腕,老年代區(qū)域,成本較高萤悴,會對系統(tǒng)性能產(chǎn)生影響瘾腰。

Java 中的堆也是 GC 收集垃圾的主要區(qū)域。GC 主要分為兩種:Minor GC覆履、FullGC ( 或稱為 Major GC )蹋盆。

Minor GC

Minor GC 是發(fā)生在新生代中的垃圾收集動作,所采用的是復(fù)制算法硝全。

當(dāng)對象在 Eden ( 包括一個 Survivor 區(qū)域栖雾,這里假設(shè)是 from 區(qū)域 ) 出生后,在經(jīng)過一次 Minor GC 后伟众,如果對象還存活析藕,并且能夠被另外一塊 Survivor 區(qū)域所容納( 上面已經(jīng)假設(shè)為 from 區(qū)域,這里應(yīng)為 to 區(qū)域凳厢,即 to 區(qū)域有足夠的內(nèi)存空間來存儲 Eden 和 from 區(qū)域中存活的對象 )账胧,則使用復(fù)制算法將這些仍然還存活的對象復(fù)制到另外一塊 Survivor 區(qū)域 ( 即 to 區(qū)域 ) 中,然后清理所使用過的 Eden 以及 Survivor 區(qū)域 ( 即from 區(qū)域 )先紫,并且將這些對象的年齡設(shè)置為1找爱,以后對象在 Survivor 區(qū)每熬過一次 Minor GC,就將對象的年齡 + 1泡孩,當(dāng)對象的年齡達到某個值時 ( 默認是 15 歲车摄,可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)定),這些對象就會成為老年代。但這也不是一定的吮播,對于一些較大的對象 ( 即需要分配一塊較大的連續(xù)內(nèi)存空間 ) 則是直接進入到老年代变屁。

Full GC

Full GC 是發(fā)生在老年代的垃圾收集動作,所采用的是標記-清除算法標記-整理算法意狠。

現(xiàn)實的生活中粟关,老年代的人通常會比新生代的人"早死"。堆內(nèi)存中的老年代(Old)不同于這個环戈,老年代里面的對象闷板,幾乎個個都是在 Survivor 區(qū)域中熬過來的,它們是不會那么容易就 "死掉" 了的院塞。因此遮晚,Full GC 發(fā)生的次數(shù)不會有 Minor GC 那么頻繁,并且做一次 Full GC 要比進行一次 Minor GC 的時間更長拦止。

另外县遣,標記-清除算法收集垃圾的時候會產(chǎn)生許多的內(nèi)存碎片 ( 即不連續(xù)的內(nèi)存空間 ),此后需要為較大的對象

分配內(nèi)存空間時汹族,若無法找到足夠的連續(xù)的內(nèi)存空間萧求,就會提前觸發(fā)一次 GC 的收集動作。

Major GC

發(fā)生在永久代(方法區(qū))顶瞒,這個區(qū)域不是用于存儲那些從老年代存活下來的對象夸政,這個區(qū)域也可能發(fā)生GC。只不過在這個區(qū)域發(fā)生GC的條件非常嚴苛榴徐,必須符合以下三種條件才會被回收:

  1. 所有實例被回收

  2. 加載該類的ClassLoader 被回收

  3. Class 對象無法通過任何途徑訪問(包括反射)

對象如何從新生代進入老年代守问?

大對象直接進入老年代

大對象是指需要大量連續(xù)內(nèi)存空間的對象,例如很長的字符串以及數(shù)組箕速。 虛擬機設(shè)置了一個-XX:PretenureSizeThreshold參數(shù)酪碘,令大于這個設(shè)置的對象直接在老年代分配。目的就是為了防止大對象在Eden空間和Survivor空間來回大量復(fù)制盐茎。

長期存活的對象進入老年代

虛擬機給每個對象定義了一個對象年齡(Age)計數(shù)器兴垦,如果對象在Eden區(qū)出生并經(jīng)過第一次Mintor GC后仍然存活,并且能被Survivor接納字柠,并被移動到Survivor空間上探越,那么該對象年齡將被設(shè)置為1。對象在Survivor區(qū)中每熬過一次Minor GC,年齡就加一窑业,當(dāng)他的年齡增加到一定程度钦幔,就會被移動到老年代(年齡值默認為15)。對象晉升老年代的閾值可以通過-XX:MaxTenuringThreshold設(shè)置常柄。

動態(tài)年齡判斷并進入老年代

為了更好的適應(yīng)不同程序的內(nèi)存狀況鲤氢,虛擬機并不是永遠要求對象的年齡必須達到MaxTenuringThreshold才會晉升到老年代搀擂。如果在Survivor空間中相同年齡的所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代卷玉,無需達到MaxTensuringThreshold的要求年齡哨颂。

Full GC觸發(fā)條件
Minor GC觸發(fā)條件

當(dāng)Eden區(qū)滿時,觸發(fā)Minor GC相种。

Full GC觸發(fā)條件:

  1. System.gc()方法的調(diào)用

此方法的調(diào)用是建議JVM進行Full GC,雖然只是建議而非一定,但很多情況下它會觸發(fā) Full GC,從而增加Full GC的頻率,也即增加了間歇性停頓的次數(shù)威恼。強烈影響系建議能不使用此方法就別使用,讓虛擬機自己去管理它的內(nèi)存寝并,可通過通過-XX:+ DisableExplicitGC來禁止RMI(Java遠程方法調(diào)用)調(diào)用System.gc箫措。

  1. 老年代空間不足

舊生代空間只有在新生代對象轉(zhuǎn)入及創(chuàng)建為大對象、大數(shù)組時才會出現(xiàn)不足的現(xiàn)象衬潦,當(dāng)執(zhí)行Full GC后空間仍然不足斤蔓,則拋出如下錯誤: java.lang.OutOfMemoryError: Java heap space 為避免以上兩種狀況引起的FullGC,調(diào)優(yōu)時應(yīng)盡量做到讓對象在Minor GC階段被回收别渔、讓對象在新生代多存活一段時間及不要創(chuàng)建過大的對象及數(shù)組附迷。

  1. 方法區(qū)空間不足

JVM規(guī)范中運行時數(shù)據(jù)區(qū)域中的方法區(qū)惧互,在HotSpot虛擬機中又被習(xí)慣稱為永生代或者永生區(qū)哎媚,Permanet Generation中存放的為一些class的信息、常量喊儡、靜態(tài)變量等數(shù)據(jù)拨与,當(dāng)系統(tǒng)中要加載的類、反射的類和調(diào)用的方法較多時艾猜,Permanet Generation可能會被占滿买喧,在未配置為采用CMS GC的情況下也會執(zhí)行Full GC。如果經(jīng)過Full GC仍然回收不了匆赃,那么JVM會拋出如下錯誤信息: java.lang.OutOfMemoryError: PermGen space 為避免Perm Gen占滿造成Full GC現(xiàn)象淤毛,可采用的方法為增大Perm Gen空間或轉(zhuǎn)為使用CMS GC。

  1. 通過Minor GC后進入老年代的平均大小大于老年代的可用內(nèi)存

如果發(fā)現(xiàn)統(tǒng)計數(shù)據(jù)說之前Minor GC的平均晉升大小比目前old gen剩余的空間大算柳,則不會觸發(fā)Minor GC而是轉(zhuǎn)為觸發(fā)full GC

  1. 由Eden區(qū)低淡、From Space區(qū)向To Space區(qū)復(fù)制時,對象大小大于To Space可用內(nèi)存瞬项,則把該對象轉(zhuǎn)存到老年代蔗蹋,且老年代的可用內(nèi)存小于該對象大小
如何判斷對象是否存活?回收對象的兩次標記過程囱淋。
  1. 引用計數(shù)法:

    Java 對象添加一個引用計數(shù)器猪杭,每當(dāng)有一個地方引用它時,計數(shù)器 +1妥衣;引用失效則 -1皂吮,當(dāng)計數(shù)器不為 0 時戒傻,判斷該對象存活;否則判斷為死亡(計數(shù)器 = 0)蜂筹。

    優(yōu)點:

    • 實現(xiàn)簡單

    • 判斷高效

    缺點:

    • 無法解決對象間相互循環(huán)引用 的問題(即該算法存在判斷邏輯的漏洞)
  2. 引用鏈法(可達性分析法):

    將一系列的 GC Roots 對象作為起點稠鼻,從這些起點開始向下搜索。

    • 可作為 GC Root 的對象有: 1.Java虛擬機棧(棧幀的本地變量表)中引用的對象 2.本地方法棧 中 JNI引用對象 3.方法區(qū) 中常量狂票、類靜態(tài)屬性引用的對象
    • 向下搜索的路徑 = 引用鏈

    當(dāng)一個對象到 GC Roots 沒有任何引用鏈相連時候齿,則判斷該對象不可達

    沒有任何引用鏈相連 = GC Root到對象不可達 = 對象不可用

    特別注意:

    • 可達性分析 僅僅只是判斷對象是否可達,但還不足以判斷對象是否存活 / 死亡

    • 當(dāng)在 可達性分析 中判斷不可達的對象闺属,只是“被判刑” = 還沒真正死亡

    不可達對象會被放在”即將回收“的集合里慌盯。

    • 要判斷一個對象真正死亡,還需要經(jīng)歷兩個階段:

      1. 第一次標記 & 篩選

      2. 第二次標記 & 篩選

    第一次標記 & 篩選

    • 對象 在 可達性分析中 被判斷為不可達后掂器,會被第一次標記 & 準備被篩選

    a. 不篩選:繼續(xù)留在 ”即將回收“的集合里亚皂,等待回收; b. 篩選:從 ”即將回收“的集合取出

    • 篩選的標準:該對象是否有必要執(zhí)行 finalize( )方法

      1. 若有必要執(zhí)行(人為設(shè)置)国瓮,則篩選出來灭必,進入下一階段(第二次標記 & 篩選);

      2. 若沒必要執(zhí)行乃摹,判斷該對象死亡禁漓,不篩選 并等待回收

    當(dāng)對象無 finalize()方法 或 finalize()已被虛擬機調(diào)用過,則視為“沒必要執(zhí)行”

    第二次標記 & 篩選

    當(dāng)對象經(jīng)過了第一次的標記 & 篩選孵睬,會被進行第二次標記 & 準備被進行 篩選

    該對象會被放到一個 F-Queue 隊列中播歼,并由 虛擬機自動建立、優(yōu)先級低的Finalizer 線程去執(zhí)行 隊列中該對象的finalize()

    1. finalize()只會被執(zhí)行一次
    1. 但并不承諾等待finalize()運行結(jié)束掰读。這是為了防止 finalize()執(zhí)行緩慢 / 停止 使得 F-Queue隊列其他對象永久等待秘狞。

    篩選標準:

    在執(zhí)行finalize()過程中,若對象依然沒與引用鏈上的GC Roots 直接關(guān)聯(lián) 或 間接關(guān)聯(lián)(即關(guān)聯(lián)上與GC Roots 關(guān)聯(lián)的對象)蹈集,那么該對象將被判斷死亡烁试,不篩選(留在”即將回收“集合里) 并 等待回收

垃圾回收算法以及垃圾回收器介紹,尤其是G1和CMS的優(yōu)缺點

垃圾回收流程拢肆。

  1. 新建的對象减响,大部分存儲在Eden中

  2. 當(dāng)Eden內(nèi)存不夠,就進行Minor GC釋放掉不活躍對象善榛;然后將部分活躍對象復(fù)制到Survivor中(如Survivor1)辩蛋,同時清空Eden區(qū)

  3. 當(dāng)Eden區(qū)再次滿了,將Survivor1中不能清空的對象存放到另一個Survivor中(如Survivor2)移盆,同時將Eden區(qū)中的不能清空的對象悼院,復(fù)制到Survivor1,同時清空Eden區(qū)

  4. 重復(fù)多次(默認15次):Survivor中沒有被清理的對象就會復(fù)制到老年區(qū)(Old)

  5. 當(dāng)Old達到一定比例咒循,則會觸發(fā)Major GC釋放老年代

  6. 當(dāng)Old區(qū)滿了据途,則觸發(fā)一個一次完整的垃圾回收(Full GC)

  7. 如果內(nèi)存還是不夠绞愚,JVM會拋出內(nèi)存不足,發(fā)生oom颖医,內(nèi)存泄漏位衩。

垃圾回收算法
  1. Mark-Sweep(標記-清除)算法

    這是最基礎(chǔ)的垃圾回收算法,之所以說它是最基礎(chǔ)的是因為它最容易實現(xiàn)熔萧,思想也是最簡單的糖驴。標記-清除算法分為兩個階段:標記階段和清除階段。標記階段的任務(wù)是標記出所有需要被回收的對象佛致,清除階段就是回收被標記的對象所占用的空間贮缕。

    這一算法有一個比較嚴重的問題就是容易產(chǎn)生內(nèi)存碎片,碎片太多可能會導(dǎo)致后續(xù)過程中需要為大對象分配空間時無法找到足夠的空間而提前觸發(fā)新的一次垃圾收集動作俺榆。

  2. Copying(復(fù)制)算法

    為了解決Mark-Sweep算法的缺陷感昼,Copying算法就被提了出來。它將可用內(nèi)存按容量劃分為大小相等的兩塊罐脊,每次只使用其中的一塊定嗓。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面萍桌,然后再把已使用的內(nèi)存空間一次清理掉宵溅,這樣一來就不容易出現(xiàn)內(nèi)存碎片的問題。

    Copying算法的效率跟存活對象的數(shù)目多少有很大的關(guān)系梗夸,如果存活對象很多层玲,那么Copying算法的效率將會大大降低号醉。

  3. 為了解決Copying算法的缺陷反症,充分利用內(nèi)存空間,提出了Mark-Compact算法畔派。該算法標記階段和Mark-Sweep一樣铅碍,但是在完成標記之后,它不是直接清理可回收對象线椰,而是將存活對象都向一端移動胞谈,然后清理掉端邊界以外的內(nèi)存。

  4. 分代收集算法是目前大部分JVM的垃圾收集器采用的算法憨愉。它的核心思想是根據(jù)對象存活的生命周期將內(nèi)存劃分為若干個不同的區(qū)域烦绳。一般情況下將堆區(qū)劃分為老年代(Tenured Generation)和新生代(Young Generation),老年代的特點是每次垃圾收集時只有少量對象需要被回收配紫,而新生代的特點是每次垃圾回收時都有大量的對象需要被回收径密,那么就可以根據(jù)不同代的特點采取最適合的收集算法。

    目前大部分垃圾收集器對于新生代都采取復(fù)制算法躺孝,因為新生代中每次垃圾回收都要回收大部分對象享扔,也就是說需要復(fù)制的操作次數(shù)較少底桂,但是實際中并不是按照1:1的比例來劃分新生代的空間的,一般來說是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間惧眠,每次使用Eden空間和其中的一塊Survivor空間籽懦,當(dāng)進行回收時,將Eden和Survivor中還存活的對象復(fù)制到另一塊Survivor空間中氛魁,然后清理掉Eden和剛才使用過的Survivor空間暮顺。

    而由于老年代的特點是每次回收都只回收少量對象,一般使用的是標記-整理算法(壓縮法)或者標記-清除算法)秀存。

垃圾回收器
  1. Serial/Serial Old收集器 是最基本最古老的收集器拖云,它是一個單線程收集器,并且在它進行垃圾收集時应又,必須暫停所有用戶線程宙项。Serial收集器是針對新生代的收集器,采用的是Copying算法株扛,Serial Old收集器是針對老年代的收集器尤筐,采用的是Mark-Compact算法。它的優(yōu)點是實現(xiàn)簡單高效洞就,但是缺點是會給用戶帶來停頓盆繁。

  2. ParNew收集器 是Serial收集器的多線程版本,使用多個線程進行垃圾收集旬蟋。

  3. Parallel Scavenge收集器 是一個新生代的多線程收集器(并行收集器)油昂,它在回收期間不需要暫停其他用戶線程,其采用的是Copying算法倾贰,該收集器與前兩個收集器有所不同冕碟,它主要是為了達到一個可控的吞吐量。

  4. Parallel Old收集器 是Parallel Scavenge收集器的老年代版本(并行收集器)匆浙,使用多線程和Mark-Compact算法安寺。

  5. CMS(Current Mark Sweep)收集器 是一種以獲取最短回收停頓時間為目標的收集器,它是一種并發(fā)收集器首尼,采用的是Mark-Sweep算法挑庶。

  6. G1收集器 是當(dāng)今收集器技術(shù)發(fā)展最前沿的成果,它是一款面向服務(wù)端應(yīng)用的收集器软能,它能充分利用多CPU迎捺、多核環(huán)境。因此它是一款并行與并發(fā)收集器查排,并且它能建立可預(yù)測的停頓時間模型凳枝。

G1和CMS的優(yōu)缺點

CMS是一款優(yōu)秀的收集器,它的主要優(yōu)點是:并發(fā)收集雹嗦、低停頓范舀,但他有以下3個明顯的缺點:

優(yōu)點:并發(fā)收集合是,低停頓

理由: 由于在整個過程和中最耗時的并發(fā)標記和 并發(fā)清除過程收集器程序都可以和用戶線程一起工作,所以總體來說锭环,Cms收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的

缺點:

1.CMS收集器對CPU資源非常敏感

在并發(fā)階段聪全,雖然不會導(dǎo)致用戶線程停頓,但是會因為占用了一部分線程使應(yīng)用程序變慢辅辩,總吞吐量會降低难礼,為了解決這種情況,虛擬機提供了一種“增量式并發(fā)收集器”

的CMS收集器變種玫锋, 就是在并發(fā)標記和并發(fā)清除的時候讓GC線程和用戶線程交替運行蛾茉,盡量減少GC 線程獨占資源的時間,這樣整個垃圾收集的過程會變長撩鹿,但是對用戶程序的影響會減少谦炬。(效果不明顯,不推薦)

\2. CMS處理器無法處理浮動垃圾

CMS在并發(fā)清理階段線程還在運行节沦, 伴隨著程序的運行自然也會產(chǎn)生新的垃圾键思,這一部分垃圾產(chǎn)生在標記過程之后,CMS無法再當(dāng)次過程中處理甫贯,所以只有等到下次gc時候在清理掉吼鳞,這一部分垃圾就稱作“浮動垃圾” ,

\3. CMS是基于“標記--清除”算法實現(xiàn)的叫搁,所以在收集結(jié)束的時候會有大量的空間碎片產(chǎn)生赔桌。空間碎片太多的時候渴逻,將會給大對象的分配帶來很大的麻煩疾党,往往會出現(xiàn)老年代還有很大的空間剩余,但是無法找到足夠大的連續(xù)空間來分配當(dāng)前對象的裸卫,只能提前觸發(fā) full gc仿贬。

為了解決這個問題,CMS提供了一個開關(guān)參數(shù)墓贿,用于在CMS頂不住要進行full gc的時候開啟內(nèi)存碎片的合并整理過程,內(nèi)存整理的過程是無法并發(fā)的蜓氨,空間碎片沒有了聋袋,但是停頓的時間變長了

與其他GC收集器相比,G1具備如下特點:

1穴吹、并行于并發(fā):G1能充分利用CPU幽勒、多核環(huán)境下的硬件優(yōu)勢,使用多個CPU(CPU或者CPU核心)來縮短stop-The-World停頓時間港令。部分其他收集器原本需要停頓Java線程執(zhí)行的GC動作啥容,G1收集器仍然可以通過并發(fā)的方式讓java程序繼續(xù)執(zhí)行锈颗。

2、分代收集:雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆咪惠,但是還是保留了分代的概念击吱。它能夠采用不同的方式去處理新創(chuàng)建的對象和已經(jīng)存活了一段時間,熬過多次GC的舊對象以獲取更好的收集效果遥昧。

3覆醇、空間整合:與CMS的“標記--清理”算法不同,G1從整體來看是基于“標記整理”算法實現(xiàn)的收集器炭臭;從局部上來看是基于“復(fù)制”算法實現(xiàn)的永脓。

4、可預(yù)測的停頓:這是G1相對于CMS的另一個大優(yōu)勢鞋仍,降低停頓時間是G1和CMS共同的關(guān)注點常摧,但G1除了追求低停頓外,還能建立可預(yù)測的停頓時間模型威创,能讓使用者明確指定在一個長度為M毫秒的時間片段內(nèi)

創(chuàng)建一個對象的步驟
  1. 檢測類是否被加載:

    當(dāng)虛擬機執(zhí)行到new時排宰,會先去常量池中查找這個類的符號引用。如果能找到符號引用那婉,說明此類已經(jīng)被加載到方法區(qū)(方法區(qū)存儲虛擬機已經(jīng)加載的類的信息)板甘,可以繼續(xù)執(zhí)行;如果找不到符號引用详炬,就會使用類加載器執(zhí)行類的加載過程盐类,類加載完成后繼續(xù)執(zhí)行。

  2. 為對象分配內(nèi)存:

    類加載完成以后呛谜,虛擬機就開始為對象分配內(nèi)存在跳,此時所需內(nèi)存的大小就已經(jīng)確定了。只需要在堆上分配所需要的內(nèi)存即可隐岛。

    具體的分配內(nèi)存有兩種情況:第一種情況是內(nèi)存空間絕對規(guī)整猫妙,第二種情況是內(nèi)存空間是不連續(xù)的

    • 于內(nèi)存絕對規(guī)整的情況相對簡單一些聚凹,虛擬機只需要在被占用的內(nèi)存和可用空間之間移動指針即可割坠,這種方式被稱為指針碰撞。

    • 對于內(nèi)存不規(guī)整的情況稍微復(fù)雜一點妒牙,這時候虛擬機需要維護一個列表彼哼,來記錄哪些內(nèi)存是可用的。分配內(nèi)存的時候需要找到一個可用的內(nèi)存空間湘今,然后在列表上記錄下已被分配敢朱,這種方式成為空閑列表

    分配內(nèi)存的時候也需要考慮線程安全問題,有兩種解決方案:

    • 第一種是采用同步的辦法拴签,使用CAS來保證操作的原子性孝常。

    • 一種是每個線程分配內(nèi)存都在自己的空間內(nèi)進行,即是每個線程都在堆中預(yù)先分配一小塊內(nèi)存蚓哩,稱為本地線程分配緩沖(TLAB)构灸,分配內(nèi)存的時候再TLAB上分配,互不干擾杖剪。

  3. 為分配的內(nèi)存空間初始化零值:

    對象的內(nèi)存分配完成后冻押,還需要將對象的內(nèi)存空間都初始化為零值,這樣能保證對象即使沒有賦初值盛嘿,也可以直接使用

  4. 對對象進行其他設(shè)置:(設(shè)置對象頭)

    分配完內(nèi)存空間洛巢,初始化零值之后,虛擬機還需要對對象進行其他必要的設(shè)置次兆,設(shè)置的地方都在對象頭中稿茉,包括這個對象所屬的類,類的元數(shù)據(jù)信息芥炭,對象的hashcode漓库,GC分代年齡等信息。這些信息存放在對象頭中园蝠。 另外渺蒿,根據(jù)虛擬機當(dāng)前運行狀態(tài)的不同,如是否啟用偏向鎖等彪薛,對象頭會有不同的設(shè)置方式茂装。

  5. 執(zhí)行 init 方法:

    執(zhí)行完上面的步驟之后,在虛擬機里這個對象就算創(chuàng)建成功了善延,但是對于Java程序來說還需要執(zhí)行init方法才算真正的創(chuàng)建完成少态,因為這個時候?qū)ο笾皇潜怀跏蓟阒盗耍€沒有真正的去根據(jù)程序中的代碼分配初始值,調(diào)用了init方法之后,這個對象才真正能使用语卤。

到此為止一個對象就產(chǎn)生了,這就是new關(guān)鍵字創(chuàng)建對象的過程送浊。

image
詳細介紹類加載過程

Class 文件需要加載到虛擬機中之后才能運行和使用,那么虛擬機是如何加載這些 Class 文件呢?

系統(tǒng)加載 Class 類型的文件主要三步:加載->連接->初始化。連接過程又可分為三步:驗證->準備->解析为肮。

image
加載

類加載過程的第一步,主要完成下面3件事情:

  1. 通過全類名獲取定義此類的二進制字節(jié)流
  1. 將字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
  1. 在內(nèi)存中生成一個代表該類的 Class 對象肤京,作為方法區(qū)這些數(shù)據(jù)的訪問入口
驗證

檢查加載到的class文件的正確性

image
準備

準備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中分配。

要注意的點:

  1. 這時候進行內(nèi)存分配的僅包括類變量(static)忘分,而不包括實例變量棋枕,實例變量會在對象實例化時隨著對象一塊分配在 Java 堆中。
  1. 這里所設(shè)置的初始值"通常情況"下是數(shù)據(jù)類型默認的零值(如0妒峦、0L重斑、null、false等)肯骇,比如我們定義了public static int value=111 窥浪,那么 value 變量在準備階段的初始值就是 0 而不是111(初始化階段才會賦值)。特殊情況:比如給 value 變量加上了 fianl 關(guān)鍵字public static final int value=111 笛丙,那么準備階段 value 的值就被賦值為 111漾脂。
image
解析

解析階段是虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程。也就是得到類或者字段胚鸯、方法在內(nèi)存中的指針或者偏移量骨稿。

符號引用就理解為一個標示,而直接引用直接指向內(nèi)存中的地址姜钳。

解析動作主要針對類或接口坦冠、字段、類方法哥桥、接口方法辙浑、方法類型、方法句柄和調(diào)用限定符7類符號引用進行拟糕。

初始化

類加載的最后一步判呕,是真正執(zhí)行類中定義的 Java 程序代碼(字節(jié)碼),初始化階段是執(zhí)行類構(gòu)造器 <clinit> ()方法的過程已卸。

雙親委派機制佛玄,使用這個機制的好處?破壞雙親委派機制的場景累澡?如何破壞梦抢?
雙親委派機制

每一個類都有一個對應(yīng)它的類加載器。系統(tǒng)中的 ClassLoder 在協(xié)同工作的時候會默認使用雙親委派機制愧哟。即在類加載的時候奥吩,系統(tǒng)會首先判斷當(dāng)前類是否被加載過。已經(jīng)被加載的類會直接返回蕊梧,否則它才會嘗試加載霞赫。加載的時候,首先會把該請求委派該父類加載器的 loadClass() 處理肥矢,因此所有的請求最終都應(yīng)該傳送到頂層的啟動類加載器 BootstrapClassLoader 中端衰。當(dāng)父類加載器無法處理時叠洗,才由自己來處理。當(dāng)父類加載器為null時旅东,會使用啟動類加載器 BootstrapClassLoader 作為父類加載器灭抑。

image

注意:其實這個雙親翻譯的容易讓別人誤解,我們一般理解的雙親都是父母抵代,這里的雙親更多地表達的是“父母這一輩”的人而已腾节,并不是說真的有一個 Mother ClassLoader 和一個 Father ClassLoader 。另外荤牍,類加載器之間的“父子”關(guān)系也不是通過繼承來體現(xiàn)的案腺,是由“優(yōu)先級”來決定。

雙親加載機制的好處
  1. 隔離:不同類型的類由不同的類加載器進行加載康吵,互不干預(yù)劈榨;
  1. 分級:加載的繼承關(guān)系體現(xiàn)了 分級的關(guān)系模型;
  1. 安全:核心類 由 bootstrap class loader加載涎才,不會被輕易替換/篡改鞋既;
  1. 加載:無重復(fù)加載,默認使用父類加載的類耍铜;
  1. 工程:簡單邑闺、易懂、可維護棕兼,并且很容易擴展出自己的類加載器陡舅;
破壞雙親委派機制的場景?如何破壞伴挚?

雙親委派模型的作用:保證JDK核心類的優(yōu)先加載靶衍;

如何破壞:

  1. 自定義類加載器,重寫loadClass方法茎芋;
  1. 使用線程上下文類加載器

破壞的場景:參照https://www.pianshen.com/article/4541600181/

了解下tomcat的類加載機制

Tomcat的類加載機制是違反了雙親委托原則的颅眶,對于一些未加載的非基礎(chǔ)類(Object,String等),各個web應(yīng)用自己的類加載器(WebAppClassLoader)會優(yōu)先加載田弥,加載不到時再交給commonClassLoader走雙親委托涛酗。

雙親委派模型要求除了頂層的啟動類加載器之外,其余的類加載器都應(yīng)當(dāng)由自己的父類加載器加載偷厦。

tomcat不遵循雙親委派機制商叹,只是自定義的classLoader順序不同,但頂層還是相同的只泼,還是要去頂層請求classloader.

JVM性能調(diào)優(yōu)剖笙,常用命令,以及工具
性能調(diào)優(yōu)
  • 線程池:解決用戶響應(yīng)時間長的問題
  • 連接池
  • JVM啟動參數(shù):調(diào)整各代的內(nèi)存比例和垃圾回收算法请唱,提高吞吐量
  • 程序算法:改進程序邏輯算法提高性能

具體參照:https://www.cnblogs.com/csniper/p/5592593.html

本章可參考:https://www.zhihu.com/question/28477388/answer/2247519738

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弥咪,一起剝皮案震驚了整個濱河市过蹂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酪夷,老刑警劉巖榴啸,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孽惰,死亡現(xiàn)場離奇詭異晚岭,居然都是意外死亡,警方通過查閱死者的電腦和手機勋功,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門坦报,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人狂鞋,你說我怎么就攤上這事片择。” “怎么了骚揍?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵字管,是天一觀的道長。 經(jīng)常有香客問我信不,道長嘲叔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任抽活,我火速辦了婚禮硫戈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘下硕。我一直安慰自己丁逝,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布梭姓。 她就那樣靜靜地躺著霜幼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪誉尖。 梳的紋絲不亂的頭發(fā)上罪既,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音释牺,去河邊找鬼萝衩。 笑死,一個胖子當(dāng)著我的面吹牛没咙,可吹牛的內(nèi)容都是我干的猩谊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼祭刚,長吁一口氣:“原來是場噩夢啊……” “哼牌捷!你這毒婦竟也來了墙牌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤暗甥,失蹤者是張志新(化名)和其女友劉穎喜滨,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撤防,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡虽风,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了寄月。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辜膝。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖漾肮,靈堂內(nèi)的尸體忽然破棺而出厂抖,到底是詐尸還是另有隱情,我是刑警寧澤克懊,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布忱辅,位于F島的核電站,受9級特大地震影響谭溉,放射性物質(zhì)發(fā)生泄漏墙懂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一夜只、第九天 我趴在偏房一處隱蔽的房頂上張望垒在。 院中可真熱鬧,春花似錦扔亥、人聲如沸场躯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽踢关。三九已至,卻和暖如春粘茄,著一層夾襖步出監(jiān)牢的瞬間签舞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工柒瓣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留儒搭,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓芙贫,卻偏偏與公主長得像搂鲫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子磺平,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345