精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

看完這篇垃圾回收,和面試官扯皮沒(méi)問(wèn)題了

開(kāi)發(fā) 開(kāi)發(fā)工具
Java 相比 C/C++ 最顯著的特點(diǎn)便是引入了自動(dòng)垃圾回收 (下文統(tǒng)一用 GC 指代自動(dòng)垃圾回收),它解決了 C/C++ 最令人頭疼的內(nèi)存管理問(wèn)題,讓程序員專注于程序本身,不用關(guān)心內(nèi)存回收這些惱人的問(wèn)題。

 Java 相比 C/C++ 最顯著的特點(diǎn)便是引入了自動(dòng)垃圾回收 (下文統(tǒng)一用 GC 指代自動(dòng)垃圾回收),它解決了 C/C++ 最令人頭疼的內(nèi)存管理問(wèn)題,讓程序員專注于程序本身,不用關(guān)心內(nèi)存回收這些惱人的問(wèn)題,這也是 Java 能大行其道的重要原因之一,GC 真正讓程序員的生產(chǎn)力得到了釋放,但是程序員很難感知到它的存在,這就好比,我們吃完飯后在桌上放下餐盤(pán)即走,服務(wù)員會(huì)替你收拾好這些餐盤(pán),你不會(huì)關(guān)心服務(wù)員什么時(shí)候來(lái)收,怎么收。

有人說(shuō)既然 GC 已經(jīng)自動(dòng)我們完成了清理,不了解 GC 貌似也沒(méi)啥問(wèn)題。在大多數(shù)情況下確實(shí)沒(méi)問(wèn)題,不過(guò)如果涉及到一些性能調(diào)優(yōu),問(wèn)題排查等,深入地了解 GC 還是必不可少的,曾經(jīng)美團(tuán)通過(guò)調(diào)整 JVM 相關(guān) GC 參數(shù)讓服務(wù)響應(yīng)時(shí)間 TP90,TP99都下降了10ms+,服務(wù)可用性得到了很大的提升!所以深入了解 GC 是成為一名優(yōu)秀 Java 程序員的必修課!

垃圾回收分上下篇,上篇會(huì)先講垃圾回收理論,主要包括

GC 的幾種主要的收集方法:標(biāo)記清除、標(biāo)記整理、復(fù)制算法的原理與特點(diǎn),各自的優(yōu)劣勢(shì)

為啥會(huì)有 Serial ,CMS, G1 等各式樣的回收器,各自的優(yōu)劣勢(shì)是什么,為啥沒(méi)有一個(gè)統(tǒng)一的萬(wàn)能的垃圾回收器

新生代為啥要設(shè)置成 Eden, S0,S1 這三個(gè)區(qū),基于什么考慮呢

堆外內(nèi)存不受 GC 控制,那該怎么釋放呢

對(duì)象可回收,就一定會(huì)被回收嗎?

什么是 SafePoint,什么是 Stop The World

下篇主要講垃圾回收的實(shí)踐,主要包括

GC 日志格式怎么看

主要有哪些發(fā)生 OOM 的場(chǎng)景

發(fā)生 OOM,如何定位,常用的內(nèi)存調(diào)試工具有哪些

本文會(huì)從以下幾方面來(lái)闡述垃圾回收

JVM 內(nèi)存區(qū)域

如何識(shí)別垃圾

引用計(jì)數(shù)法

可達(dá)性算法

垃圾回收主要方法

標(biāo)記清除法

復(fù)制法

標(biāo)記整理法

分代收集算法

垃圾回收器對(duì)比

文字比較多,不過(guò)也為了便于讀者理解加了不少 GC 的動(dòng)畫(huà),相信看完會(huì)有不少收獲

JVM 內(nèi)存區(qū)域

要搞懂垃圾回收的機(jī)制,我們首先要知道垃圾回收主要回收的是哪些數(shù)據(jù),這些數(shù)據(jù)主要在哪一塊區(qū)域,所以我們一起來(lái)看下 JVM 的內(nèi)存區(qū)域

 

 

 

 

虛擬機(jī)棧:描述的是方法執(zhí)行時(shí)的內(nèi)存模型,是線程私有的,生命周期與線程相同,每個(gè)方法被執(zhí)行的同時(shí)會(huì)創(chuàng)建棧楨(下文會(huì)看到),主要保存執(zhí)行方法時(shí)的局部變量表、操作數(shù)棧、動(dòng)態(tài)連接和方法返回地址等信息,方法執(zhí)行時(shí)入棧,方法執(zhí)行完出棧,出棧就相當(dāng)于清空了數(shù)據(jù),入棧出棧的時(shí)機(jī)很明確,所以這塊區(qū)域不需要進(jìn)行 GC。

本地方法棧:與虛擬機(jī)棧功能非常類似,主要區(qū)別在于虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法時(shí)服務(wù),而本地方法棧為虛擬機(jī)執(zhí)行本地方法時(shí)服務(wù)的。這塊區(qū)域也不需要進(jìn)行 GC

程序計(jì)數(shù)器:線程獨(dú)有的, 可以把它看作是當(dāng)前線程執(zhí)行的字節(jié)碼的行號(hào)指示器,比如如下字節(jié)碼內(nèi)容,在每個(gè)字節(jié)碼`前面都有一個(gè)數(shù)字(行號(hào)),我們可以認(rèn)為它就是程序計(jì)數(shù)器存儲(chǔ)的內(nèi)容

 

記錄這些數(shù)字(指令地址)有啥用呢,我們知道 Java 虛擬機(jī)的多線程是通過(guò)線程輪流切換并分配處理器的時(shí)間來(lái)完成的,在任何一個(gè)時(shí)刻,一個(gè)處理器只會(huì)執(zhí)行一個(gè)線程,如果這個(gè)線程被分配的時(shí)間片執(zhí)行完了(線程被掛起),處理器會(huì)切換到另外一個(gè)線程執(zhí)行,當(dāng)下次輪到執(zhí)行被掛起的線程(喚醒線程)時(shí),怎么知道上次執(zhí)行到哪了呢,通過(guò)記錄在程序計(jì)數(shù)器中的行號(hào)指示器即可知道,所以程序計(jì)數(shù)器的主要作用是記錄線程運(yùn)行時(shí)的狀態(tài),方便線程被喚醒時(shí)能從上一次被掛起時(shí)的狀態(tài)繼續(xù)執(zhí)行,需要注意的是,程序計(jì)數(shù)器是唯一一個(gè)在 Java 虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何 OOM 情況的區(qū)域,所以這塊區(qū)域也不需要進(jìn)行 GC

 

 

本地內(nèi)存:線程共享區(qū)域,Java 8 中,本地內(nèi)存,也是我們通常說(shuō)的堆外內(nèi)存,包含元空間和直接內(nèi)存,注意到上圖中 Java 8 和 Java 8 之前的 JVM 內(nèi)存區(qū)域的區(qū)別了嗎,在 Java 8 之前有個(gè)永久代的概念,實(shí)際上指的是 HotSpot 虛擬機(jī)上的永久代,它用永久代實(shí)現(xiàn)了 JVM 規(guī)范定義的方法區(qū)功能,主要存儲(chǔ)類的信息,常量,靜態(tài)變量,即時(shí)編譯器編譯后代碼等,這部分由于是在堆中實(shí)現(xiàn)的,受 GC 的管理,不過(guò)由于永久代有 -XX:MaxPermSize 的上限,所以如果動(dòng)態(tài)生成類(將類信息放入永久代)或大量地執(zhí)行String.intern (將字段串放入永久代中的常量區(qū)),很容易造成 OOM,有人說(shuō)可以把永久代設(shè)置得足夠大,但很難確定一個(gè)合適的大小,受類數(shù)量,常量數(shù)量的多少影響很大。所以在 Java 8 中就把方法區(qū)的實(shí)現(xiàn)移到了本地內(nèi)存中的元空間中,這樣方法區(qū)就不受 JVM 的控制了,也就不會(huì)進(jìn)行 GC,也因此提升了性能(發(fā)生 GC 會(huì)發(fā)生 Stop The Word,造成性能受到一定影響,后文會(huì)提到),也就不存在由于永久代限制大小而導(dǎo)致的 OOM 異常了(假設(shè)總內(nèi)存1G,JVM 被分配內(nèi)存 100M, 理論上元空間可以分配 2G-100M = 1.9G,空間大小足夠),也方便在元空間中統(tǒng)一管理。綜上所述,在 Java 8 以后這一區(qū)域也不需要進(jìn)行 GC

畫(huà)外音: 思考一個(gè)問(wèn)題,堆外內(nèi)存不受 GC控制,無(wú)法通過(guò) GC 釋放內(nèi)存,那該以什么樣的形式釋放呢,總不能只創(chuàng)建不釋放吧,這樣的話內(nèi)存可能很快就滿了,這里不做詳細(xì)闡述,請(qǐng)看文末的參考文章

堆:前面幾塊數(shù)據(jù)區(qū)域都不進(jìn)行 GC,那只剩下堆了,是的,這里是 GC 發(fā)生的區(qū)域!對(duì)象實(shí)例和數(shù)組都是在堆上分配的,GC 也主要對(duì)這兩類數(shù)據(jù)進(jìn)行回收,這塊也是我們之后重點(diǎn)需要分析的區(qū)域

如何識(shí)別垃圾

上一節(jié)我們?cè)敿?xì)講述了 JVM 的內(nèi)存區(qū)域,知道了 GC 主要發(fā)生在堆,那么 GC 該怎么判斷堆中的對(duì)象實(shí)例或數(shù)據(jù)是不是垃圾呢,或者說(shuō)判斷某些數(shù)據(jù)是否是垃圾的方法有哪些。

引用計(jì)數(shù)法

最容易想到的一種方式是引用計(jì)數(shù)法,啥叫引用計(jì)數(shù)法,簡(jiǎn)單地說(shuō),就是對(duì)象被引用一次,在它的對(duì)象頭上加一次引用次數(shù),如果沒(méi)有被引用(引用次數(shù)為 0),則此對(duì)象可回收

String ref = new String("Java");

以上代碼 ref1 引用了右側(cè)定義的對(duì)象,所以引用次數(shù)是 1

 

 

 

 

如果在上述代碼后面添加一個(gè) ref = null,則由于對(duì)象沒(méi)被引用,引用次數(shù)置為 0,由于不被任何變量引用,此時(shí)即被回收,動(dòng)圖如下

 

 

 

 

看起來(lái)用引用計(jì)數(shù)確實(shí)沒(méi)啥問(wèn)題了,不過(guò)它無(wú)法解決一個(gè)主要的問(wèn)題:循環(huán)引用!啥叫循環(huán)引用

  1. public  class TestRC { 
  2.  
  3.     TestRC instance; 
  4.     public TestRC(String name) { 
  5.     } 
  6.  
  7.     public static  void main(String[] args) { 
  8.         // 第一步 
  9.     A a = new TestRC("a"); 
  10.     B b = new TestRC("b"); 
  11.  
  12.         // 第二步 
  13.     a.instance = b; 
  14.     b.instance = a; 
  15.  
  16.         // 第三步 
  17.     a = null
  18.     b = null
  19.     } 

 

按步驟一步步畫(huà)圖

 

 

 

 

到了第三步,雖然 a,b 都被置為 null 了,但是由于之前它們指向的對(duì)象互相指向了對(duì)方(引用計(jì)數(shù)都為 1),所以無(wú)法回收,也正是由于無(wú)法解決循環(huán)引用的問(wèn)題,所以現(xiàn)代虛擬機(jī)都不用引用計(jì)數(shù)法來(lái)判斷對(duì)象是否應(yīng)該被回收。

可達(dá)性算法

現(xiàn)代虛擬機(jī)基本都是采用這種算法來(lái)判斷對(duì)象是否存活,可達(dá)性算法的原理是以一系列叫做 GC Root 的對(duì)象為起點(diǎn)出發(fā),引出它們指向的下一個(gè)節(jié)點(diǎn),再以下個(gè)節(jié)點(diǎn)為起點(diǎn),引出此節(jié)點(diǎn)指向的下一個(gè)結(jié)點(diǎn)。。。(這樣通過(guò) GC Root 串成的一條線就叫引用鏈),直到所有的結(jié)點(diǎn)都遍歷完畢,如果相關(guān)對(duì)象不在任意一個(gè)以 GC Root 為起點(diǎn)的引用鏈中,則這些對(duì)象會(huì)被判斷為「垃圾」,會(huì)被 GC 回收。

 

 

 

 

如圖示,如果用可達(dá)性算法即可解決上述循環(huán)引用的問(wèn)題,因?yàn)閺腉C Root 出發(fā)沒(méi)有到達(dá) a,b,所以 a,b 可回收

a, b 對(duì)象可回收,就一定會(huì)被回收嗎?并不是,對(duì)象的 finalize 方法給了對(duì)象一次垂死掙扎的機(jī)會(huì),當(dāng)對(duì)象不可達(dá)(可回收)時(shí),當(dāng)發(fā)生GC時(shí),會(huì)先判斷對(duì)象是否執(zhí)行了 finalize 方法,如果未執(zhí)行,則會(huì)先執(zhí)行 finalize 方法,我們可以在此方法里將當(dāng)前對(duì)象與 GC Roots 關(guān)聯(lián),這樣執(zhí)行 finalize 方法之后,GC 會(huì)再次判斷對(duì)象是否可達(dá),如果不可達(dá),則會(huì)被回收,如果可達(dá),則不回收!

注意: finalize 方法只會(huì)被執(zhí)行一次,如果第一次執(zhí)行 finalize 方法此對(duì)象變成了可達(dá)確實(shí)不會(huì)回收,但如果對(duì)象再次被 GC,則會(huì)忽略 finalize 方法,對(duì)象會(huì)被回收!這一點(diǎn)切記!

那么這些 GC Roots 到底是什么東西呢,哪些對(duì)象可以作為 GC Root 呢,有以下幾類

虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象

方法區(qū)中類靜態(tài)屬性引用的對(duì)象

方法區(qū)中常量引用的對(duì)象

本地方法棧中 JNI(即一般說(shuō)的 Native 方法)引用的對(duì)象

虛擬機(jī)棧中引用的對(duì)象

如下代碼所示,a 是棧幀中的本地變量,當(dāng) a = null 時(shí),由于此時(shí) a 充當(dāng)了 GC Root 的作用,a 與原來(lái)指向的實(shí)例 new Test() 斷開(kāi)了連接,所以對(duì)象會(huì)被回收。

  1. public class Test { 
  2.     public static  void main(String[] args) { 
  3.     Test a = new Test(); 
  4.     a = null
  5.     } 

 

方法區(qū)中類靜態(tài)屬性引用的對(duì)象

如下代碼所示,當(dāng)棧幀中的本地變量 a = null 時(shí),由于 a 原來(lái)指向的對(duì)象與 GC Root (變量 a) 斷開(kāi)了連接,所以 a 原來(lái)指向的對(duì)象會(huì)被回收,而由于我們給 s 賦值了變量的引用,s 在此時(shí)是類靜態(tài)屬性引用,充當(dāng)了 GC Root 的作用,它指向的對(duì)象依然存活!

  1. public  class Test { 
  2.     public  static Test s; 
  3.     public static  void main(String[] args) { 
  4.     Test a = new Test(); 
  5.     a.s = new Test(); 
  6.     a = null
  7.     } 

 

方法區(qū)中常量引用的對(duì)象

如下代碼所示,常量 s 指向的對(duì)象并不會(huì)因?yàn)?a 指向的對(duì)象被回收而回收

  1. public  class Test { 
  2.     public  static  final Test s = new Test(); 
  3.         public static void main(String[] args) { 
  4.         Test a = new Test(); 
  5.         a = null
  6.         } 

 

本地方法棧中 JNI 引用的對(duì)象

這是簡(jiǎn)單給不清楚本地方法為何物的童鞋簡(jiǎn)單解釋一下:所謂本地方法就是一個(gè) java 調(diào)用非 java 代碼的接口,該方法并非 Java 實(shí)現(xiàn)的,可能由 C 或 Python等其他語(yǔ)言實(shí)現(xiàn)的, Java 通過(guò) JNI 來(lái)調(diào)用本地方法, 而本地方法是以庫(kù)文件的形式存放的(在 WINDOWS 平臺(tái)上是 DLL 文件形式,在 UNIX 機(jī)器上是 SO 文件形式)。通過(guò)調(diào)用本地的庫(kù)文件的內(nèi)部方法,使 JAVA 可以實(shí)現(xiàn)和本地機(jī)器的緊密聯(lián)系,調(diào)用系統(tǒng)級(jí)的各接口方法,還是不明白?見(jiàn)文末參考,對(duì)本地方法定義與使用有詳細(xì)介紹。

當(dāng)調(diào)用 Java 方法時(shí),虛擬機(jī)會(huì)創(chuàng)建一個(gè)棧楨并壓入 Java 棧,而當(dāng)它調(diào)用的是本地方法時(shí),虛擬機(jī)會(huì)保持 Java 棧不變,不會(huì)在 Java 棧禎中壓入新的禎,虛擬機(jī)只是簡(jiǎn)單地動(dòng)態(tài)連接并直接調(diào)用指定的本地方法。

 

 

 

 

 

  1. JNIEXPORT void JNICALL Java_com_pecuyu_jnirefdemo_MainActivity_newStringNative(JNIEnv *env, jobject instance,jstring jmsg) { 
  2. ... 
  3.    // 緩存String的class 
  4.    jclass jc = (*env)->FindClass(env, STRING_PATH); 

如上代碼所示,當(dāng) java 調(diào)用以上本地方法時(shí),jc 會(huì)被本地方法棧壓入棧中, jc 就是我們說(shuō)的本地方法棧中 JNI 的對(duì)象引用,因此只會(huì)在此本地方法執(zhí)行完成后才會(huì)被釋放。

垃圾回收主要方法

上一節(jié)我們知道了可以通過(guò)可達(dá)性算法來(lái)識(shí)別哪些數(shù)據(jù)是垃圾,那該怎么對(duì)這些垃圾進(jìn)行回收呢。主要有以下幾種方式方式

標(biāo)記清除算法

復(fù)制算法

標(biāo)記整理法

標(biāo)記清除算法

步驟很簡(jiǎn)單

先根據(jù)可達(dá)性算法標(biāo)記出相應(yīng)的可回收對(duì)象(圖中黃色部分)

對(duì)可回收的對(duì)象進(jìn)行回收

 

操作起來(lái)確實(shí)很簡(jiǎn)單,也不用做移動(dòng)數(shù)據(jù)的操作,那有啥問(wèn)題呢?仔細(xì)看上圖,沒(méi)錯(cuò),內(nèi)存碎片!假如我們想在上圖中的堆中分配一塊需要連續(xù)內(nèi)存占用 4M 或 5M 的區(qū)域,顯然是會(huì)失敗,怎么解決呢,如果能把上面未使用的 2M, 2M,1M 內(nèi)存能連起來(lái)就能連成一片可用空間為 5M 的區(qū)域即可,怎么做呢?

 

 

復(fù)制算法

把堆等分成兩塊區(qū)域, A 和 B,區(qū)域 A 負(fù)責(zé)分配對(duì)象,區(qū)域 B 不分配, 對(duì)區(qū)域 A 使用以上所說(shuō)的標(biāo)記法把存活的對(duì)象標(biāo)記出來(lái)(下圖有誤無(wú)需清除),然后把區(qū)域 A 中存活的對(duì)象都復(fù)制到區(qū)域 B(存活對(duì)象都依次緊鄰排列)最后把 A 區(qū)對(duì)象全部清理掉釋放出空間,這樣就解決了內(nèi)存碎片的問(wèn)題了。

 

 

 

 

不過(guò)復(fù)制算法的缺點(diǎn)很明顯,比如給堆分配了 500M 內(nèi)存,結(jié)果只有 250M 可用,空間平白無(wú)故減少了一半!這肯定是不能接受的!另外每次回收也要把存活對(duì)象移動(dòng)到另一半,效率低下(我們可以想想刪除數(shù)組元素再把非刪除的元素往一端移,效率顯然堪憂)

標(biāo)記整理法

前面兩步和標(biāo)記清除法一樣,不同的是它在標(biāo)記清除法的基礎(chǔ)上添加了一個(gè)整理的過(guò)程 ,即將所有的存活對(duì)象都往一端移動(dòng),緊鄰排列(如圖示),再清理掉另一端的所有區(qū)域,這樣的話就解決了內(nèi)存碎片的問(wèn)題。

 

 

 

 

但是缺點(diǎn)也很明顯:每進(jìn)一次垃圾清除都要頻繁地移動(dòng)存活的對(duì)象,效率十分低下。

分代收集算法

分代收集算法整合了以上算法,綜合了這些算法的優(yōu)點(diǎn),最大程度避免了它們的缺點(diǎn),所以是現(xiàn)代虛擬機(jī)采用的首選算法,與其說(shuō)它是算法,倒不是說(shuō)它是一種策略,因?yàn)樗前焉鲜鰩追N算法整合在了一起,為啥需要分代收集呢,來(lái)看一下對(duì)象的分配有啥規(guī)律

 

如圖示:縱軸代表已分配的字節(jié),而橫軸代表程序運(yùn)行時(shí)間

 

 

由圖可知,大部分的對(duì)象都很短命,都在很短的時(shí)間內(nèi)都被回收了(IBM 專業(yè)研究表明,一般來(lái)說(shuō),98% 的對(duì)象都是朝生夕死的,經(jīng)過(guò)一次 Minor GC 后就會(huì)被回收),所以分代收集算法根據(jù)對(duì)象存活周期的不同將堆分成新生代和老生代(Java8以前還有個(gè)永久代),默認(rèn)比例為 1 : 2,新生代又分為 Eden 區(qū), from Survivor 區(qū)(簡(jiǎn)稱S0),to Survivor 區(qū)(簡(jiǎn)稱 S1),三者的比例為 8: 1 : 1,這樣就可以根據(jù)新老生代的特點(diǎn)選擇最合適的垃圾回收算法,我們把新生代發(fā)生的 GC 稱為 Young GC(也叫 Minor GC),老年代發(fā)生的 GC 稱為 Old GC(也稱為 Full GC)。

 

 

 

 

畫(huà)外音:思考一下,新生代為啥要分這么多區(qū)?

那么分代垃圾收集是怎么工作的呢,我們一起來(lái)看看

分代收集工作原理

1、對(duì)象在新生代的分配與回收

由以上的分析可知,大部分對(duì)象在很短的時(shí)間內(nèi)都會(huì)被回收,對(duì)象一般分配在 Eden 區(qū)

 

 

 

 

當(dāng) Eden 區(qū)將滿時(shí),觸發(fā) Minor GC

 

 

 

我們之前怎么說(shuō)來(lái)著,大部分對(duì)象在短時(shí)間內(nèi)都會(huì)被回收, 所以經(jīng)過(guò) Minor GC 后只有少部分對(duì)象會(huì)存活,它們會(huì)被移到 S0 區(qū)(這就是為啥空間大小 Eden: S0: S1 = 8:1:1, Eden 區(qū)遠(yuǎn)大于 S0,S1 的原因,因?yàn)樵?Eden 區(qū)觸發(fā)的 Minor GC 把大部對(duì)象(接近98%)都回收了,只留下少量存活的對(duì)象,此時(shí)把它們移到 S0 或 S1 綽綽有余)同時(shí)對(duì)象年齡加一(對(duì)象的年齡即發(fā)生 Minor GC 的次數(shù)),最后把 Eden 區(qū)對(duì)象全部清理以釋放出空間,動(dòng)圖如下

 

 

 

 

當(dāng)觸發(fā)下一次 Minor GC 時(shí),會(huì)把 Eden 區(qū)的存活對(duì)象和 S0(或S1) 中的存活對(duì)象(S0 或 S1 中的存活對(duì)象經(jīng)過(guò)每次 Minor GC 都可能被回收)一起移到 S1(Eden 和 S0 的存活對(duì)象年齡+1), 同時(shí)清空 Eden 和 S0 的空間。

 

 

 

若再觸發(fā)下一次 Minor GC,則重復(fù)上一步,只不過(guò)此時(shí)變成了 從 Eden,S1 區(qū)將存活對(duì)象復(fù)制到 S0 區(qū),每次垃圾回收, S0, S1 角色互換,都是從 Eden ,S0(或S1) 將存活對(duì)象移動(dòng)到 S1(或S0)。也就是說(shuō)在 Eden 區(qū)的垃圾回收我們采用的是復(fù)制算法,因?yàn)樵?Eden 區(qū)分配的對(duì)象大部分在 Minor GC 后都消亡了,只剩下極少部分存活對(duì)象(這也是為啥 Eden:S0:S1 默認(rèn)為 8:1:1 的原因),S0,S1 區(qū)域也比較小,所以最大限度地降低了復(fù)制算法造成的對(duì)象頻繁拷貝帶來(lái)的開(kāi)銷。

2、對(duì)象何時(shí)晉升老年代

當(dāng)對(duì)象的年齡達(dá)到了我們?cè)O(shè)定的閾值,則會(huì)從S0(或S1)晉升到老年代

 

如圖示:年齡閾值設(shè)置為 15, 當(dāng)發(fā)生下一次 Minor GC 時(shí),S0 中有個(gè)對(duì)象年齡達(dá)到 15,達(dá)到我們的設(shè)定閾值,晉升到老年代!

 

 

大對(duì)象 當(dāng)某個(gè)對(duì)象分配需要大量的連續(xù)內(nèi)存時(shí),此時(shí)對(duì)象的創(chuàng)建不會(huì)分配在 Eden 區(qū),會(huì)直接分配在老年代,因?yàn)槿绻汛髮?duì)象分配在 Eden 區(qū), Minor GC 后再移動(dòng)到 S0,S1 會(huì)有很大的開(kāi)銷(對(duì)象比較大,復(fù)制會(huì)比較慢,也占空間),也很快會(huì)占滿 S0,S1 區(qū),所以干脆就直接移到老年代.

還有一種情況也會(huì)讓對(duì)象晉升到老年代,即在 S0(或S1) 區(qū)相同年齡的對(duì)象大小之和大于 S0(或S1)空間一半以上時(shí),則年齡大于等于該年齡的對(duì)象也會(huì)晉升到老年代。

3、空間分配擔(dān)保

在發(fā)生 MinorGC 之前,虛擬機(jī)會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象的總空間,如果大于,那么Minor GC 可以確保是安全的,如果不大于,那么虛擬機(jī)會(huì)查看 HandlePromotionFailure 設(shè)置值是否允許擔(dān)保失敗。如果允許,那么會(huì)繼續(xù)檢查老年代最大可用連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于則進(jìn)行 Minor GC,否則可能進(jìn)行一次 Full GC。

4、Stop The World

如果老年代滿了,會(huì)觸發(fā) Full GC, Full GC 會(huì)同時(shí)回收新生代和老年代(即對(duì)整個(gè)堆進(jìn)行GC),它會(huì)導(dǎo)致 Stop The World(簡(jiǎn)稱 STW),造成挺大的性能開(kāi)銷。

什么是 STW ?所謂的 STW, 即在 GC(minor GC 或 Full GC)期間,只有垃圾回收器線程在工作,其他工作線程則被掛起。

 

 

 

 

畫(huà)外音:為啥在垃圾收集期間其他工作線程會(huì)被掛起?想象一下,你一邊在收垃圾,另外一群人一邊丟垃圾,垃圾能收拾干凈嗎。

一般 Full GC 會(huì)導(dǎo)致工作線程停頓時(shí)間過(guò)長(zhǎng)(因?yàn)镕ull GC 會(huì)清理整個(gè)堆中的不可用對(duì)象,一般要花較長(zhǎng)的時(shí)間),如果在此 server 收到了很多請(qǐng)求,則會(huì)被拒絕服務(wù)!所以我們要盡量減少 Full GC(Minor GC 也會(huì)造成 STW,但只會(huì)觸發(fā)輕微的 STW,因?yàn)?Eden 區(qū)的對(duì)象大部分都被回收了,只有極少數(shù)存活對(duì)象會(huì)通過(guò)復(fù)制算法轉(zhuǎn)移到 S0 或 S1 區(qū),所以相對(duì)還好)。

現(xiàn)在我們應(yīng)該明白把新生代設(shè)置成 Eden, S0,S1區(qū)或者給對(duì)象設(shè)置年齡閾值或者默認(rèn)把新生代與老年代的空間大小設(shè)置成 1:2 都是為了盡可能地避免對(duì)象過(guò)早地進(jìn)入老年代,盡可能晚地觸發(fā) Full GC。想想新生代如果只設(shè)置 Eden 會(huì)發(fā)生什么,后果就是每經(jīng)過(guò)一次 Minor GC,存活對(duì)象會(huì)過(guò)早地進(jìn)入老年代,那么老年代很快就會(huì)裝滿,很快會(huì)觸發(fā) Full GC,而對(duì)象其實(shí)在經(jīng)過(guò)兩三次的 Minor GC 后大部分都會(huì)消亡,所以有了 S0,S1的緩沖,只有少數(shù)的對(duì)象會(huì)進(jìn)入老年代,老年代大小也就不會(huì)這么快地增長(zhǎng),也就避免了過(guò)早地觸發(fā) Full GC。

由于 Full GC(或Minor GC) 會(huì)影響性能,所以我們要在一個(gè)合適的時(shí)間點(diǎn)發(fā)起 GC,這個(gè)時(shí)間點(diǎn)被稱為 Safe Point,這個(gè)時(shí)間點(diǎn)的選定既不能太少以讓 GC 時(shí)間太長(zhǎng)導(dǎo)致程序過(guò)長(zhǎng)時(shí)間卡頓,也不能過(guò)于頻繁以至于過(guò)分增大運(yùn)行時(shí)的負(fù)荷。一般當(dāng)線程在這個(gè)時(shí)間點(diǎn)上狀態(tài)是可以確定的,如確定 GC Root 的信息等,可以使 JVM 開(kāi)始安全地 GC。Safe Point 主要指的是以下特定位置:

循環(huán)的末尾

方法返回前

調(diào)用方法的 call 之后

拋出異常的位置 另外需要注意的是由于新生代的特點(diǎn)(大部分對(duì)象經(jīng)過(guò) Minor GC后會(huì)消亡), Minor GC 用的是復(fù)制算法,而在老生代由于對(duì)象比較多,占用的空間較大,使用復(fù)制算法會(huì)有較大開(kāi)銷(復(fù)制算法在對(duì)象存活率較高時(shí)要進(jìn)行多次復(fù)制操作,同時(shí)浪費(fèi)一半空間)所以根據(jù)老生代特點(diǎn),在老年代進(jìn)行的 GC 一般采用的是標(biāo)記整理法來(lái)進(jìn)行回收。

垃圾收集器種類

如果說(shuō)收集算法是內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)。Java 虛擬機(jī)規(guī)范并沒(méi)有規(guī)定垃圾收集器應(yīng)該如何實(shí)現(xiàn),因此一般來(lái)說(shuō)不同廠商,不同版本的虛擬機(jī)提供的垃圾收集器實(shí)現(xiàn)可能會(huì)有差別,一般會(huì)給出參數(shù)來(lái)讓用戶根據(jù)應(yīng)用的特點(diǎn)來(lái)組合各個(gè)年代使用的收集器,主要有以下垃圾收集器

 

 

 

 

在新生代工作的垃圾回收器:Serial, ParNew, ParallelScavenge

在老年代工作的垃圾回收器:CMS,Serial Old, Parallel Old

同時(shí)在新老生代工作的垃圾回收器:G1

圖片中的垃圾收集器如果存在連線,則代表它們之間可以配合使用,接下來(lái)我們來(lái)看看各個(gè)垃圾收集器的具體功能。

新生代收集器

Serial 收集器

Serial 收集器是工作在新生代的,單線程的垃圾收集器,單線程意味著它只會(huì)使用一個(gè) CPU 或一個(gè)收集線程來(lái)完成垃圾回收,不僅如此,還記得我們上文提到的 STW 了嗎,它在進(jìn)行垃圾收集時(shí),其他用戶線程會(huì)暫停,直到垃圾收集結(jié)束,也就是說(shuō)在 GC 期間,此時(shí)的應(yīng)用不可用。

看起來(lái)單線程垃圾收集器不太實(shí)用,不過(guò)我們需要知道的任何技術(shù)的使用都不能脫離場(chǎng)景,在Client 模式下,它簡(jiǎn)單有效(與其他收集器的單線程比),對(duì)于限定單個(gè) CPU 的環(huán)境來(lái)說(shuō),Serial 單線程模式無(wú)需與其他線程交互,減少了開(kāi)銷,專心做 GC 能將其單線程的優(yōu)勢(shì)發(fā)揮到極致,另外在用戶的桌面應(yīng)用場(chǎng)景,分配給虛擬機(jī)的內(nèi)存一般不會(huì)很大,收集幾十甚至一兩百兆(僅是新生代的內(nèi)存,桌面應(yīng)用基本不會(huì)再大了),STW 時(shí)間可以控制在一百多毫秒內(nèi),只要不是頻繁發(fā)生,這點(diǎn)停頓是可以接受的,所以對(duì)于運(yùn)行在 Client 模式下的虛擬機(jī),Serial 收集器是新生代的默認(rèn)收集器

ParNew 收集器

ParNew 收集器是 Serial 收集器的多線程版本,除了使用多線程,其他像收集算法,STW,對(duì)象分配規(guī)則,回收策略與 Serial 收集器完成一樣,在底層上,這兩種收集器也共用了相當(dāng)多的代碼,它的垃圾收集過(guò)程如下

 

 

 

ParNew 主要工作在 Server 模式,我們知道服務(wù)端如果接收的請(qǐng)求多了,響應(yīng)時(shí)間就很重要了,多線程可以讓垃圾回收得更快,也就是減少了 STW 時(shí)間,能提升響應(yīng)時(shí)間,所以是許多運(yùn)行在 Server 模式下的虛擬機(jī)的首選新生代收集器,另一個(gè)與性能無(wú)關(guān)的原因是因?yàn)槌?Serial 收集器,只有它能與 CMS 收集器配合工作,CMS 是一個(gè)劃時(shí)代的垃圾收集器,是真正意義上的并發(fā)收集器,它第一次實(shí)現(xiàn)了垃圾收集線程與用戶線程(基本上)同時(shí)工作,它采用的是傳統(tǒng)的 GC 收集器代碼框架,與 Serial,ParNew 共用一套代碼框架,所以能與這兩者一起配合工作,而后文提到的 Parallel Scavenge 與 G1 收集器沒(méi)有使用傳統(tǒng)的 GC 收集器代碼框架,而是另起爐灶獨(dú)立實(shí)現(xiàn)的,另外一些收集器則只是共用了部分的框架代碼,所以無(wú)法與 CMS 收集器一起配合工作。

在多 CPU 的情況下,由于 ParNew 的多線程回收特性,毫無(wú)疑問(wèn)垃圾收集會(huì)更快,也能有效地減少 STW 的時(shí)間,提升應(yīng)用的響應(yīng)速度。

Parallel Scavenge 收集器

Parallel Scavenge 收集器也是一個(gè)使用復(fù)制算法,多線程,工作于新生代的垃圾收集器,看起來(lái)功能和 ParNew 收集器一樣,它有啥特別之處嗎

關(guān)注點(diǎn)不同,CMS 等垃圾收集器關(guān)注的是盡可能縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而 Parallel Scavenge 目標(biāo)是達(dá)到一個(gè)可控制的吞吐量(吞吐量 = 運(yùn)行用戶代碼時(shí)間 / (運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間)),也就是說(shuō) CMS 等垃圾收集器更適合用到與用戶交互的程序,因?yàn)橥nD時(shí)間越短,用戶體驗(yàn)越好,而 Parallel Scavenge 收集器關(guān)注的是吞吐量,所以更適合做后臺(tái)運(yùn)算等不需要太多用戶交互的任務(wù)。

Parallel Scavenge 收集器提供了兩個(gè)參數(shù)來(lái)精確控制吞吐量,分別是控制最大垃圾收集時(shí)間的 -XX:MaxGCPauseMillis 參數(shù)及直接設(shè)置吞吐量大小的 -XX:GCTimeRatio(默認(rèn)99%)

除了以上兩個(gè)參數(shù),還可以用 Parallel Scavenge 收集器提供的第三個(gè)參數(shù) -XX:UseAdaptiveSizePolicy,開(kāi)啟這個(gè)參數(shù)后,就不需要手工指定新生代大小,Eden 與 Survivor 比例(SurvivorRatio)等細(xì)節(jié),只需要設(shè)置好基本的堆大小(-Xmx 設(shè)置最大堆),以及最大垃圾收集時(shí)間與吞吐量大小,虛擬機(jī)就會(huì)根據(jù)當(dāng)前系統(tǒng)運(yùn)行情況收集監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以盡可能地達(dá)到我們?cè)O(shè)定的最大垃圾收集時(shí)間或吞吐量大小這兩個(gè)指標(biāo)。自適應(yīng)策略也是 Parallel Scavenge 與 ParNew 的重要區(qū)別!

老年代收集器

Serial Old 收集器

上文我們知道, Serial 收集器是工作于新生代的單線程收集器,與之相對(duì)地,Serial Old 是工作于老年代的單線程收集器,此收集器的主要意義在于給 Client 模式下的虛擬機(jī)使用,如果在 Server 模式下,則它還有兩大用途:一種是在 JDK 1.5 及之前的版本中與 Parallel Scavenge 配合使用,另一種是作為 CMS 收集器的后備預(yù)案,在并發(fā)收集發(fā)生 Concurrent Mode Failure 時(shí)使用(后文講述),它與 Serial 收集器配合使用示意圖如下

 

 

 

 

Parallel Old 收集器

Parallel Old 是相對(duì)于 Parallel Scavenge 收集器的老年代版本,使用多線程和標(biāo)記整理法,兩者組合示意圖如下,這兩者的組合由于都是多線程收集器,真正實(shí)現(xiàn)了「吞吐量?jī)?yōu)先」的目標(biāo)

 

 

 

 

CMS 收集器

CMS 收集器是以實(shí)現(xiàn)最短 STW 時(shí)間為目標(biāo)的收集器,如果應(yīng)用很重視服務(wù)的響應(yīng)速度,希望給用戶最好的體驗(yàn),則 CMS 收集器是個(gè)很不錯(cuò)的選擇!

我們之前說(shuō)老年代主要用標(biāo)記整理法,而 CMS 雖然工作于老年代,但采用的是標(biāo)記清除法,主要有以下四個(gè)步驟

初始標(biāo)記

并發(fā)標(biāo)記

重新標(biāo)記

并發(fā)清除

 

 

 

 

從圖中可以的看到初始標(biāo)記和重新標(biāo)記兩個(gè)階段會(huì)發(fā)生 STW,造成用戶線程掛起,不過(guò)初始標(biāo)記僅標(biāo)記 GC Roots 能關(guān)聯(lián)的對(duì)象,速度很快,并發(fā)標(biāo)記是進(jìn)行 GC Roots Tracing 的過(guò)程,重新標(biāo)記是為了修正并發(fā)標(biāo)記期間因用戶線程繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,這一階段停頓時(shí)間一般比初始標(biāo)記階段稍長(zhǎng),但遠(yuǎn)比并發(fā)標(biāo)記時(shí)間短。

整個(gè)過(guò)程中耗時(shí)最長(zhǎng)的是并發(fā)標(biāo)記和標(biāo)記清理,不過(guò)這兩個(gè)階段用戶線程都可工作,所以不影響應(yīng)用的正常使用,所以總體上看,可以認(rèn)為 CMS 收集器的內(nèi)存回收過(guò)程是與用戶線程一起并發(fā)執(zhí)行的。

但是 CMS 收集器遠(yuǎn)達(dá)不到完美的程度,主要有以下三個(gè)缺點(diǎn)

CMS 收集器對(duì) CPU 資源非常敏感 原因也可以理解,比如本來(lái)我本來(lái)可以有 10 個(gè)用戶線程處理請(qǐng)求,現(xiàn)在卻要分出 3 個(gè)作為回收線程,吞吐量下降了30%,CMS 默認(rèn)啟動(dòng)的回收線程數(shù)是 (CPU數(shù)量+3)/ 4, 如果 CPU 數(shù)量只有一兩個(gè),那吞吐量就直接下降 50%,顯然是不可接受的

CMS 無(wú)法處理浮動(dòng)垃圾(Floating Garbage),可能出現(xiàn) 「Concurrent Mode Failure」而導(dǎo)致另一次 Full GC 的產(chǎn)生,由于在并發(fā)清理階段用戶線程還在運(yùn)行,所以清理的同時(shí)新的垃圾也在不斷出現(xiàn),這部分垃圾只能在下一次 GC 時(shí)再清理掉(即浮云垃圾),同時(shí)在垃圾收集階段用戶線程也要繼續(xù)運(yùn)行,就需要預(yù)留足夠多的空間要確保用戶線程正常執(zhí)行,這就意味著 CMS 收集器不能像其他收集器一樣等老年代滿了再使用,JDK 1.5 默認(rèn)當(dāng)老年代使用了68%空間后就會(huì)被激活,當(dāng)然這個(gè)比例可以通過(guò) -XX:CMSInitiatingOccupancyFraction 來(lái)設(shè)置,但是如果設(shè)置地太高很容易導(dǎo)致在 CMS 運(yùn)行期間預(yù)留的內(nèi)存無(wú)法滿足程序要求,會(huì)導(dǎo)致 Concurrent Mode Failure 失敗,這時(shí)會(huì)啟用 Serial Old 收集器來(lái)重新進(jìn)行老年代的收集,而我們知道 Serial Old 收集器是單線程收集器,這樣就會(huì)導(dǎo)致 STW 更長(zhǎng)了。

CMS 采用的是標(biāo)記清除法,上文我們已經(jīng)提到這種方法會(huì)產(chǎn)生大量的內(nèi)存碎片,這樣會(huì)給大內(nèi)存分配帶來(lái)很大的麻煩,如果無(wú)法找到足夠大的連續(xù)空間來(lái)分配對(duì)象,將會(huì)觸發(fā) Full GC,這會(huì)影響應(yīng)用的性能。當(dāng)然我們可以開(kāi)啟 -XX:+UseCMSCompactAtFullCollection(默認(rèn)是開(kāi)啟的),用于在 CMS 收集器頂不住要進(jìn)行 FullGC 時(shí)開(kāi)啟內(nèi)存碎片的合并整理過(guò)程,內(nèi)存整理會(huì)導(dǎo)致 STW,停頓時(shí)間會(huì)變長(zhǎng),還可以用另一個(gè)參數(shù) -XX:CMSFullGCsBeforeCompation 用來(lái)設(shè)置執(zhí)行多少次不壓縮的 Full GC 后跟著帶來(lái)一次帶壓縮的。

G1(Garbage First) 收集器

G1 收集器是面向服務(wù)端的垃圾收集器,被稱為駕馭一切的垃圾回收器,主要有以下幾個(gè)特點(diǎn)

像 CMS 收集器一樣,能與應(yīng)用程序線程并發(fā)執(zhí)行。

整理空閑空間更快。

需要 GC 停頓時(shí)間更好預(yù)測(cè)。

不會(huì)像 CMS 那樣犧牲大量的吞吐性能。

不需要更大的 Java Heap

與 CMS 相比,它在以下兩個(gè)方面表現(xiàn)更出色

運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存碎片,G1 從整體上看采用的是標(biāo)記-整理法,局部(兩個(gè) Region)上看是基于復(fù)制算法實(shí)現(xiàn)的,兩個(gè)算法都不會(huì)產(chǎn)生內(nèi)存碎片,收集后提供規(guī)整的可用內(nèi)存,這樣有利于程序的長(zhǎng)時(shí)間運(yùn)行。

在 STW 上建立了可預(yù)測(cè)的停頓時(shí)間模型,用戶可以指定期望停頓時(shí)間,G1 會(huì)將停頓時(shí)間控制在用戶設(shè)定的停頓時(shí)間以內(nèi)。

為什么G1能建立可預(yù)測(cè)的停頓模型呢,主要原因在于 G1 對(duì)堆空間的分配與傳統(tǒng)的垃圾收集器不一器,傳統(tǒng)的內(nèi)存分配就像我們前文所述,是連續(xù)的,分成新生代,老年代,新生代又分 Eden,S0,S1,如下

 

 

 

 

而 G1 各代的存儲(chǔ)地址不是連續(xù)的,每一代都使用了 n 個(gè)不連續(xù)的大小相同的 Region,每個(gè)Region占有一塊連續(xù)的虛擬內(nèi)存地址,如圖示

 

 

除了和傳統(tǒng)的新老生代,幸存區(qū)的空間區(qū)別,Region還多了一個(gè)H,它代表Humongous,這表示這些Region存儲(chǔ)的是巨大對(duì)象(humongous object,H-obj),即大小大于等于region一半的對(duì)象,這樣超大對(duì)象就直接分配到了老年代,防止了反復(fù)拷貝移動(dòng)。那么 G1 分配成這樣有啥好處呢?

 

 

傳統(tǒng)的收集器如果發(fā)生 Full GC 是對(duì)整個(gè)堆進(jìn)行全區(qū)域的垃圾收集,而分配成各個(gè) Region 的話,方便 G1 跟蹤各個(gè) Region 里垃圾堆積的價(jià)值大小(回收所獲得的空間大小及回收所需經(jīng)驗(yàn)值),這樣根據(jù)價(jià)值大小維護(hù)一個(gè)優(yōu)先列表,根據(jù)允許的收集時(shí)間,優(yōu)先收集回收價(jià)值最大的 Region,也就避免了整個(gè)老年代的回收,也就減少了 STW 造成的停頓時(shí)間。同時(shí)由于只收集部分 Region,可就做到了 STW 時(shí)間的可控。

G1 收集器的工作步驟如下

初始標(biāo)記

并發(fā)標(biāo)記

最終標(biāo)記

篩選回收

 

 

 

 

可以看到整體過(guò)程與 CMS 收集器非常類似,篩選階段會(huì)根據(jù)各個(gè) Region 的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶期望的 GC 停頓時(shí)間來(lái)制定回收計(jì)劃。

總結(jié)

本文簡(jiǎn)述了垃圾回收的原理與垃圾收集器的種類,相信大家對(duì)開(kāi)頭提的一些問(wèn)題應(yīng)該有了更深刻的認(rèn)識(shí),在生產(chǎn)環(huán)境中我們要根據(jù)不同的場(chǎng)景來(lái)選擇垃圾收集器組合,如果是運(yùn)行在桌面環(huán)境處于 Client 模式的,則用 Serial + Serial Old 收集器綽綽有余,如果需要響應(yīng)時(shí)間快,用戶體驗(yàn)好的,則用 ParNew + CMS 的搭配模式,即使是號(hào)稱是「駕馭一切」的 G1,也需要根據(jù)吞吐量等要求適當(dāng)調(diào)整相應(yīng)的 JVM 參數(shù),沒(méi)有最牛的技術(shù),只有最合適的使用場(chǎng)景,切記!

理論有了,下一篇我們會(huì)進(jìn)入手動(dòng)操作環(huán)節(jié),我們會(huì)一起來(lái)動(dòng)手操作一些 demo,做一些實(shí)驗(yàn),來(lái)驗(yàn)證我們看到的一些現(xiàn)象,比如對(duì)象一般分配在新生代,什么情況下會(huì)直接到老年代,該怎么實(shí)驗(yàn)?發(fā)生了OOM,該用哪些工具調(diào)試呢?等等,敬請(qǐng)期待!

責(zé)任編輯:武曉燕 來(lái)源: 碼海
相關(guān)推薦

2020-03-14 09:17:55

HTTPS網(wǎng)絡(luò)協(xié)議HTTP

2020-04-07 01:04:18

SessionCookieToken

2020-04-15 12:24:55

Exception Error Java

2020-11-02 08:12:52

finalJava開(kāi)發(fā)

2020-01-15 08:06:28

HTTP超文本傳輸協(xié)議網(wǎng)絡(luò)協(xié)議

2020-05-15 11:14:58

操作系統(tǒng)面試官運(yùn)行

2019-05-31 15:30:00

人工智能機(jī)器人互聯(lián)網(wǎng)

2021-12-02 18:20:25

算法垃圾回收

2020-12-10 08:43:17

垃圾回收JVM

2018-04-23 11:00:44

PythonRedisNoSQL

2021-05-08 07:53:33

面試線程池系統(tǒng)

2021-02-03 15:30:10

面試垃圾回收器前端

2018-04-27 14:46:07

面試簡(jiǎn)歷程序員

2019-10-10 11:20:22

MySQL索引數(shù)據(jù)庫(kù)

2021-04-30 00:00:50

Semaphore信號(hào)量面試官

2020-04-03 14:05:10

面試RedisJava

2009-06-25 17:48:24

Java垃圾回收

2019-04-15 14:40:46

消息隊(duì)列Java編程

2019-04-26 14:12:19

MySQL數(shù)據(jù)庫(kù)隔離級(jí)別

2020-04-20 08:35:48

HTTP HTTPS網(wǎng)絡(luò)協(xié)議
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

欧美一卡在线观看| 2020国产精品自拍| 欧美激情手机在线视频 | 曰本女人与公拘交酡| 高清久久一区| 欧美日韩精品在线观看| 亚洲一区在线免费| 丰满熟女一区二区三区| 久久亚洲精品伦理| 美女性感视频久久久| 亚洲一区二区观看| 日韩成人精品一区二区三区| 亚洲国产一区二区a毛片| 日本不卡一二三区| 成人h动漫精品一区二区无码| 亚洲在线日韩| 九九热这里只有精品免费看| 色婷婷av777| 亚洲91网站| 欧美中文一区二区三区| 久久人人爽人人爽人人av| 国产黄在线观看免费观看不卡| 久久福利资源站| 91产国在线观看动作片喷水| 男人在线观看视频| 国产一区二区三区四区五区| 日韩女优av电影在线观看| 北条麻妃视频在线| av不卡高清| 亚洲三级小视频| 日韩色妇久久av| 天堂在线中文资源| 成人精品免费网站| 97影院在线午夜| 亚洲在线精品视频| 日韩电影免费在线| 热99精品只有里视频精品| 国产真实乱人偷精品视频| 91精品国产91久久久久久密臀 | 97在线观看免费视频| 国产精品zjzjzj在线观看| 91麻豆精品国产| 最近中文字幕一区二区| 最新日韩精品| 亚洲成av人片在线| 日本一本中文字幕| 国产色婷婷在线| 夜夜精品视频一区二区 | 亚洲在线黄色| 国内精品久久影院| 国产无精乱码一区二区三区| 欧美精品综合| 欧美另类第一页| 强乱中文字幕av一区乱码| 欧美疯狂party性派对| 中文字幕视频一区二区在线有码 | 九九热精品国产| 久久人体av| 这里是久久伊人| 午夜av中文字幕| 精品国产一区二| 欧美一卡2卡3卡4卡| 久久综合桃花网| 日韩成人18| 欧美精品一区二区三区在线播放| 色悠悠在线视频| 国产精品白丝一区二区三区| 亚洲精品大尺度| 中文字幕av网址| gogogo高清在线观看一区二区| 亚洲午夜国产成人av电影男同| av在线网站观看| 欧美偷拍自拍| 久久视频在线直播| 精品午夜福利视频| 国产精品嫩草99av在线| 国产a∨精品一区二区三区不卡| 亚洲色成人www永久网站| 日韩精品一二三四| 国产在线精品自拍| 性欧美一区二区三区| www.日韩大片| 日韩中文字幕一区| a视频在线免费看| 五月天精品一区二区三区| 日韩avxxx| 精品九九久久| 精品国产免费人成在线观看| 深爱五月激情网| 亚洲午夜精品一区二区国产 | 欧美a级黄色大片| bl视频在线免费观看| 欧美日韩午夜激情| 国产成年人视频网站| 1313精品午夜理伦电影| 亚洲免费中文字幕| 日韩a级片在线观看 | 牛夜精品久久久久久久| 精品国产18久久久久久二百| 亚洲国产精品热久久| www.99热| 激情亚洲网站| 国产欧美精品在线播放| 特黄aaaaaaaaa真人毛片| 国产欧美一区二区在线| 日本福利视频网站| 美女一区网站| 欧美一区二区黄| 亚洲天堂岛国片| 伊人影院久久| 国产综合香蕉五月婷在线| 性xxxx18| 亚洲美女视频在线| 亚洲精品怡红院| 久久97久久97精品免视看秋霞| 在线视频欧美日韩精品| 国产一级生活片| 老司机午夜精品| 欧美一级日本a级v片| 青草av在线| 欧美二区在线观看| 一级黄色性视频| 在线观看亚洲| 91大片在线观看| 五月天婷婷在线视频| 欧美日韩国产综合新一区| 日本中文字幕在线不卡| 日本a级不卡| 日本在线观看天堂男亚洲| 亚洲美女福利视频| 综合久久久久久| 992kp快乐看片永久免费网址| 久久影院资源站| 色综合久久久888| 一区二区三区www污污污网站| 久久亚洲一区二区三区明星换脸 | 高h视频在线播放| 欧美浪妇xxxx高跟鞋交| 国产aⅴ激情无码久久久无码| 精品999日本| 99国精产品一二二线| 九色porny丨首页在线| 在线亚洲高清视频| 女~淫辱の触手3d动漫| 9国产精品视频| 国产精品日韩一区二区三区| 在线免费观看的av| 日韩欧美国产一区二区三区| 69夜色精品国产69乱| 蓝色福利精品导航| 亚洲国产日韩综合一区| 一呦二呦三呦精品国产| 伊人青青综合网站| 最近国语视频在线观看免费播放| 久久精品视频一区| 日韩亚洲在线视频| 美女精品一区最新中文字幕一区二区三区| 久久人91精品久久久久久不卡 | 日韩精品中文字幕在线观看| 日韩高清免费av| 91丨porny丨国产| 99蜜桃臀久久久欧美精品网站| 亚洲色图美女| 国内精品国产三级国产在线专| 国产毛片毛片毛片毛片| 亚洲激情成人在线| 人妻av一区二区| 免费视频一区| 亚洲精品一区二区三区蜜桃久| 福利视频一区| 欧美日韩国产二区| 天天干天天摸天天操| 精品日本高清在线播放| 亚洲国产日韩一区无码精品久久久| 视频一区欧美日韩| 亚洲高清在线观看一区| 国产日韩在线观看视频| 欧美日本高清视频| 日韩av免费观影| 91福利视频在线| 疯狂撞击丝袜人妻| 成人天堂资源www在线| 国产精品秘入口18禁麻豆免会员| 国产精品一区二区三区av麻 | 日韩精品中文字幕在线| 国产精品成人久久| 久久亚洲精精品中文字幕早川悠里| 日韩av资源在线| 国产精品福利在线观看播放| 高清不卡一区二区三区| 吞精囗交69激情欧美| 久久久精品在线观看| 风流少妇一区二区三区91| 懂色aⅴ精品一区二区三区蜜月| 黄色三级生活片| 国产成人午夜高潮毛片| 情侣黄网站免费看| 欧美成人有码| 日本一区二区三区www| 欧美日韩第一区日日骚| 亚洲欧洲www| 亚洲欧洲在线一区| 国产区一区二| 奇米4444一区二区三区| 免费网站成人| 亚洲国产一区自拍| 一本色道久久综合精品婷婷| 亚洲成人自拍网| 中文字幕伦理片| 国产91精品一区二区麻豆网站| 欧美性猛交久久久乱大交小说 | 91p九色成人| 欧美福利在线观看| аⅴ资源新版在线天堂| 亚洲精品按摩视频| 99久久精品日本一区二区免费| 欧美日韩中文在线| 精品99久久久久成人网站免费| 国产拍揄自揄精品视频麻豆| 尤物网站在线观看| 国产一区美女在线| 亚州精品一二三区| 首页欧美精品中文字幕| 韩日视频在线观看| 亚洲精品国产首次亮相| 日韩免费中文专区| 亚洲成在人线免费观看| 国产一区二区三区四区五区加勒比| 91精品国产一区二区在线观看 | 在线视频 91| 色婷婷精品大在线视频| 日韩熟女精品一区二区三区| 亚洲男女一区二区三区| 黄色片网站在线播放| 国产婷婷色一区二区三区在线| 亚洲色偷偷色噜噜狠狠99网| 国产精品一区二区久久不卡| 亚洲一区日韩精品| 日韩不卡一二三区| 国产无套内射久久久国产| 中文在线不卡| 国产深夜男女无套内射| 亚洲精品黄色| 免费超爽大片黄| 亚洲国产免费| av免费观看大全| av不卡在线| 日韩av在线第一页| 亚洲一区日韩在线| 国产精品动漫网站| 视频一区欧美日韩| 天堂一区在线观看| 九九国产精品视频| 亚洲日本黄色片| 国产激情视频一区二区三区欧美| 四虎成人在线播放| 国产精品一区二区视频| 欧美精品 - 色网| 国产精品 日产精品 欧美精品| 年下总裁被打光屁股sp| 成人中文字幕在线| 男人网站在线观看| 91麻豆国产在线观看| 少妇大叫太粗太大爽一区二区| 国产亚洲精久久久久久| 中国特黄一级片| 亚洲手机成人高清视频| 欧美日韩成人免费观看| 亚洲国产aⅴ成人精品无吗| 日韩av男人天堂| 色综合天天做天天爱| 中文字幕人妻色偷偷久久| 在线成人午夜影院| 黄片毛片在线看| 亚洲欧美日韩在线一区| 999国产在线视频| 久久久国产精品视频| av手机在线观看| 91av在线免费观看| 国产69精品久久| 成人av电影免费| 妖精视频一区二区三区| 亚洲一区3d动漫同人无遮挡 | 久久一区二区三区喷水| 欧美 亚洲 视频| 乱人伦精品视频在线观看| 亚欧激情乱码久久久久久久久| 国产成人综合亚洲网站| 玖玖爱在线观看| 一区二区三区影院| 91精品国产高清一区二区三密臀| 欧美日本视频在线| 手机看片福利永久| 日韩在线观看你懂的| 大黄网站在线观看| 国产精品在线看| 欧美三级午夜理伦三级在线观看 | 51调教丨国产调教视频| 国产精品国模大尺度视频| 黄色小视频在线免费看| 91久久精品一区二区| 国产黄频在线观看| 亚洲无限av看| 国产亚洲成av人片在线观看| 国产精品入口免费视频一| 国产精品毛片视频| 亚洲第一精品区| 午夜影院日韩| 日批视频免费看 | 欧美一区二区三区爽爽爽| 色综合久久中文字幕| 成人精品在线播放| 最好看的2019的中文字幕视频| h片在线观看下载| 成人有码视频在线播放| 久久不卡国产精品一区二区 | 日韩你懂的在线播放| 户外极限露出调教在线视频| 久久久亚洲影院| 国产一区二区三区黄网站| 午夜精品区一区二区三| 国产午夜精品一区二区三区欧美| 97免费公开视频| 最新国产精品久久精品| 最近中文字幕免费在线观看| 亚洲美女在线看| 美女网站在线看| 国产精品国色综合久久| 亚洲国产日韩欧美在线| 牛夜精品久久久久久久| 国产欧美日韩中文久久| 成人免费视频毛片| 日韩亚洲欧美中文三级| 国产在线观看91| 成人黄色大片在线免费观看| 成人毛片免费看| 熟妇人妻va精品中文字幕| 久久天堂av综合合色蜜桃网| 国产又大又黑又粗免费视频| 亚洲成人av片| 成年人视频免费在线播放| 高清不卡日本v二区在线| 国产精品久久| 农村末发育av片一区二区| 洋洋av久久久久久久一区| 国产视频www| 欧美国产日韩精品| 成人福利一区| 国产精品国产亚洲精品看不卡| 成人av资源在线| 国产欧美日韩另类| 日韩成人xxxx| 91久久国产综合久久91猫猫| 欧美成人一区二区在线| 久久青草久久| 成人性视频免费看| 欧美乱妇15p| 午夜伦理在线视频| 精品欧美日韩| 蜜桃视频一区| 亚洲a∨无码无在线观看| 8v天堂国产在线一区二区| av免费在线观看网址| 99一区二区| 国产日韩欧美一区二区三区在线观看| 国产艳俗歌舞表演hd| 欧美性受xxxx黑人xyx性爽| 3p在线观看| 成人一区二区在线| 国产精品美女久久久| 天天干天天舔天天操| 91精品在线免费| 成人国产电影在线观看| 欧美日韩天天操| 精品一区二区在线视频| 久久久久无码国产精品不卡| 日韩精品在线免费| 国内欧美日韩| 久草视频这里只有精品| 久久先锋资源网| 91国产免费视频| 久久久久久久久久亚洲| 欧美精品乱码| 欧美熟妇另类久久久久久多毛| 午夜精品国产更新| 91精品国产91久久久久游泳池| **亚洲第一综合导航网站| 亚洲少妇一区| 你懂得在线观看| 亚洲第一网站免费视频| av在线一区不卡| 男女激情免费视频| 日本一区二区免费在线观看视频| 99久久婷婷国产一区二区三区| 97国产精品久久| 国产精品久久占久久| 久久人人爽人人人人片| 欧美精品免费视频| 69久成人做爰电影| 久久精品xxx|