精品欧美一区二区三区在线观看 _久久久久国色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)

性xxxxxxxxx| 涩涩涩999| 国产一级久久久| 久久影视三级福利片| 日韩欧美a级成人黄色| 亚洲 国产 日韩 综合一区| 99国产精品99| 亚洲欧美清纯在线制服| 久久精品91久久香蕉加勒比| 亚洲少妇一区二区三区| 精品三区视频| 五月婷婷激情综合网| 夜夜爽99久久国产综合精品女不卡| 毛茸茸多毛bbb毛多视频| 影视一区二区三区| 一区二区久久久久| 亚洲精品无人区| 欧美一级在线免费观看| 国模娜娜一区二区三区| 秋霞成人午夜鲁丝一区二区三区| 无码国产精品久久一区免费| 日韩欧美一区二区三区在线观看| 99re热这里只有精品视频| 国产女人精品视频| www毛片com| 精品96久久久久久中文字幕无| 欧美va亚洲va| 国产精品一区二区小说| 成人免费看黄| 一区二区三区在线视频免费观看| 国产成人精品福利一区二区三区| 欧美成人手机视频| 日韩高清欧美| 日韩精品小视频| 麻豆短视频在线观看| 91精品国产一区二区在线观看| 亚洲欧美一区二区不卡| 亚洲精品久久区二区三区蜜桃臀| 97人妻精品一区二区三区软件 | 人人艹在线视频| 欧美激情网址| 欧美精品一区二区久久久| 不用播放器的免费av| 成人自拍视频网| 色先锋久久av资源部| 成人在线免费观看av| 91探花在线观看| 夜夜嗨av一区二区三区网页| 激情五月五月婷婷| 黄色在线免费| 亚洲色图.com| 成年人黄色在线观看| 日韩伦理在线观看| 国产精品免费网站在线观看| 亚洲高清视频在线观看| 999在线视频| 国产精品欧美一级免费| 少妇熟女一区二区| av在线免费网址| 一区二区三区欧美在线观看| 国产精品无码电影在线观看| 激情av在线| 亚洲国产精品精华液网站| 亚洲人成无码网站久久99热国产| www.亚洲.com| 国产精品久久久久久久久晋中| 国产一区二区自拍| 亚欧洲精品视频| 久久亚洲捆绑美女| 欧美日韩一区二区视频在线| 超碰97在线免费观看| 国产精品视频观看| 老汉色影院首页| 91视频欧美| 色屁屁一区二区| 国产色视频在线播放| 日韩视频在线直播| 亚洲国产小视频在线观看| 最近中文字幕免费视频| 97久久夜色精品国产| 欧美精品制服第一页| 国产精品99无码一区二区| 美女日韩在线中文字幕| 国产精品直播网红| 亚洲AV午夜精品| 久久久久久久av麻豆果冻| 亚洲免费视频一区| av2020不卡| 欧美性猛片aaaaaaa做受| 青青草原播放器| 日韩电影不卡一区| 日韩中文字幕在线视频| 豆国产97在线 | 亚洲| 久久亚洲综合| 亚洲va男人天堂| 日本高清视频www| 国产精品美女久久久久高潮| 欧美这里只有精品| 黑人一区二区三区| 精品国产乱码久久| 免费黄色在线网址| 国产一区二区高清| 亚洲综合中文字幕在线观看| 男男激情在线| 亚洲综合免费观看高清完整版在线| 国产成人免费高清视频| 9i看片成人免费高清| 日韩一区二区中文字幕| 精品无码国产污污污免费网站 | 性无码专区无码| 捆绑紧缚一区二区三区视频| 国内一区二区在线视频观看| 久草免费在线观看| 日本高清不卡在线观看| 久久久久亚洲av无码网站| 日韩欧美精品一区| 日韩av免费在线播放| 亚洲精品久久久蜜桃动漫| 国产精品蜜臀在线观看| av观看免费在线| 国产 日韩 欧美 综合 一区| 久久这里有精品视频| 免费一级a毛片| 久久久亚洲国产美女国产盗摄| 日本午夜精品电影| 成人女同在线观看| 日韩手机在线导航| 黄视频网站免费看| 老司机精品视频在线| 日韩亚洲视频在线| 国产精品粉嫩| 日韩精品小视频| 日本道在线观看| a级精品国产片在线观看| 超薄肉色丝袜足j调教99| 日本在线一区二区| 中文一区二区视频| 中文在线观看av| 欧美激情中文不卡| 亚洲成色www.777999| 国产精品探花在线观看| 青青久久av北条麻妃海外网| 日韩欧美亚洲系列| 欧美色播在线播放| 免费a在线观看播放| 在线午夜精品| 免费久久久一本精品久久区| 午夜久久中文| 亚洲欧美日韩高清| 樱花视频在线免费观看| 久久精品欧美日韩精品 | 亚洲制服少妇| 久久www免费人成精品| 人在线成免费视频| 亚洲老头同性xxxxx| 国产午夜麻豆影院在线观看| 久久精品亚洲麻豆av一区二区 | 国模套图日韩精品一区二区| 日韩激情av在线播放| 成人精品免费在线观看| 久久婷婷国产综合国色天香| 国产综合av在线| 国产一区二区三区91| 国产精品成人国产乱一区| 99re热久久这里只有精品34| 91麻豆精品国产91久久久更新时间| 精品人妻一区二区三区香蕉 | 欧美激情亚洲精品| 国产刺激高潮av| 大桥未久av一区二区三区| 国产精品无码在线| 青娱乐精品在线视频| 天天做天天爱天天高潮| www国产精品| 奇米4444一区二区三区| 9191在线观看| 精品少妇一区二区三区在线播放| 99久久久无码国产精品不卡| 国产在线日韩欧美| 国产二区视频在线| 精品国产美女| 1卡2卡3卡精品视频| 国产美女高潮在线观看| 一本色道久久88综合日韩精品| 日韩欧美激情视频| 欧美激情一区在线| 色黄视频免费看| 久久国产66| 黑人巨大国产9丨视频| 欧美人妖视频| 成人黄色av播放免费| 国产直播在线| 神马国产精品影院av| 日韩一级在线播放| 欧美男女性生活在线直播观看| 91狠狠综合久久久久久| 国产成人午夜视频| 成人性视频欧美一区二区三区| 综合干狼人综合首页| 成人午夜在线观看| 在线天堂资源| 欧美激情精品久久久久久变态| 99国产揄拍国产精品| 日韩欧美在线网址| 欧美三级免费看| 国产精品久久久爽爽爽麻豆色哟哟 | 欧美性猛交xxxx乱大交| 动漫性做爰视频| 国产丝袜美腿一区二区三区| 中文字幕欧美视频| 日韩av成人高清| 国产免费毛卡片| 午夜精品久久99蜜桃的功能介绍| 99在线观看视频| 久久青草免费| 日本午夜人人精品| 黄网在线免费看| 欧美成人午夜影院| 日韩在线观看www| 亚洲天堂开心观看| 蜜桃久久一区二区三区| 日韩一区二区三区电影 | 99亚洲一区二区| 肉大捧一出免费观看网站在线播放| 一区中文字幕电影| 国产精品香蕉在线观看| 欧美××××黑人××性爽| 2019最新中文字幕| 九九精品调教| 欧美高清在线观看| 菠萝菠萝蜜在线视频免费观看| 亚洲福利在线观看| 亚洲AV无码成人片在线观看| 日韩午夜在线影院| 91成人国产综合久久精品| 欧美最新大片在线看| 无码人妻精品一区二区50| 精品免费在线观看| 日韩精品一区二区在线播放| 亚洲午夜久久久久中文字幕久| 国产精品一区二区入口九绯色| 日韩精品乱码免费| 欧美韩国日本在线| 老司机一区二区三区| 国产福利视频在线播放| 美女日韩在线中文字幕| 丰满少妇在线观看| 久久精品国产久精国产爱| 亚洲一区日韩精品| 国产乱淫av一区二区三区| 91亚洲一区二区| 国产999精品久久久久久绿帽| 日韩免费高清在线| 久久香蕉精品| 国产免费又粗又猛又爽| 久久精品国产久精国产| a级大片免费看| 不卡视频在线观看| 加勒比一区二区| 国产亚洲欧美在线| 欧美精品日韩在线| 亚洲精品乱码久久久久久| 国产精品999久久久| 色菇凉天天综合网| 11024精品一区二区三区日韩| 欧美午夜片在线免费观看| 激情五月色婷婷| 欧美调教femdomvk| 国产三级自拍视频| 亚洲国内精品视频| 国产鲁鲁视频在线观看免费| www.欧美免费| wwwwxxxx在线观看| 国产成人久久久| 成人在线精品| 美乳视频一区二区| 欧美xxxx中国| 国产h视频在线播放| 男男视频亚洲欧美| 激情av中文字幕| 国产欧美精品一区| 久久久久黄色片| 欧洲精品一区二区| 亚洲高清精品视频| 亚洲视频在线免费观看| 中文在线观看免费| 青青a在线精品免费观看| 国产日韩一区二区三免费高清| 国产精品精品视频| 欧美一区一区| 欧美日韩国产精品一区二区| 亚洲国产不卡| 18禁男女爽爽爽午夜网站免费| 亚洲人人精品| 五月婷婷丁香色| 99精品1区2区| 1024手机在线视频| 在线观看国产日韩| 黄色av网址在线| 久久久精品久久| 欧美电影网站| 国产精品麻豆免费版| 精品盗摄女厕tp美女嘘嘘| 免费的av在线| 日韩成人午夜电影| 日韩aaaaa| 亚洲人成网站精品片在线观看| 欧美激情精品久久| 欧美主播一区二区三区| 亚洲第一精品网站| 啊v视频在线一区二区三区| 成人性生活av| 国产精品一区二区欧美| 国产精品国产一区| 色婷婷狠狠18| 久久伊人蜜桃av一区二区| 国语对白在线播放| 欧美日韩精品欧美日韩精品一| ,一级淫片a看免费| 亚洲视频在线视频| 国产免费拔擦拔擦8x高清在线人| 日韩av大片在线| 盗摄牛牛av影视一区二区| 一本久久a久久精品vr综合| 久久深夜福利| 玖草视频在线观看| 亚洲成a人在线观看| 国产视频在线观看免费| 日韩在线视频观看| 91成人在线| 天堂社区 天堂综合网 天堂资源最新版| 色综合天天爱| 东京热加勒比无码少妇| www..com久久爱| 久久久久无码国产精品| 91精品国产色综合久久ai换脸 | 99久精品国产| 久草视频中文在线| 日韩一级成人av| 成人av免费| 亚洲v日韩v综合v精品v| 99久久影视| 在线观看国产福利| 亚洲三级在线免费观看| 国产成人a人亚洲精品无码| 久久成人在线视频| 欧美大片91| 99久久99久久精品| 国产精品一区二区三区99| 国产97免费视频| 欧美一区二区三区人| 直接在线观看的三级网址| 91在线观看欧美日韩| 亚洲字幕久久| 波多野结衣办公室双飞| 亚洲二区在线视频| 欧洲亚洲在线| 国产精品www网站| 色综合久久一区二区三区| 欧洲美女亚洲激情| 一个色综合av| 欧美新色视频| 国产精品久久久久久久天堂 | 婷婷四月色综合| 免费欧美日韩国产三级电影| 天天操夜夜操av| 欧美成人r级一区二区三区| 成人三级小说| 欧美日韩国产免费一区二区三区 | 欧美福利小视频| 国产日韩三级| 成人久久久久久久久| 亚洲国产精品v| aa视频在线免费观看| 久久全国免费视频| 你微笑时很美电视剧整集高清不卡| av网站手机在线观看| 久久综合给合久久狠狠狠97色69| 久草网视频在线观看| 日韩电影免费观看在线观看| 欧美大片免费高清观看| 中文字幕一区二区三区四区五区| 日韩av网站在线观看| 中文字幕影音先锋| 日韩电影中文字幕在线| 久久精品黄色| www.男人天堂网| 国产亚洲综合在线| av中文在线观看| 日本精品视频在线观看| 中文字幕一区二区三三| 91精品人妻一区二区三区蜜桃欧美| 婷婷久久综合九色国产成人| 大地资源中文在线观看免费版 | 成人污污视频| 久久久亚洲精品无码| 中文字幕一区二区三区在线观看| 中文字幕人妻色偷偷久久| 欧美激情免费观看| 欧美一区二区三区激情视频|