日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一文搞定 JVM 面试,教你吊打面试官~

發(fā)布時(shí)間:2024/3/13 编程问答 70 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一文搞定 JVM 面试,教你吊打面试官~ 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1.什么是類加載?類加載的過程?

類的加載指的是將類的class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個(gè)此類的對(duì)象,通過這個(gè)對(duì)象可以訪問到方法區(qū)對(duì)應(yīng)的類信息。

加載

  • 通過類的全限定名獲取定義此類的二進(jìn)制字節(jié)流

  • 將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)

  • 使用到類時(shí)才會(huì)加載,例如調(diào)用類的main( )方法,new對(duì)象等等,在加載階段會(huì)在內(nèi)存中生成一個(gè)代表該類的Class對(duì)象,作為方法區(qū)類信息的訪問入口

  • 驗(yàn)證

  • 校驗(yàn)字節(jié)碼文件的正確性
  • 準(zhǔn)備

  • 給類的靜態(tài)變量分配內(nèi)存,并賦予默認(rèn)值
  • 解析

  • 虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用。
  • 符號(hào)引用用于描述目標(biāo),直接引用直接指向目標(biāo)的地址。
  • 注意:這里的符號(hào)引用是指那些在編譯期間就能夠確定下來的數(shù)據(jù),包括靜態(tài)屬性、常量、私有屬性等等,因?yàn)檫@些數(shù)據(jù)不會(huì)被繼承或者被重寫,所以它們適合在類加載階段進(jìn)行解析,這就是所謂的靜態(tài)鏈接過程(類加載期間完成)。

    而那些在編譯期間無法確定下來的數(shù)據(jù),就只能等到運(yùn)行期間再將符號(hào)引用替換為直接引用,這也就是動(dòng)態(tài)鏈接的過程。

    初始化

  • 對(duì)類的靜態(tài)變量初始化為指定的值,執(zhí)行靜態(tài)代碼塊
  • 2.什么是類加載器,類加載器有哪些?

    實(shí)現(xiàn)通過類的全限定名獲取該類的二進(jìn)制字節(jié)流的代碼塊叫做類加載器。

    Java中有如下四種類加載器

  • 引導(dǎo)類加載器:負(fù)責(zé)加載支撐JVM運(yùn)行的位于JRE的lib目錄下的核心類庫,比如
    rt.jar、charsets.jar等,該加載器無法被Java程序直接引用。
  • 擴(kuò)展類加載器:負(fù)責(zé)加載支撐JVM運(yùn)行的位于JRE的lib目錄下的ext擴(kuò)展目錄中的JAR
    類包。
  • 系統(tǒng)類加載器:負(fù)責(zé)加載ClassPath路徑下的類包,主要就是加載你自己寫的那
    些類。
  • 自定義加載器:負(fù)責(zé)加載用戶自定義路徑下的類包。
  • 3.什么是雙親委派機(jī)制?

  • 如果一個(gè)類加載器收到了類加載請求,它并不會(huì)自己先去加載,而是把這個(gè)請求委托給父類的加載器去執(zhí)行。

  • 系統(tǒng)類加載器先看看自己是否已經(jīng)加載過當(dāng)前類(緩存),如果有就直接返回,如果沒有則委托給其父加載器—擴(kuò)展類加載器,擴(kuò)展類加載器先看看自己是否已經(jīng)加載過當(dāng)前類(緩存),如果有就直接返回,如果沒有則再委托給引導(dǎo)類加載器。。。
  • 注意:這里的父類加載器并不是說這兩個(gè)類是子父類關(guān)系(extends),而是說系統(tǒng)類加載器的parent屬性是擴(kuò)展類加載器,然后擴(kuò)展類加載器的parent屬性是null,源碼里面會(huì)判斷擴(kuò)展類加載器的parent為null然后進(jìn)入到尋找引導(dǎo)類加載器。
  • 如果父類加載器可以完成類加載任務(wù),就成功返回;倘若父類加載器無法完成此加載任務(wù),子類加載器才會(huì)嘗試自己去加載,這就是雙親委派機(jī)制。

  • 父類加載器一層一層往下分配任務(wù),如果子類加載器能加載,則加載此類;如果將加載任務(wù)分配到系統(tǒng)類加載器也無法加載此類,則拋出異常。

  • 為什么要設(shè)計(jì)雙親委派機(jī)制?

    沙箱安全機(jī)制

  • 自己寫的 java.lang.String.class 類不會(huì)被加載,這樣便可以防止核心API庫被隨意篡改
  • 盡管我們自己可以改寫成和JDK原生類路徑一樣的代碼,但是因?yàn)殡p親委派機(jī)制是往上走,到了引導(dǎo)類加載器,會(huì)自動(dòng)把對(duì)應(yīng)路徑的原生API加載到虛擬機(jī),這樣子就能避免黑客隨意篡改我們的核心類庫了。
  • 避免類的重復(fù)加載

  • 當(dāng)父加載器已經(jīng)加載了該類,就沒有必要讓子ClassLoader再加載一次,保證被加載類的唯一性
  • 全盤負(fù)責(zé)委托機(jī)制

    “全盤負(fù)責(zé)”是指當(dāng)一個(gè)ClassLoder裝載一個(gè)類時(shí),除非顯示的使用另外一個(gè)ClassLoder,否則該類
    所依賴及引用的類也由這個(gè)ClassLoder載入。

    4.如何自定義類加載器?

    我們來看下應(yīng)用程序類加載器AppClassLoader加載類的雙親委派機(jī)制源碼,AppClassLoader的loadClass方法最終會(huì)調(diào)用其父類ClassLoader的loadClass方法,該方法的大體邏輯如下:

  • 首先,檢查一下指定名稱的類是否已經(jīng)加載過,如果加載過了,就不需要再加載,直接返回。
  • 如果此類沒有加載過,那么再判斷一下是否有父加載器;如果有父加載器,則由父加載器加載(即調(diào)用parent.loadClass(name, false);)或者是調(diào)用bootstrap類加載器來加載。
  • 如果父加載器及bootstrap類加載器都沒有找到指定的類,那么調(diào)用當(dāng)前類加載器的findClass方法來完成類加載。
  • //ClassLoader的loadClass方法,里面實(shí)現(xiàn)了雙親委派機(jī)制 protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 檢查當(dāng)前類加載器是否已經(jīng)加載了該類Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) { //如果當(dāng)前類加載器的父加載器不為空則委托父加載器加載該類c = parent.loadClass(name, false);} else { //如果當(dāng)前類加載器的父加載器為空則委托引導(dǎo)類加載器加載該類c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();//都會(huì)調(diào)用URLClassLoader的findClass方法在加載器的類路徑里查找并加載該類c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) { //不會(huì)執(zhí)行resolveClass(c);}return c;} }

    自定義類加載器

    自定義類加載器只需要繼承 java.lang.ClassLoader 類,該類有兩個(gè)核心方法,一個(gè)是loadClass(String, boolean),實(shí)現(xiàn)了雙親委派機(jī)制,還有一個(gè)方法是findClass,默認(rèn)實(shí)現(xiàn)是空方法。

  • 我們一般自定義類加載器都是繼承ClassLoader,重寫findClass()方法,來實(shí)現(xiàn)類加載,這樣就不會(huì)違背雙親委派機(jī)制。
  • 也可以通過重寫loadClass()方法進(jìn)行類加載,但是這樣會(huì)違背雙親委派機(jī)制。
  • 5.Tomcat 打破雙親委派機(jī)制

    先思考一個(gè)問題,Tomcat是個(gè)web容器, 那么它要解決什么問題:

  • 一個(gè)web容器可能需要部署多個(gè)應(yīng)用程序,不同的應(yīng)用程序可能會(huì)依賴同一個(gè)第三方類庫的不同版本,不能要求同一個(gè)類庫在同一個(gè)服務(wù)器具有多份,因此要保證每個(gè)應(yīng)用程序的類庫都是獨(dú)立的,保證相互隔離。

  • 部署在同一個(gè)web容器中相同的類庫相同的版本可以共享。否則,如果服務(wù)器有10個(gè)應(yīng)用程序,那么要有10份相同的類庫加載進(jìn)虛擬機(jī)。

  • web容器也有自己依賴的類庫,不能與應(yīng)用程序的類庫混淆。基于安全考慮,應(yīng)該讓容器的類庫和程序的類庫隔離開來。

  • web容器要支持jsp的修改,我們知道,jsp文件最終也是要編譯成class文件才能在虛擬機(jī)中運(yùn)行,但程序運(yùn)行后修改jsp已經(jīng)是司空見慣的事情, web容器需要支持 jsp 修改后不用重啟。

  • 以 Tomcat 類加載為例,Tomcat 如果使用默認(rèn)的雙親委派類加載機(jī)制行不行?

    答案是不行的。為什么?

    第一個(gè)問題,如果使用默認(rèn)的類加載器機(jī)制,那么是無法加載兩個(gè)相同類庫的不同版本的,默認(rèn)的類加器是不管你是什么版本的,只在乎你的全限定類名,并且只有一份。

    第二個(gè)問題,默認(rèn)的類加載器是能夠?qū)崿F(xiàn)的,因?yàn)樗穆氊?zé)就是保證唯一性

    第三個(gè)問題和第一個(gè)問題一樣。

    我們再來看第四個(gè)問題,我們想要怎么實(shí)現(xiàn)jsp文件的熱加載,jsp 文件其實(shí)也就是class文件,那么如果修改了,但類名還是一樣,類加載器會(huì)直接取方法區(qū)中已經(jīng)存在的,修改后的jsp是不會(huì)重新加載的。

    那怎么辦呢?我們可以直接卸載掉這個(gè)jsp文件的類加載器,所以你應(yīng)該想到了,每個(gè)jsp文件對(duì)應(yīng)一個(gè)唯一的類加載器,當(dāng)一個(gè)jsp文件修改了,就直接卸載掉這個(gè)jsp類加載器。重新創(chuàng)建jsp類加載器,重新加載jsp文件。

    Tomcat自定義類加載器詳解

    tomcat的幾個(gè)主要類加載器:

    • commonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被Tomcat容器本身以及各個(gè)Webapp訪問;
    • catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對(duì)于Webapp不可見;
    • sharedLoader:各個(gè)Webapp共享的類加載器,加載路徑中的class對(duì)于所有Webapp可見,但是對(duì)于Tomcat容器不可見;
    • WebappClassLoader:各個(gè)Webapp私有的類加載器,加載路徑中的class只對(duì)當(dāng)前Webapp可見,比如加載war包里相關(guān)的類,每個(gè)war包應(yīng)用都有自己的WebappClassLoader,實(shí)現(xiàn)相互隔離,比如不同war包應(yīng)用引入了不同的spring版本,這樣實(shí)現(xiàn)就能加載各自的spring版本。

    從圖中的委派關(guān)系中可以看出:

    CommonClassLoader能加載的類都可以被CatalinaClassLoader和SharedClassLoader使用,從而實(shí)現(xiàn)了公有類庫的共用,而CatalinaClassLoader和SharedClassLoader自己能加載的類則與對(duì)方相互隔離。

    WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個(gè)WebAppClassLoader實(shí)例之間相互隔離。

    而JasperLoader的加載范圍僅僅是這個(gè)JSP文件所編譯出來的那一個(gè).Class文件,它出現(xiàn)的目的就是為了被丟棄:當(dāng)Web容器檢測到JSP文件被修改時(shí),會(huì)替換掉當(dāng)前的JasperLoader實(shí)例,并通過再創(chuàng)建一個(gè)新的JSP類加載器來實(shí)現(xiàn)JSP文件的熱加載功能。

    tomcat 這種類加載機(jī)制違背了Java 推薦的雙親委派模型了嗎?

    答案是:違背了。
    很顯然,tomcat 不是這樣實(shí)現(xiàn)的,tomcat 為了實(shí)現(xiàn)隔離性,沒有遵守這個(gè)約定,每個(gè)webappClassLoader 加載自己的目錄下的class文件,不會(huì)傳遞給父加載器,打破了雙親委派機(jī)制。

    Tomcat的JasperLoader熱加載

    后臺(tái)啟動(dòng)線程監(jiān)聽jsp文件變化,如果變化了找到該jsp對(duì)應(yīng)的servlet類的加載器引用(GCRoots),重新生成新的JasperLoader加載器并賦值給該引用;

    然后加載新的jsp對(duì)應(yīng)的servlet類,之前的那個(gè)加載器因?yàn)闆]有GCRoots引用了,下一次GC的時(shí)候會(huì)被銷毀。

    6.JVM內(nèi)存模型(運(yùn)行時(shí)數(shù)據(jù)區(qū))

    類加載后的大的Class對(duì)象是存放在堆中,math對(duì)象應(yīng)該是指向方法區(qū)中的類元信息,而類元信息再去指向堆中的大的Class對(duì)象

    public class Math {public static final int initData = 666;public static User user = new User();public int compute() {int a = 1;int b = 2;int c = (a + b) * 10;return c;}public static void main(String[] args) {Math math = new Math();math.compute();System.out.println("test");}}

    7.JVM內(nèi)存參數(shù)設(shè)置

    棧和方法區(qū)所占用的內(nèi)存空間都是堆外的本地內(nèi)存

    Spring Boot程序的JVM參數(shù)設(shè)置格式

    java -Xms2048M -Xmx2048M -Xmn1024M -Xss512K -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar microservice-eureka-server.jar

    -Xss:每個(gè)線程的棧大小

    -Xms:設(shè)置堆的初始可用大小,默認(rèn)物理內(nèi)存的1/64

    -Xmx:設(shè)置堆的最大可用大小,默認(rèn)物理內(nèi)存的1/4

    -Xmn:新生代大小

    -XX:NewRatio:默認(rèn)2表示新生代占老年代的1/2,占整個(gè)堆內(nèi)存的1/3。

    -XX:SurvivorRatio:默認(rèn)8表示一個(gè)survivor區(qū)占用1/8的Eden內(nèi)存,即1/10的新生代內(nèi)存。

    關(guān)于元空間的JVM參數(shù)有兩個(gè):-XX:MetaspaceSize=N-XX:MaxMetaspaceSize=N

    -XX:MaxMetaspaceSize: 設(shè)置元空間最大值, 默認(rèn)是-1, 即不受限制, 或者說只受限于本地內(nèi)存大小。

    -XX:MetaspaceSize: 指定元空間觸發(fā)Full GC的初始閾值(元空間無固定初始大小), 以字節(jié)為單位,默認(rèn)是21M左右,達(dá)到該值就會(huì)觸發(fā)Full GC進(jìn)行類型卸載, 同時(shí)收集器會(huì)對(duì)該值進(jìn)行調(diào)整:如果釋放了大量的空間, 就適當(dāng)降低該值; 如果釋放了很少的空間, 那么在不超過 -XX:MaxMetaspaceSize(如果設(shè)置了的話) 的情況下, 適當(dāng)提高該值。

    這個(gè)跟早期JDK版本的**-XX:PermSize**參數(shù)意思不一樣,-XX:PermSize代表永久代的初始容量。

    由于調(diào)整元空間的大小需要Full GC,這是非常昂貴的操作,如果應(yīng)用在啟動(dòng)的時(shí)候發(fā)生大量Full GC,通常都是由于永久代或元空間發(fā)生了大小調(diào)整,基于這種情況,一般建議在JVM參數(shù)中將MetaspaceSizeMaxMetaspaceSize設(shè)置成一樣的值,并設(shè)置得比初始值要大,對(duì)于8G物理內(nèi)存的機(jī)器來說,一般我會(huì)將這兩個(gè)值都設(shè)置為256M

    StackOverflowError示例:

    // JVM設(shè)置 -Xss128k(默認(rèn)1M) public class StackOverflowTest {static int count = 0;static void redo() {count++;redo();}public static void main(String[] args) {try {redo();} catch (Throwable t) {t.printStackTrace();System.out.println(count);}} }運(yùn)行結(jié)果: java.lang.StackOverflowErrorat com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:12)at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:13)at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:13)......

    注意:

    -Xss設(shè)置越小count值越小,說明一個(gè)線程棧里能分配的棧幀就越少,但是對(duì)JVM整體來說能開啟的線程數(shù)會(huì)更多。

    8.對(duì)象的創(chuàng)建

    對(duì)象創(chuàng)建的主要流程:

    類加載檢查

    虛擬機(jī)遇到一條new指令時(shí),首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載、解析和初始化過。如果沒有,那必須先執(zhí)行相應(yīng)的類加載過程。

    new指令對(duì)應(yīng)到語言層面上講是,new關(guān)鍵詞、對(duì)象克隆、反射、對(duì)象序列化等。

    分配內(nèi)存

    在類加載檢查通過后,接下來虛擬機(jī)將為新生對(duì)象分配內(nèi)存。對(duì)象所需內(nèi)存的大小在類加載完成后便可完全確定,為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來。

    這個(gè)步驟有兩個(gè)問題:

  • 如何劃分內(nèi)存。

  • 在并發(fā)情況下, 可能出現(xiàn)正在給對(duì)象A分配內(nèi)存,指針還沒來得及修改,對(duì)象B又同時(shí)使用了原來的指針來分配內(nèi)存的情況。

  • 劃分內(nèi)存的方法:

    • “指針碰撞”(Bump the Pointer,默認(rèn)用指針碰撞)

    如果Java堆中內(nèi)存是絕對(duì)規(guī)整的,所有用過的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,那所分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間那邊挪動(dòng)一段與對(duì)象大小相等的距離。

    • “空閑列表”(Free List,CMS收集器使用這種方式)

    如果Java堆中的內(nèi)存并不是規(guī)整的,已使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò),那就沒有辦法簡單地進(jìn)行指針碰撞了,虛擬機(jī)就必須維護(hù)一個(gè)列表,記錄上哪些內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例, 并更新列表上的記錄。

    解決并發(fā)問題的方法:

    • CAS(compare and swap)

    虛擬機(jī)采用CAS配上失敗重試的方式保證更新操作的原子性來對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理。

    • 本地線程分配緩沖(Thread Local Allocation Buffer,TLAB)

    把內(nèi)存分配的動(dòng)作按照線程劃分在不同的空間中進(jìn)行,即每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存。通過 -XX:+/-UseTLAB 參數(shù)來設(shè)定虛擬機(jī)是否使用 TLAB(JVM會(huì)默認(rèn)開啟 -XX:+UseTLAB),-XX:TLABSize:指定TLAB大小。

    初始化零值

    內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭), 如果使用TLAB,這一工作過程也可以提前至TLAB分配時(shí)進(jìn)行。

    這一步操作保證了對(duì)象的實(shí)例字段在Java代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。

    設(shè)置對(duì)象頭

    初始化零值之后,虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的GC分代年齡等信息。這些信息存放在對(duì)象的對(duì)象頭Object Header之中。

    在HotSpot虛擬機(jī)中,對(duì)象在內(nèi)存中存儲(chǔ)的布局可以分為3塊區(qū)域:對(duì)象頭(Header)、 實(shí)例數(shù)據(jù)(Instance Data)和對(duì)齊填充(Padding)。

    HotSpot虛擬機(jī)的對(duì)象頭包括兩部分信息,第一部分用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù), 如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程ID、偏向時(shí)間戳等。對(duì)象頭的另外一部分是類型指針,即對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。

    32位對(duì)象頭

    64位對(duì)象頭

    關(guān)于對(duì)其填充

    對(duì)于大部分處理器,對(duì)象以8字節(jié)整數(shù)倍來對(duì)齊填充都是最高效的存取方式。

    什么是指針壓縮?

  • jdk1.6 update14開始,在64bit操作系統(tǒng)中,JVM支持指針壓縮
  • jvm配置參數(shù):UseCompressedOops,compressed–壓縮、oop(ordinary object pointer)–對(duì)象指針
  • 啟用指針壓縮:-XX:+UseCompressedOops(默認(rèn)開啟),禁用指針壓縮:-XX:-UseCompressedOops
  • 為什么要進(jìn)行指針壓縮?

  • 在64位平臺(tái)的HotSpot中如果使用32位指針(實(shí)際存儲(chǔ)用64位),內(nèi)存使用會(huì)多出1.5倍左右;而如果使用較大指針在主內(nèi)存和緩存之間移動(dòng)數(shù)據(jù),會(huì)占用較大寬帶,同時(shí)GC也會(huì)承受較大壓力
  • 為了減少64位平臺(tái)下內(nèi)存的消耗,啟用指針壓縮功能
  • 在jvm中,32位地址最大支持4G內(nèi)存(2^32),可以通過對(duì)對(duì)象指針存入堆內(nèi)存時(shí)進(jìn)行壓縮編碼,取出到cpu寄存器后進(jìn)行解碼的方式來進(jìn)行優(yōu)化(對(duì)象指針在堆中是32位,在寄存器中是35位,2的35次方=32G),使得jvm只用32位地址就可以支持更大的內(nèi)存配置(小于等于32G)
  • 堆內(nèi)存小于4G時(shí),不需要啟用指針壓縮,jvm會(huì)直接去除高32位地址,即使用低虛擬地址空間
  • 堆內(nèi)存大于32G時(shí),指針壓縮會(huì)失效,會(huì)強(qiáng)制使用64位(即8字節(jié))來對(duì)java對(duì)象尋址,這就會(huì)出現(xiàn)1的問題,所以堆內(nèi)存不要大于32G為好
  • 執(zhí)行 init 方法

    執(zhí)行init方法,即對(duì)象按照程序員的意愿進(jìn)行初始化。對(duì)應(yīng)到語言層面上講,就是為屬性賦值(注意,這與上面的賦零值不同,這是由程序員賦的值)和執(zhí)行構(gòu)造方法。

    9.對(duì)象內(nèi)存分配

    對(duì)象內(nèi)存分配流程圖

    對(duì)象棧上分配

    我們通過JVM內(nèi)存分配可以知道JAVA中的對(duì)象都是在堆上進(jìn)行分配,當(dāng)對(duì)象沒有被引用的時(shí)候,需要依靠GC進(jìn)行內(nèi)存回收,如果對(duì)象數(shù)量較多的時(shí)候,會(huì)給GC帶來較大壓力,也間接影響了應(yīng)用的性能。

    為了減少臨時(shí)對(duì)象在堆內(nèi)分配的數(shù)量,JVM通過逃逸分析確定該對(duì)象不會(huì)被外部訪問。如果不會(huì)逃逸則可以將該對(duì)象在棧上分配內(nèi)存,這樣該對(duì)象所占用的內(nèi)存空間就可以隨棧幀出棧而銷毀,就減輕了垃圾回收的壓力。

    對(duì)象逃逸分析

    就是分析對(duì)象動(dòng)態(tài)作用域,當(dāng)一個(gè)對(duì)象在方法中被定義后,它可能被外部方法所引用,例如作為調(diào)用參數(shù)傳遞到其他地方中。

    public User test1() {User user = new User();user.setId(1);user.setName("zhuge");//TODO 保存到數(shù)據(jù)庫return user; }public void test2() {User user = new User();user.setId(1);user.setName("zhuge");//TODO 保存到數(shù)據(jù)庫 }

    很顯然test1方法中的user對(duì)象被返回了,這個(gè)對(duì)象的作用域范圍不確定,test2方法中的user對(duì)象我們可以確定當(dāng)方法結(jié)束時(shí)這個(gè)對(duì)象就是無效對(duì)象了,對(duì)于這樣的對(duì)象我們其實(shí)可以將其分配在棧內(nèi)存里,讓其在方法結(jié)束時(shí)跟隨棧內(nèi)存一起被回收掉。

    JVM對(duì)于這種情況可以通過開啟逃逸分析參數(shù)(-XX:+DoEscapeAnalysis)來優(yōu)化對(duì)象內(nèi)存分配位置,使其通過標(biāo)量替換優(yōu)先分配在棧上(棧上分配),JDK7之后默認(rèn)開啟逃逸分析,如果要關(guān)閉使用參數(shù)(-XX:-DoEscapeAnalysis

    標(biāo)量替換

    通過逃逸分析確定該對(duì)象不會(huì)被外部訪問,并且對(duì)象可以被進(jìn)一步分解時(shí),JVM不會(huì)創(chuàng)建該對(duì)象,而是將該對(duì)象的成員變量分解成若干個(gè)被這個(gè)方法使用的成員變量所代替,這些代替的成員變量就在棧幀或寄存器上分配空間。開啟標(biāo)量替換參數(shù)(-XX:+EliminateAllocations),JDK7之后默認(rèn)開啟

    標(biāo)量與聚合量

    標(biāo)量即不可被進(jìn)一步分解的量,而JAVA的基本數(shù)據(jù)類型就是標(biāo)量(如:int、long等基本數(shù)據(jù)類型以及reference類型等),標(biāo)量的對(duì)立就是可以被進(jìn)一步分解的量,而這種量稱之為聚合量。而在JAVA中對(duì)象就是可以被進(jìn)一步分解的聚合量。

    棧上分配示例:

    /*** 棧上分配,標(biāo)量替換* 代碼調(diào)用了1億次alloc(),如果是分配到堆上,大概需要1GB以上的堆空間,如果堆空間小于該值,必然會(huì)觸發(fā)GC。* * 使用如下參數(shù)不會(huì)發(fā)生GC* -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations* 使用如下參數(shù)都會(huì)發(fā)生大量GC* -Xmx15m -Xms15m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations* -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations* +DoEscapeAnalysis:開啟逃逸分析,-XX:+EliminateAllocations:開啟標(biāo)量替換*/ public class AllotOnStack {public static void main(String[] args) {long start = System.currentTimeMillis();for (int i = 0; i < 100000000; i++) {alloc();}long end = System.currentTimeMillis();System.out.println(end - start);}private static void alloc() {User user = new User();user.setId(1);user.setName("zhuge");} }

    結(jié)論:棧上分配依賴于逃逸分析和標(biāo)量替換

    10.對(duì)象在Eden區(qū)分配

    大多數(shù)情況下,對(duì)象在新生代中的 Eden 區(qū)分配。當(dāng) Eden 區(qū)沒有足夠空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次 Minor GC。

    Minor GC 和 Full GC

    • Minor GC/Young GC:指發(fā)生新生代的的垃圾收集動(dòng)作,Minor GC非常頻繁,回收速度一般也比較快。
    • Major GC/Full GC:一般會(huì)回收老年代 、年輕代、方法區(qū)的垃圾,Major GC的速度一般會(huì)比Minor GC 慢10倍以上。

    Eden : s0 : s1 = 8 : 1 : 1

  • 大量的對(duì)象被分配在Eden區(qū),Eden區(qū)滿了后會(huì)觸發(fā)Minor GC,可能會(huì)有99%以上的對(duì)象成為垃圾被回收掉,剩余存活的對(duì)象會(huì)被挪到為空的那塊survivor區(qū);
  • 下一次eden區(qū)滿了后又會(huì)觸發(fā)minor gc,把eden區(qū)和survivor區(qū)的垃圾對(duì)象回收,把剩余存活的對(duì)象一次性挪動(dòng)到另外一塊為空的survivor區(qū),因?yàn)樾律膶?duì)象都是朝生夕死的,存活時(shí)間很短,所以JVM默認(rèn)的 8:1:1 的比例是很合適的,讓eden區(qū)盡量的大,survivor區(qū)夠用即可
  • JVM 默認(rèn)有這個(gè)參數(shù) -XX:+UseAdaptiveSizePolicy(默認(rèn)開啟),會(huì)導(dǎo)致這個(gè) 8:1:1 的比例自動(dòng)變化,如果不想讓這個(gè)比例有變化可以設(shè)置參數(shù) -XX:-UseAdaptiveSizePolicy
  • 大對(duì)象直接進(jìn)入老年代

    大對(duì)象就是需要大量連續(xù)內(nèi)存空間的對(duì)象(比如:字符串、數(shù)組)。

    當(dāng)Eden區(qū)的空間填滿時(shí),程序又需要?jiǎng)?chuàng)建新對(duì)象,JVM的垃圾收集器將對(duì)Eden區(qū)進(jìn)行垃圾回收(Minor GC / Young GC),將Eden區(qū)中的不再被其他對(duì)象所引用的對(duì)象進(jìn)行銷毀。再加載新的對(duì)象放到Eden區(qū)。如果觸發(fā)MinorGC后對(duì)象還是無法放在Eden區(qū),說明是超大對(duì)象,則直接將對(duì)象放到老年代。

    JVM參數(shù) -XX:PretenureSizeThreshold 可以設(shè)置大對(duì)象的大小,如果對(duì)象超過設(shè)置大小會(huì)直接進(jìn)入老年代,不會(huì)進(jìn)入年輕代,這個(gè)參數(shù)只在 Serial 和 ParNew 兩個(gè)收集器下有效。

    比如設(shè)置JVM參數(shù):-XX:PretenureSizeThreshold=1000000(單位是字節(jié),只有Serial和ParNew的收集器有效)

    為什么要這樣設(shè)計(jì)呢?

    為了避免為大對(duì)象分配內(nèi)存時(shí)的復(fù)制操作而降低效率。

    長期存活的對(duì)象將進(jìn)入老年代

    既然虛擬機(jī)采用了分代收集的思想來管理內(nèi)存,那么內(nèi)存回收時(shí)就必須能識(shí)別哪些對(duì)象應(yīng)放在新生代,哪些對(duì)象應(yīng)放在老年代中。為了做到這一點(diǎn),虛擬機(jī)給每個(gè)對(duì)象設(shè)置一個(gè)對(duì)象年齡(Age)計(jì)數(shù)器。

    如果對(duì)象在 Eden 出生并經(jīng)過第一次 Minor GC 后仍然能夠存活,并且能被 Survivor 容納的話,將被移動(dòng)到 Survivor 空間中,并將對(duì)象年齡設(shè)為1。

    對(duì)象在 Survivor 中每熬過一次 MinorGC,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲,CMS收集器默認(rèn)6歲,不同的垃圾收集器會(huì)略微有點(diǎn)不同),就會(huì)被晉升到老年代中。

    對(duì)象晉升到老年代的年齡閾值,可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)置。

    對(duì)象動(dòng)態(tài)年齡判斷

    當(dāng)前存放對(duì)象的Survivor區(qū)域里(其中一塊區(qū)域,放對(duì)象的那塊s區(qū)),一批對(duì)象的總大小大于這塊Survivor區(qū)域內(nèi)存大小的50%(-XX:TargetSurvivorRatio可以指定),那么此時(shí)大于等于這批對(duì)象年齡最大值的對(duì)象,就可以直接進(jìn)入老年代了;

    例如Survivor區(qū)域里現(xiàn)在有一批對(duì)象,年齡1+年齡2+年齡n 的多個(gè)年齡對(duì)象總和超過了Survivor區(qū)域的50%,此時(shí)就會(huì)把年齡n(含)以上的對(duì)象都放入老年代。

    這個(gè)規(guī)則其實(shí)是希望那些可能是長期存活的對(duì)象,盡早進(jìn)入老年代。對(duì)象動(dòng)態(tài)年齡判斷機(jī)制一般是在 Minor GC 之后觸發(fā)的。

    老年代空間分配擔(dān)保機(jī)制

    年輕代每次minor gc之前JVM都會(huì)計(jì)算下老年代剩余可用空間

    如果這個(gè)可用空間小于年輕代里現(xiàn)有的所有對(duì)象大小之和(包括垃圾對(duì)象

    就會(huì)看一個(gè) “-XX:-HandlePromotionFailure”(jdk1.8默認(rèn)就設(shè)置了)的參數(shù)是否設(shè)置了

    如果有這個(gè)參數(shù),就會(huì)看看老年代的可用內(nèi)存大小,是否大于之前每一次 minor gc 后進(jìn)入老年代的對(duì)象的平均大小

    如果上一步結(jié)果是小于或者前面說的參數(shù)沒有設(shè)置,那么就會(huì)觸發(fā)一次 Full gc,對(duì)老年代和年輕代一起回收一次垃圾,如果回收完還是沒有足夠空間存放新的對(duì)象就會(huì)發(fā)生 “OOM

    當(dāng)然,如果minor gc之后剩余存活的需要挪動(dòng)到老年代的對(duì)象大小還是大于老年代可用空間,那么也會(huì)觸發(fā)Full gc,Full gc 完之后如果還是沒有空間放minor gc之后的存活對(duì)象,則也會(huì)發(fā)生“OOM”。

    11.對(duì)象內(nèi)存回收

    堆中幾乎存放著所有的對(duì)象實(shí)例,對(duì)堆進(jìn)行垃圾回收前的第一步就是要判斷哪些對(duì)象已經(jīng)死亡(即不能再被任何途徑使用的對(duì)象)。

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

    給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它,計(jì)數(shù)器就加1;

    當(dāng)引用失效時(shí),計(jì)數(shù)器就會(huì)減1,任何時(shí)候計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的對(duì)象。

    這個(gè)方法實(shí)現(xiàn)簡單,效率高,但是目前主流的虛擬機(jī)中并沒有選擇這個(gè)算法來管理內(nèi)存,其最主要的原因是它很難解決對(duì)象之間相互循環(huán)引用的問題。

    所謂對(duì)象之間的相互循環(huán)引用問題,如下面代碼所示:除了對(duì)象objA 和 objB 相互引用著對(duì)方之外,這兩個(gè)對(duì)象之間再無任何引用。但是因?yàn)樗麄兓ハ嘁弥鴮?duì)方,導(dǎo)致它們的引用計(jì)數(shù)器都不為0,于是引用計(jì)數(shù)算法無法通知 GC 回收器回收他們。

    public class ReferenceCountingGc {Object instance = null;public static void main(String[] args) {ReferenceCountingGc objA = new ReferenceCountingGc();ReferenceCountingGc objB = new ReferenceCountingGc();objA.instance = objB;objB.instance = objA;objA = null;objB = null;} }

    可達(dá)性分析算法

    “GC Roots” 對(duì)象作為起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索引用到的對(duì)象,找到的對(duì)象都標(biāo)記為非垃圾對(duì)象,其余未標(biāo)記的對(duì)象都是垃圾對(duì)象。

    **GC Roots **根節(jié)點(diǎn)有哪些?

  • 局部變量
  • 靜態(tài)變量
  • finalize( ) 方法最終判定對(duì)象是否存活

    即使在可達(dá)性分析算法中不可達(dá)的對(duì)象,也并非是“非死不可”的,這時(shí)候它們暫時(shí)處于“緩刑”階段,要真正宣告一個(gè)對(duì)象死亡,至少要經(jīng)歷再次標(biāo)記過程。

    標(biāo)記的前提是對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈。

    第一次標(biāo)記并進(jìn)行一次篩選

    篩選的條件是此對(duì)象是否有必要執(zhí)行finalize( )方法。當(dāng)對(duì)象沒有重寫finalize( )方法時(shí),對(duì)象將直接被回收。

    第二次標(biāo)記

    如果這個(gè)對(duì)象覆蓋了finalize方法,finalize方法是對(duì)象脫逃死亡命運(yùn)的最后一次機(jī)會(huì),如果對(duì)象要在finalize( )中成功拯救自己,只要重新與引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián)即可,

    譬如把自己賦值給某個(gè)類變量或?qū)ο蟮某蓡T變量,那在第二次標(biāo)記時(shí)它將移除出“即將回收”的集合。如果對(duì)象這時(shí)候還沒逃脫,那基本上它就真的被回收了。

    注意:一個(gè)對(duì)象的finalize( )方法只會(huì)被執(zhí)行一次,也就是說通過調(diào)用finalize方法自我救命的機(jī)會(huì)只有一次。

    12.常見引用類型

    Java 的引用類型一般分為四種:強(qiáng)引用軟引用、弱引用、虛引用。

    強(qiáng)引用

    指在程序代碼中普遍存在的引用賦值,類似 “Object obj = new Object( )” 這種引用關(guān)系;如果內(nèi)存空間不足了,GC 寧愿拋出 OutOfMemoryError,也不會(huì)回收具有強(qiáng)引用的對(duì)象。

    軟引用

    用來描述一些還有用,但非必需的對(duì)象,例如緩存數(shù)據(jù)。當(dāng)內(nèi)存足夠時(shí),不會(huì)回收軟引用的可達(dá)對(duì)象;當(dāng)內(nèi)存不夠時(shí),才會(huì)回收軟引用的可達(dá)對(duì)象。

    SoftReference<User> user = new SoftReference<User>(new User());

    軟引用在實(shí)際中有重要的應(yīng)用,例如瀏覽器的后退按鈕。按后退時(shí),這個(gè)后退顯示的網(wǎng)頁內(nèi)容是重新進(jìn)行請求還是從緩存中取出呢?

    我們就可以用到軟引用,將后退顯示的網(wǎng)頁內(nèi)容緩存起來,后面需要點(diǎn)擊后退按鈕時(shí)就無需重新進(jìn)行請求可以快速響應(yīng)。當(dāng)內(nèi)存空間不足時(shí),就會(huì)回收掉這些軟引用的對(duì)象。

    因?yàn)檫@些緩存數(shù)據(jù)是可有可無的,有的話更好,沒有的話也不影響。

    弱引用

    用來描述那些非必需的對(duì)象,如果一個(gè)對(duì)象只具有弱引用,不管內(nèi)存空間是否充足,都會(huì)在下一次 GC 時(shí)被回收。

    WeakReference<User> user = new WeakReference<User>(new User());

    虛引用

    如果一個(gè)對(duì)象只具有虛引用,那么它就和沒有任何引用一樣,任何時(shí)候都可能被 GC 回收。

    13.如何判斷一個(gè)類是無用的類

    方法區(qū)主要回收的是無用的類,那么如何判斷一個(gè)類是無用的類呢?

    類需要同時(shí)滿足下面3個(gè)條件才能算是 “無用的類”

    • 該類所有的對(duì)象實(shí)例都已經(jīng)被回收,也就是 Java 堆中不存在該類的任何實(shí)例。
    • 加載該類的 ClassLoader 已經(jīng)被回收(自定義類加載器可以被回收,比如jsp類加載器)。
    • 該類對(duì)應(yīng)的 java.lang.Class 對(duì)象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

    這三個(gè)條件是非常苛刻的,所以當(dāng)我們做完Full GC后,元空間是釋放不出什么空間的,因?yàn)闆]有太多的類是能被回收的。當(dāng)然也有特殊的情況,就是tomcat自定義的類加載器,jsp類加載器這些就可以被回收。

    14.垃圾收集算法

    分代收集理論

    當(dāng)前虛擬機(jī)的垃圾收集都采用分代收集算法,根據(jù)對(duì)象存活周期的不同將內(nèi)存分為幾塊。一般將java堆分為新生代和老年代,這樣我們就可以根據(jù)各個(gè)年代的特點(diǎn)選擇合適的垃圾收集算法。

  • 比如在新生代中,每次收集都會(huì)有大量對(duì)象(近99%)死去,所以可以選擇復(fù)制算法,只需要付出少量對(duì)象的復(fù)制成本就可以完成每次垃圾收集。

  • 而老年代的對(duì)象存活幾率是比較高的,而且沒有額外的空間對(duì)它進(jìn)行分配擔(dān)保,所以我們必須選擇“標(biāo)記-清除”或“標(biāo)記-整理”算法進(jìn)行垃圾收集。

  • 注意,“標(biāo)記-清除”或“標(biāo)記-整理”算法會(huì)比復(fù)制算法慢10倍以上。

  • 復(fù)制算法

    為了解決效率問題,“復(fù)制算法”出現(xiàn)了。它可以將內(nèi)存分為大小相同的兩塊,每次使用其中的一塊。

    當(dāng)這一塊的內(nèi)存使用完后,就將還存活的對(duì)象復(fù)制到另一塊去,然后再把使用的空間一次性清理掉。這樣就使得每次的內(nèi)存回收都是對(duì)內(nèi)存區(qū)間的一半進(jìn)行回收。

    在Minor GC過程中對(duì)象被挪動(dòng)后,引用如何修改?

    對(duì)象在堆內(nèi)部挪動(dòng)的過程其實(shí)是復(fù)制,原有區(qū)域?qū)ο筮€在,一般不直接清理,JVM內(nèi)部清理過程只是將對(duì)象分配指針移動(dòng)到原有區(qū)域的頭位置即可。

    比如掃描S0區(qū)域,掃到GC Roots引用的非垃圾對(duì)象,是將這些對(duì)象復(fù)制到S1區(qū)或老年代,最后掃描完了再將S0區(qū)域的對(duì)象分配指針移動(dòng)到S0區(qū)域的起始位置即可,S0區(qū)域之前的對(duì)象并不直接清理,當(dāng)有新對(duì)象分配了,原有區(qū)域里的對(duì)象也就被覆蓋(清除)了。

    Minor GC在根掃描過程中會(huì)記錄所有被掃描到的對(duì)象引用(在年輕代這些引用很少,因?yàn)榇蟛糠侄际抢鴮?duì)象不會(huì)被掃描到),如果引用的對(duì)象被復(fù)制到新地址了,最后會(huì)一并更新引用指向新地址。

    標(biāo)記-清除算法

    算法分為“標(biāo)記”和“清除”兩個(gè)階段:

    標(biāo)記存活的對(duì)象, 統(tǒng)一回收所有未被標(biāo)記的對(duì)象(一般選擇這種),

    也可以反過來,標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象 。它是最基礎(chǔ)的收集算法,比較簡單,但是會(huì)帶來兩個(gè)明顯的問題:

  • 效率問題(如果需要標(biāo)記的對(duì)象太多,效率不高)
  • 空間問題(標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的碎片)
  • 標(biāo)記-整理算法

    根據(jù)老年代的特點(diǎn)推出的一種標(biāo)記算法,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,

    但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行回收,而是讓所有存活的對(duì)象向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。

    15.垃圾收集器

    雖然我們對(duì)各個(gè)垃圾收集器進(jìn)行比較,但并非為了挑選出一個(gè)最好的收集器。因?yàn)橹钡浆F(xiàn)在為止還沒有最好的垃圾收集器出現(xiàn),更加沒有萬能的垃圾收集器,我們能做的就是根據(jù)具體的應(yīng)用場景選擇適合自己的垃圾收集器

    Serial收集器

    -XX:+UseSerialGC -XX:+UseSerialOldGC

    Serial(串行)收集器是最基本、歷史最悠久的垃圾收集器了。

    這個(gè)收集器是一個(gè)單線程收集器。它的 “單線程” 的意義不僅僅意味著它只會(huì)使用一條垃圾收集線程去完成垃圾收集工作,更重要的是它在進(jìn)行垃圾收集工作時(shí)必須暫停其他所有的工作線程( “Stop The World” ),直到它收集結(jié)束。

    新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法

    Serial收集器有沒有優(yōu)于其他垃圾收集器的地方呢?

    簡單而高效(與其他收集器的單線程相比)。Serial收集器由于沒有線程交互的開銷,自然可以獲得很高的單線程收集效率。

    Serial Old收集器是Serial收集器的老年代版本,它同樣是一個(gè)單線程收集器。它主要有兩大用途:一種用途是在JDK1.5及以前的版本中與Parallel Scavenge收集器搭配使用,另一種用途是作為CMS收集器的后備方案

    Parallel Scavenge收集器

    -XX:+UseParallelGC -XX:+UseParallelOldGC

    Parallel收集器其實(shí)就是Serial收集器的多線程版本,除了使用多線程進(jìn)行垃圾收集外,其余行為(控制參數(shù)、收集算法、回收策略等等)和Serial收集器類似。

    默認(rèn)的收集線程數(shù)跟cpu核數(shù)相同,當(dāng)然也可以用參數(shù)(-XX:ParallelGCThreads)指定收集線程數(shù),但是一般不推薦修改。

    Parallel Scavenge 收集器的關(guān)注點(diǎn)是吞吐量(高效率的利用CPU)。

    CMS、G1 等垃圾收集器的關(guān)注點(diǎn)更多的是用戶線程的停頓時(shí)間(提高用戶體驗(yàn))。

    所謂吞吐量就是CPU中用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值。 Parallel Scavenge 收集器提供了很多參數(shù)供用戶找到最合適的停頓時(shí)間或最大吞吐量,如果對(duì)于收集器運(yùn)作不太了解的話,可以選擇把內(nèi)存管理優(yōu)化交給虛擬機(jī)去完成也是一個(gè)不錯(cuò)的選擇。

    新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法

    Parallel Old收集器是Parallel Scavenge收集器的老年代版本。使用多線程和“標(biāo)記-整理”算法。

    在注重吞吐量以及CPU資源的場合,都可以優(yōu)先考慮Parallel Scavenge收集器和Parallel Old收集器(JDK8默認(rèn)的新生代和老年代收集器)。

    ParNew收集器

    -XX:+UseParNewGC

    ParNew收集器其實(shí)跟Parallel收集器很類似,區(qū)別主要在于它可以和CMS收集器配合使用。

    新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法

    它是許多運(yùn)行在Server模式下的虛擬機(jī)的首要選擇。

    CMS收集器(重點(diǎn))

    -XX:+UseConcMarkSweepGC(old)

    CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。

    它非常符合在注重用戶體驗(yàn)的應(yīng)用上使用,它是HotSpot虛擬機(jī)第一款真正意義上的并發(fā)收集器,它第一次實(shí)現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時(shí)工作。

    運(yùn)作過程

  • 初始標(biāo)記階段(STW):暫停所有的用戶線程(STW),并記錄下 GC Roots 能直接引用的對(duì)象,速度很快。

  • 并發(fā)標(biāo)記階段: 并發(fā)標(biāo)記階段就是從 GC Roots 的直接關(guān)聯(lián)對(duì)象開始遍歷整個(gè)對(duì)象圖的過程, 這個(gè)過程耗時(shí)較長但是不需要暫停用戶線程(STW), 可以和用戶線程一起并發(fā)運(yùn)行。因?yàn)橛脩舫绦蚶^續(xù)運(yùn)行,可能會(huì)導(dǎo)致已經(jīng)標(biāo)記過的對(duì)象狀態(tài)發(fā)生改變。

  • 重新標(biāo)記階段(STW): 重新標(biāo)記階段就是為了修正并發(fā)標(biāo)記期間因?yàn)橛脩舫绦蚶^續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,這個(gè)階段的停頓時(shí)間一般會(huì)比初始標(biāo)記階段的時(shí)間稍長,但遠(yuǎn)遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短。

    主要用到三色標(biāo)記里的增量更新算法(見下面詳解)做重新標(biāo)記。

  • 并發(fā)清除階段: 開啟用戶線程,同時(shí) GC 線程開始對(duì)未標(biāo)記的區(qū)域做清掃。

    這個(gè)階段如果有新增對(duì)象會(huì)被直接標(biāo)記為黑色,并且不做任何處理。

  • 并發(fā)重置階段:重置本次 GC 過程中的標(biāo)記數(shù)據(jù)。

  • CMS 是一款優(yōu)秀的垃圾收集器,主要優(yōu)點(diǎn):并發(fā)收集、低停頓。但是它有下面幾個(gè)明顯的缺點(diǎn):

  • 會(huì)和應(yīng)用線程一起爭搶 CPU 資源,從而導(dǎo)致應(yīng)用程序變慢,吞吐量降低。
  • 無法處理浮動(dòng)垃圾(在并發(fā)標(biāo)記和并發(fā)清除階段又產(chǎn)生垃圾,這種浮動(dòng)垃圾只能等到下一次 GC 再清理了)
  • 它使用的標(biāo)記-清除算法會(huì)導(dǎo)致收集結(jié)束時(shí)產(chǎn)生大量空間碎片,當(dāng)然可以通過設(shè)置參數(shù)**-XX:+UseCMSCompactAtFullCollection** 讓JVM在執(zhí)行完標(biāo)記清除后再做整理
  • 執(zhí)行過程中的不確定性,有可能在并發(fā)標(biāo)記或并發(fā)清除階段還沒完成時(shí),老年代突然來了個(gè)大對(duì)象或者年輕代觸發(fā)了動(dòng)態(tài)年齡判斷機(jī)制導(dǎo)致一大批對(duì)象挪動(dòng)到老年代,而此時(shí)老年代是沒有空間可以存放這些對(duì)象的,此時(shí)整個(gè)過程就會(huì)STW,并切換到用Serial Old(單線程)收集器來進(jìn)行垃圾收集,直到本次垃圾收集結(jié)束。(也就是**“concurrent mode failure”**,并發(fā)失敗)
  • CMS的相關(guān)核心參數(shù)

  • -XX:+UseConcMarkSweepGC:啟用CMS
  • -XX:ConcGCThreads:并發(fā)的GC線程數(shù)
  • -XX:+UseCMSCompactAtFullCollection:Full GC之后做壓縮整理(減少碎片)
  • -XX:CMSFullGCsBeforeCompaction:多少次Full GC之后壓縮一次,默認(rèn)是0,代表每次Full GC后都會(huì)壓縮一次
  • -XX:CMSInitiatingOccupancyFraction:當(dāng)老年代使用達(dá)到該比例時(shí)會(huì)觸發(fā)Full GC(默認(rèn)是92,這是百分比,如果系統(tǒng)中大對(duì)象比較多,可能需要把該參數(shù)調(diào)小一點(diǎn))
  • -XX:+CMSScavengeBeforeRemark:在CMS(Full GC)前啟動(dòng)一次minor gc,目的在于減少老年代對(duì)年輕代的引用,降低CMS標(biāo)記階段時(shí)的開銷,一般CMS的GC耗時(shí) 80%都在標(biāo)記階段
  • -XX:+CMSParallellnitialMarkEnabled:表示在初始標(biāo)記的時(shí)候多線程執(zhí)行,縮短STW
  • -XX:+CMSParallelRemarkEnabled:表示在重新標(biāo)記的時(shí)候多線程執(zhí)行,縮短STW
  • G1收集器(重點(diǎn))

    G1將Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),JVM目標(biāo)是不超過2048個(gè)Region(JVM源碼里 TARGET_REGION_NUMBER 定義),實(shí)際可以超過該值,但是不推薦。

    一般Region大小等于堆大小除以2048,比如堆大小為4096M,則Region大小為2M,當(dāng)然也可以用參數(shù)"-XX:G1HeapRegionSize"手動(dòng)指定Region大小,但是推薦默認(rèn)的計(jì)算方式。

    G1保留了年輕代和老年代的概念,但不再是物理隔閡了,它們都是可以不連續(xù)的Region集合。

    默認(rèn)年輕代對(duì)堆內(nèi)存的初始占比是「5%」,如果堆大小為4096M,那么年輕代占據(jù)200MB左右的內(nèi)存,對(duì)應(yīng)大概是100個(gè)Region,可以通過“-XX:G1NewSizePercent”設(shè)置新生代初始占比。

    在系統(tǒng)運(yùn)行過程中,JVM會(huì)不停地給新生代增加更多的Region,但是新生代最多占比不會(huì)超過「60%」,可以通過“-XX:G1MaxNewSizePercent”調(diào)整。年輕代中的Eden和Survivor對(duì)應(yīng)的Region也跟之前一樣,默認(rèn)「8:1:1」,假設(shè)年輕代現(xiàn)在有1000個(gè)Region,Eden區(qū)對(duì)應(yīng)800個(gè),S0對(duì)應(yīng)100個(gè),S1對(duì)應(yīng)100個(gè)。

    一個(gè)Region可能之前是年輕代,之后這個(gè)Region進(jìn)行了垃圾回收,變成空的Region,那么后續(xù)這個(gè)Region就有可能會(huì)變成老年代,也就是說Region的區(qū)域功能可能會(huì)動(dòng)態(tài)變化。

    G1垃圾收集器對(duì)于對(duì)象什么時(shí)候會(huì)轉(zhuǎn)移到老年代跟之前講過的原則一樣,唯一不同的是對(duì)大對(duì)象的處理

    G1有專門分配大對(duì)象的Region叫Humongous區(qū),而不是讓大對(duì)象直接進(jìn)入老年代的Region中。

    在G1中,大對(duì)象的判定規(guī)則就是一個(gè)大對(duì)象超過了一個(gè)Region大小的「50%」,比如每個(gè)Region是2M,只要一個(gè)大對(duì)象超過了1M,就會(huì)被放入Humongous區(qū)中,而且一個(gè)大對(duì)象如果太大,可能會(huì)橫跨多個(gè)Region來存放。

    Humongous區(qū)專門存放短期巨型對(duì)象,不用直接進(jìn)入老年代,可以節(jié)約老年代的空間,避免因?yàn)槔夏甏臻g不足而導(dǎo)致的GC開銷。

    Full GC的時(shí)候除了收集年輕代和老年代之外,也會(huì)將Humongous區(qū)一并回收。

    G1收集器進(jìn)行一次GC(主要是Mixed GC)的運(yùn)作過程

  • 初始標(biāo)記階段(STW):暫停所有的用戶線程(STW),并記錄下 GC Roots 能直接引用的對(duì)象,速度很快。
  • 并發(fā)標(biāo)記階段:并發(fā)標(biāo)記階段就是從 GC Roots 的直接關(guān)聯(lián)對(duì)象開始遍歷整個(gè)對(duì)象圖的過程, 這個(gè)過程耗時(shí)較長但是不需要暫停用戶線程(STW), 可以和用戶線程一起并發(fā)運(yùn)行。因?yàn)橛脩舫绦蚶^續(xù)運(yùn)行,可能會(huì)導(dǎo)致已經(jīng)標(biāo)記過的對(duì)象狀態(tài)發(fā)生改變。
  • 最終標(biāo)記階段(STW): 最終標(biāo)記階段就是為了修正并發(fā)標(biāo)記期間因?yàn)橛脩舫绦蚶^續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,這個(gè)階段的停頓時(shí)間一般會(huì)比初始標(biāo)記階段的時(shí)間稍長,但遠(yuǎn)遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短。
  • 篩選回收階段(STW):篩選回收階段首先對(duì)各個(gè)Region的回收價(jià)值和回收成本進(jìn)行排序根據(jù)用戶所期望的GC停頓STW時(shí)間(可以用JVM參數(shù) -XX:MaxGCPauseMillis 指定)來制定回收計(jì)劃
  • 比如說老年代此時(shí)有1000個(gè)Region都滿了,但是根據(jù)預(yù)期停頓時(shí)間,本次垃圾回收可能只能停頓200毫秒,那么通過之前回收成本計(jì)算得知,可能回收其中800個(gè)Region剛好需要200ms,那么就只會(huì)回收800個(gè)Region(Collection Set,要回收的集合),盡量把GC導(dǎo)致的停頓時(shí)間控制在我們指定的范圍內(nèi)。

    這個(gè)階段其實(shí)也可以做到與用戶程序一起并發(fā)執(zhí)行,但是因?yàn)橹换厥找徊糠諶egion,時(shí)間是用戶可控制的,所以停頓用戶線程將大幅提高收集效率。

    不管是年輕代還是老年代,回收算法主要用的是「復(fù)制算法」,將一個(gè)Region中的存活對(duì)象復(fù)制到另一個(gè)Region中,這種不會(huì)像CMS收集器那樣回收完因?yàn)橛泻芏鄡?nèi)存碎片還需要再整理一次,G1采用復(fù)制算法幾乎不會(huì)有太多內(nèi)存碎片。

    注意:CMS回收階段是跟用戶線程一起并發(fā)執(zhí)行的,G1因?yàn)閮?nèi)部實(shí)現(xiàn)太復(fù)雜暫時(shí)沒有實(shí)現(xiàn)并發(fā)回收,不過到了ZGC,Shenandoah就實(shí)現(xiàn)了并發(fā)收集,Shenandoah可以看成是G1的升級(jí)版本。

    優(yōu)先列表

    G1收集器在后臺(tái)維護(hù)了一個(gè)「優(yōu)先列表」,每次根據(jù)允許的收集時(shí)間,優(yōu)先選擇回收價(jià)值最大的Region,

    比如一個(gè)Region花200ms只能回收10M垃圾,另外一個(gè)Region花50ms就能回收20M垃圾,在回收時(shí)間有限的情況下,G1當(dāng)然會(huì)優(yōu)先選擇后面這個(gè)Region來進(jìn)行回收。

    這種使用Region劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式,保證了G1收集器在有限時(shí)間內(nèi)可以盡可能的提高收集效率。

    G1收集器的優(yōu)點(diǎn):

    • 并行與并發(fā):G1能充分利用CPU、多核環(huán)境下的硬件優(yōu)勢,使用多個(gè)CPU核心來縮短 STW 停頓時(shí)間。部分其他收集器原本需要停頓用戶線程來執(zhí)行GC操作,而G1收集器仍然可以通過并發(fā)的方式讓用戶程序繼續(xù)運(yùn)行。
    • 分代收集:雖然G1可以不需要其他收集器配合就能獨(dú)立管理整個(gè)GC堆,但還是保留了分代的概念。
    • 空間整合:與CMS的「標(biāo)記-清除」算法不同,G1從整體上來看是基于「標(biāo)記-整理」算法實(shí)現(xiàn)的收集器,而從局部上來看是基于「復(fù)制」算法實(shí)現(xiàn)的。
    • 可預(yù)測的停頓時(shí)間:這是G1相對(duì)于CMS的另一個(gè)大優(yōu)勢,降低停頓時(shí)間是 G1 和 CMS 共同的關(guān)注點(diǎn),但 G1 除了追求低停頓外,還能建立可預(yù)測的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長度為M毫秒的時(shí)間片段(通過參數(shù)"-XX:MaxGCPauseMillis"指定)內(nèi)完成垃圾收集。

    G1垃圾收集分類

    Young GC

    Young GC并不是說現(xiàn)有的Eden區(qū)放滿了就會(huì)馬上觸發(fā),G1會(huì)計(jì)算現(xiàn)在Eden區(qū)回收大概要多長時(shí)間,如果回收時(shí)間遠(yuǎn)遠(yuǎn)小于參數(shù) -XX:MaxGCPauseMills 設(shè)定的值,那么增加年輕代的Region,繼續(xù)給新對(duì)象存放,不會(huì)馬上做Young GC,直到下次Eden區(qū)放滿,G1計(jì)算回收時(shí)間接近參數(shù) -XX:MaxGCPauseMills 設(shè)定的值,那么就會(huì)觸發(fā) Young GC。

    Mixed GC

    Mixed GC不是Full GC,老年代的堆占有率達(dá)到參數(shù)(-XX:InitiatingHeapOccupancyPercent)設(shè)定的值時(shí)觸發(fā),回收所有的Young和部分Old(根據(jù)期望的GC停頓時(shí)間確定Old區(qū)垃圾收集的優(yōu)先順序)以及大對(duì)象區(qū),正常情況G1的垃圾收集是先做Mixed GC,主要使用「復(fù)制」算法,需要把各個(gè)Region中存活的對(duì)象拷貝到別的Region中去,拷貝過程中如果發(fā)現(xiàn)沒有足夠的空Region能夠承載拷貝對(duì)象就會(huì)觸發(fā)一次Full GC。

    Full GC

    停止用戶程序,然后采用單線程進(jìn)行標(biāo)記清理和壓縮整理,使得空閑出來一批Region來供下一次Mixed GC使用,這個(gè)過程是非常耗時(shí)的。

    G1收集器參數(shù)設(shè)置

    -XX:+UseG1GC:使用G1收集器

    -XX:ParallelGCThreads:指定GC工作的線程數(shù)量

    -XX:G1HeapRegionSize:指定分區(qū)大小(1MB~32MB,且必須是2的N次冪),默認(rèn)將整堆劃分為2048個(gè)分區(qū)

    -XX:MaxGCPauseMillis:目標(biāo)暫停時(shí)間(默認(rèn)200ms)

    -XX:G1NewSizePercent:新生代內(nèi)存初始空間(默認(rèn)整堆的5%,值配置整數(shù),默認(rèn)就是百分比)

    -XX:G1MaxNewSizePercent:新生代最大內(nèi)存空間,默認(rèn)是50%

    -XX:InitiatingHeapOccupancyPercent:老年代占用空間達(dá)到整堆內(nèi)存閾值(默認(rèn)45%),則執(zhí)行新生代和老年代的混合收集(MixedGC),比如我們之前說的堆默認(rèn)有2048個(gè)Region,如果有接近1000個(gè)Region都是老年代的Region,則可能就要觸發(fā)「Mixed GC」了

    -XX:G1MixedGCLiveThresholdPercent:默認(rèn)85%,Region中的存活對(duì)象低于這個(gè)值時(shí)才會(huì)回收該Region,如果超過這個(gè)值,存活對(duì)象過多,回收的的意義不大

    -XX:G1MixedGCCountTarget:在一次回收過程中指定做幾次篩選回收(默認(rèn)8次),在最后一個(gè)篩選回收階段可以回收一會(huì),然后暫停回收,恢復(fù)用戶程序運(yùn)行,一會(huì)再開始回收,這樣可以讓系統(tǒng)不至于單次停頓時(shí)間過長。

    -XX:G1HeapWastePercent:默認(rèn)5%,GC過程中空出來的Region是否充足閾值,在混合回收的時(shí)候,對(duì)Region回收都是基于復(fù)制算法進(jìn)行的,都是把要回收的Region里的存活對(duì)象放入其他Region,然后這個(gè)Region中的垃圾對(duì)象全部清理掉,這樣的話在回收過程中就會(huì)不斷空出來新的Region,一旦空閑出來的Region數(shù)量達(dá)到了整堆內(nèi)存的5%,此時(shí)就會(huì)立即停止「混合回收」,意味著本次混合回收結(jié)束了。

    垃圾收集器優(yōu)化建議

    假設(shè)參數(shù) -XX:MaxGCPauseMills 設(shè)置的值很大,導(dǎo)致系統(tǒng)運(yùn)行很久,年輕代可能都占用了堆內(nèi)存的60%了,此時(shí)才觸發(fā)年輕代GC。

    那么存活下來的對(duì)象可能就會(huì)很多,此時(shí)就會(huì)導(dǎo)致Survivor區(qū)放不下那么多的對(duì)象,就會(huì)進(jìn)入老年代中。

    或者是你年輕代GC過后,存活下來的對(duì)象過多,導(dǎo)致進(jìn)入Survivor區(qū)后觸發(fā)了動(dòng)態(tài)年齡判斷機(jī)制,達(dá)到了Survivor區(qū)的50%,也會(huì)導(dǎo)致一些對(duì)象進(jìn)入老年代中。

    所以這里的核心還是在于調(diào)節(jié) -XX:MaxGCPauseMills 這個(gè)參數(shù)的值,在保證他的年輕代GC別太頻繁的同時(shí),還得考慮每次GC過后的存活對(duì)象有多少,避免存活對(duì)象太多快速進(jìn)入老年代,導(dǎo)致頻繁觸發(fā)「Mixed GC」。

    什么場景適合使用 G1

  • 50%以上的堆被存活對(duì)象占用
  • 對(duì)象分配和晉升的速度變化非常大
  • 垃圾回收時(shí)間特別長,超過1秒
  • 8GB以上的堆內(nèi)存(建議值)
  • 停頓時(shí)間是500ms以內(nèi)
  • 每秒幾十萬并發(fā)的系統(tǒng)如何優(yōu)化JVM

    Kafka類似的支撐高并發(fā)消息系統(tǒng)大家肯定不陌生,對(duì)于kafka來說,每秒處理幾萬甚至幾十萬消息是很正常的,一般來說部署kafka需要用大內(nèi)存機(jī)器(比如64G),也就是說可以給年輕代分配三四十G的內(nèi)存用來支撐高并發(fā)處理,這里就涉及到一個(gè)問題了,我們以前常說的對(duì)于Eden區(qū)的 young GC是很快的,這種大內(nèi)存情況下它的執(zhí)行速度還會(huì)很快嗎?

    很顯然不可能,因?yàn)閮?nèi)存太大,處理起來還是要花不少時(shí)間的,假設(shè)三四十G內(nèi)存回收可能最快也要幾秒鐘,按kafka這個(gè)并發(fā)量放滿三四十G的Eden區(qū)可能也就一兩分鐘吧,那么意味著整個(gè)系統(tǒng)每運(yùn)行一兩分鐘就會(huì)因?yàn)閥oung GC卡頓幾秒鐘沒法處理新消息,顯然是不行的。

    那么對(duì)于這種情況下如何優(yōu)化呢,我們可以使用G1收集器,設(shè)置 -XX:MaxGCPauseMills 為50ms,假設(shè) 50ms 能夠回收三到四個(gè)G的內(nèi)存,然后50ms的卡頓其實(shí)完全能夠接受,用戶幾乎無感知,那么整個(gè)系統(tǒng)就可以在卡頓幾乎無感知的情況下一邊處理業(yè)務(wù)一邊收集垃圾。

    G1天生就適合這種大內(nèi)存機(jī)器的JVM運(yùn)行,可以比較完美的解決大內(nèi)存垃圾回收時(shí)間過長的問題。

    如何選擇垃圾收集器

  • 優(yōu)先調(diào)整堆的大小讓服務(wù)器自己來選擇
  • 如果內(nèi)存小于100M,使用串行收集器(Serial + Serial Old)
  • 如果是單核,并且沒有停頓時(shí)間的要求,串行或JVM自己選擇
  • 如果允許停頓時(shí)間超過1秒,選擇并行或JVM自己選
  • 如果響應(yīng)時(shí)間最重要,并且不能超過1秒,使用并發(fā)收集器(CMS、G1)
  • 4G以下可以用parallel,4-8G可以用ParNew+CMS,8G以上可以用G1,幾百G以上用ZGC
  • 以下有連線的可以搭配使用

    JDK1.8 默認(rèn)使用 Parallel(年輕代和老年代都是)

    JDK1.9 默認(rèn)使用 G1

    16.三色標(biāo)記算法(加分)

    為了解決「標(biāo)記-清除」算法的問題,于是就出現(xiàn)了『三色標(biāo)記算法』!

    三色標(biāo)記算法指的是將所有對(duì)象分為白色、黑色和灰色三種類型。

  • 黑色表示從 GCRoots 開始,已經(jīng)掃描過它全部引用的對(duì)象,

  • 灰色指的是掃描過對(duì)象本身,還沒完全掃描過它全部引用的對(duì)象,

  • 白色指的是還沒掃描過的對(duì)象。

  • 但僅僅將對(duì)象劃分成三個(gè)顏色還不夠,真正關(guān)鍵的是:實(shí)現(xiàn)可達(dá)性分析算法的時(shí)候,將整個(gè)過程拆分成了初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)記、并發(fā)清除四個(gè)階段。

    • 初始標(biāo)記階段,指的是標(biāo)記 GC Roots 直接引用的節(jié)點(diǎn),將它們標(biāo)記為灰色,這個(gè)階段需要 「Stop the World」。
    • 并發(fā)標(biāo)記階段,指的是從灰色節(jié)點(diǎn)開始,去掃描整個(gè)引用鏈,然后將它們標(biāo)記為黑色,這個(gè)階段不需要「Stop the World」。
    • 重新標(biāo)記階段,指的是去校正并發(fā)標(biāo)記階段的錯(cuò)誤,這個(gè)階段需要「Stop the World」。
    • 并發(fā)清除,指的是將已經(jīng)確定為垃圾的對(duì)象清除掉,這個(gè)階段不需要「Stop the World」。

    通過將最耗時(shí)的引用鏈掃描剝離出來作為「并發(fā)標(biāo)記階段」,將其與用戶線程并發(fā)執(zhí)行,從而極大地降低了 GC 停頓時(shí)間。 但 GC 線程與用戶線程并發(fā)執(zhí)行,會(huì)帶來新的問題:對(duì)象引用關(guān)系可能會(huì)發(fā)生變化,有可能發(fā)生多標(biāo)漏標(biāo)問題。

    多標(biāo)問題

    在并發(fā)標(biāo)記過程中,如果由于方法運(yùn)行結(jié)束導(dǎo)致部分局部變量(GC Roots)被銷毀,這個(gè)GC Roots引用的對(duì)象之前又被掃描過(被標(biāo)記為非垃圾對(duì)象),那么本輪GC不會(huì)回收這部分內(nèi)存。

    這部分本應(yīng)該回收但沒有回收到的內(nèi)存,被稱之為“浮動(dòng)垃圾”。浮動(dòng)垃圾并不會(huì)影響垃圾回收的正確性,只是需要等到下一輪垃圾回收才被清除。

    另外,針對(duì)并發(fā)標(biāo)記(還有并發(fā)清理)開始后產(chǎn)生的新對(duì)象,通常的做法是直接全部當(dāng)成黑色,本輪回收不會(huì)進(jìn)行清除。這部分對(duì)象在運(yùn)行期間可能也會(huì)變成垃圾,這也算是浮動(dòng)垃圾的一部分。

    漏標(biāo)問題

    漏標(biāo)問題指的是原本應(yīng)該被標(biāo)記為存活的對(duì)象,被遺漏標(biāo)記為白色,從而導(dǎo)致該對(duì)象被錯(cuò)誤地回收。

    例如下圖中,假設(shè)我們現(xiàn)在遍歷到了節(jié)點(diǎn) E,此時(shí)應(yīng)用執(zhí)行如下代碼。

    這時(shí)候因?yàn)?E 對(duì)象已經(jīng)沒有引用 G 對(duì)象了,因此掃描 E 對(duì)象的時(shí)候并不會(huì)將 G 對(duì)象標(biāo)記為黑色存活狀態(tài)。但由于用戶線程的 D 對(duì)象引用了 G 對(duì)象,這時(shí)候 G 對(duì)象應(yīng)該是存活的,應(yīng)該標(biāo)記為黑色。但由于 D 對(duì)象已經(jīng)被掃描過了,不會(huì)再次掃描,因此 G 對(duì)象就被漏標(biāo)了。

    var G = objE.fieldG; objE.fieldG = null; // 灰色E 斷開引用 白色G objD.fieldG = G; // 黑色D 引用 白色G

    漏標(biāo)問題就非常嚴(yán)重了,其會(huì)導(dǎo)致存活對(duì)象被回收,會(huì)嚴(yán)重影響程序功能。

    那么我們的垃圾回收器是怎么解決這個(gè)問題呢?

    增加一個(gè)「重新標(biāo)記」階段。無論是在 CMS 回收器還是 G1 回收器,它們都在「并發(fā)標(biāo)記」階段之后,新增了一個(gè)「重新標(biāo)記」階段來校正「并發(fā)標(biāo)記」階段出現(xiàn)的問題。

    只是對(duì)于 CMS 回收器和 G1 回收器來說,它們解決的原理不同罷了。

    漏標(biāo)解決方案

    漏標(biāo)問題要發(fā)生需要滿足如下兩個(gè)充要條件:

  • 有至少一個(gè)黑色對(duì)象在自己被標(biāo)記之后指向了這個(gè)白色對(duì)象
  • 某個(gè)灰色對(duì)象在自己引用掃描完成之前刪除了對(duì)白色對(duì)象的引用
  • CMS 解決方案

    CMS 回收器采用的是增量更新方案,即破壞第一個(gè)條件:「有至少一個(gè)黑色對(duì)象在自己被標(biāo)記之后指向了這個(gè)白色對(duì)象」。

    既然有黑色對(duì)象在自己標(biāo)記后,又重新指向了白色對(duì)象。那么我們就把這個(gè)新插入的「引用」記錄下來,在后續(xù)「重新標(biāo)記」階段再以這些記錄過的引用關(guān)系中的黑色對(duì)象為根,對(duì)其引用進(jìn)行重新掃描。通過這種方式,被黑色對(duì)象引用的白色對(duì)象就會(huì)變成灰色,從而變?yōu)榇婊顮顟B(tài)。

    G1 解決方案

    G1 回收器采用的是原始快照的方案,即破壞第二個(gè)條件:「某個(gè)灰色對(duì)象在自己引用掃描完成之前刪除了對(duì)白色對(duì)象的引用」。

    既然灰色對(duì)象在掃描完成之前刪除了對(duì)白色對(duì)象的引用,那么我們是否能在灰色對(duì)象取消引用之前,先將這個(gè)要?jiǎng)h除的「引用」記錄下來。隨后在「重新標(biāo)記」階段再以這些記錄過的引用關(guān)系中的灰色對(duì)象為根,對(duì)它的引用進(jìn)行重新掃描,這樣就能掃描到白色對(duì)象,將白色對(duì)象直接標(biāo)記為黑色。

    這種方式有個(gè)缺點(diǎn),就是會(huì)產(chǎn)生浮動(dòng)垃圾。 因?yàn)楫?dāng)用戶線程取消引用的時(shí)候,有可能是真的取消引用,對(duì)應(yīng)的對(duì)象是真的要回收掉的。這時(shí)候我們通過這種方式,就會(huì)把本該回收的對(duì)象又復(fù)活了,從而導(dǎo)致出現(xiàn)浮動(dòng)垃圾。但相對(duì)于本該存活的對(duì)象被回收,這個(gè)代價(jià)還是可以接受的,畢竟在下次 GC 的時(shí)候就可以回收了。

    為什么G1用原始快照,CMS用增量更新?

    原始快照相對(duì)于增量更新效率會(huì)更高(當(dāng)然原始快照可能會(huì)造成更多的浮動(dòng)垃圾),因?yàn)椴恍枰凇?strong>重新標(biāo)記」階段再次深度掃描被刪除引用的對(duì)象,而CMS對(duì)增量引用的根對(duì)象會(huì)做深度掃描;

    G1因?yàn)楹芏鄬?duì)象都位于不同的region,CMS就一塊老年代區(qū)域,重新深度掃描根對(duì)象的話G1的代價(jià)會(huì)比CMS高,所以G1選擇原始快照而不深度掃描根對(duì)象,只是簡單標(biāo)記,等到下一輪GC再深度掃描。

    寫屏障

    以上無論是對(duì)引用關(guān)系的插入還是刪除,虛擬機(jī)的記錄操作都是通過「寫屏障」實(shí)現(xiàn)的

    寫屏障

    給某個(gè)對(duì)象的成員變量賦值時(shí),其底層代碼大概長這樣:

    /** * @param field 某對(duì)象的成員變量,如 a.b.d * @param new_value 新值,如 null */ void oop_field_store(oop* field, oop new_value) { *field = new_value; // 賦值操作 }

    所謂的寫屏障,其實(shí)就是指在賦值操作前后,加入一些處理(可以參考AOP的概念):

    void oop_field_store(oop* field, oop new_value) { pre_write_barrier(field); // 寫屏障-寫前操作*field = new_value; post_write_barrier(field, value); // 寫屏障-寫后操作 }

    寫屏障實(shí)現(xiàn)原始快照

    當(dāng)對(duì)象E的成員變量的引用發(fā)生變化時(shí),比如引用消失(e.g = null),我們可以利用寫屏障,將E原來成員變量的引用對(duì)象G記錄下來:

    void pre_write_barrier(oop* field) {oop old_value = *field; // 獲取舊值remark_set.add(old_value); // 記錄原來的引用對(duì)象 }

    寫屏障實(shí)現(xiàn)增量更新

    當(dāng)對(duì)象D的成員變量的引用發(fā)生變化時(shí),比如新增引用(d.g = g),我們可以利用寫屏障,將D新的成員變量的引用對(duì)象D記錄下來:

    void post_write_barrier(oop* field, oop new_value) { remark_set.add(new_value); // 記錄新引用的對(duì)象 }

    17.卡表(Card Table)

    在新生代做GC Roots可達(dá)性掃描過程中可能會(huì)碰到跨代引用的對(duì)象,這種情況如果又去對(duì)老年代做掃描那效率就太低了。

    為此,在新生代可以引入**記憶集(Remember Set)**的數(shù)據(jù)結(jié)構(gòu)(記錄從非收集區(qū)到收集區(qū)的指針集合),避免把整個(gè)老年代加入GC Roots掃描范圍內(nèi)。

    在垃圾收集場景中,收集器只需要通過記憶集判斷出某一塊非收集區(qū)域是否存在指向收集區(qū)域的指針即可,無需了解跨代引用指針的全部細(xì)節(jié)。

    hotspot使用一種叫做**卡表(Card Table)**的方式實(shí)現(xiàn)記憶集,也是目前最常用的一種方式。關(guān)于卡表與記憶集的關(guān)系, 可以類比Java語言中HashMap與Map的關(guān)系。

    卡表使用一個(gè)字節(jié)數(shù)組實(shí)現(xiàn):CARD_TABLE[ ],每個(gè)元素對(duì)應(yīng)著其標(biāo)識(shí)的內(nèi)存區(qū)域一塊特定大小的內(nèi)存塊,稱為“卡頁”。

    hotSpot使用的卡頁是2的9次方大小,即512字節(jié)。

    將老年代劃分成一塊塊特定大小的區(qū)域(512byte),每一塊稱為一個(gè)card,即卡頁。

    一個(gè)卡頁中可包含多個(gè)對(duì)象,只要其中有一個(gè)對(duì)象存在「跨代引用」,其對(duì)應(yīng)的卡表的元素標(biāo)識(shí)就變成1,表示該元素變臟,否則為0。

    卡表除了包含對(duì)應(yīng)卡頁的元素標(biāo)識(shí),還存儲(chǔ)了每個(gè)卡頁對(duì)應(yīng)的內(nèi)存地址。

    在掃描GC Roots時(shí),除了掃描年輕代的存活對(duì)象之外,還需要掃描卡表中為1的對(duì)應(yīng)卡頁中的對(duì)象。

    億級(jí)流量電商系統(tǒng)JVM參數(shù)設(shè)置優(yōu)化

    大型電商系統(tǒng)后端現(xiàn)在一般都是拆分成多個(gè)子系統(tǒng)部署的,比如,商品系統(tǒng),庫存系統(tǒng),訂單系統(tǒng),促銷系統(tǒng),會(huì)員系統(tǒng)等等。

    這里以比較核心的訂單系統(tǒng)為例:

    分析

    假設(shè)單臺(tái)機(jī)器每秒產(chǎn)生60M對(duì)象,運(yùn)行14次后占滿Eden區(qū)。

    這里需要注意下,前面13秒的所有對(duì)象都可以回收掉,因?yàn)檫@些訂單對(duì)象在產(chǎn)生1秒后都變?yōu)槔鴮?duì)象,而最后一秒產(chǎn)生的對(duì)象(300個(gè)請求),執(zhí)行到一半的時(shí)候Eden區(qū)放滿了,這時(shí)候就會(huì)觸發(fā)Minor GC(STW),意味著最后一秒產(chǎn)生的那60M對(duì)象會(huì)被挪動(dòng)到S區(qū),而前面13秒產(chǎn)生的700多M對(duì)象會(huì)被銷毀。

    然后這最后的60M對(duì)象并不會(huì)挪動(dòng)到S區(qū),而是挪動(dòng)到老年代里面去了,這是什么呢?

    根據(jù)動(dòng)態(tài)年齡判斷機(jī)制,假設(shè)這60M對(duì)象的年齡都是1,而且光這60M對(duì)象就已經(jīng)超過了S區(qū)內(nèi)存大小的50%,所以會(huì)把這60M對(duì)象已經(jīng)年齡大于等于1的所有對(duì)象都挪動(dòng)到老年代中去。

    也就意味著每過14秒就有60M對(duì)象挪動(dòng)到老年代,假設(shè)老年代的空間大小為2G,那么幾分鐘后老年代就會(huì)被放滿,老年代放滿就會(huì)觸發(fā)Full GC,這樣子就會(huì)導(dǎo)致我們的系統(tǒng)幾分鐘就會(huì)執(zhí)行一次Full GC,正常情況下Full GC應(yīng)該是幾個(gè)小時(shí),幾天甚至幾周做一次才正常。

    這種情況是可以做優(yōu)化的,只需修改下JVM的參數(shù),就可以讓我們的JVM幾乎不發(fā)生Full GC

    解決方案:

    -Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8

    將整個(gè)年輕代調(diào)大一些,比如設(shè)置整個(gè)年輕代為2G,那么Eden區(qū)就是1.6G,S0和S1各為200M。

    假設(shè)經(jīng)過25秒后Eden區(qū)才會(huì)放滿,此時(shí)會(huì)觸發(fā)Minor GC,Eden區(qū)會(huì)被清空,而最后的那60M對(duì)象會(huì)被放到S區(qū)中,而根據(jù)動(dòng)態(tài)年齡判斷機(jī)制發(fā)現(xiàn)是沒問題的,因此這60M是可以放進(jìn)S區(qū)的。

    再經(jīng)過25秒Eden區(qū)滿了又會(huì)觸發(fā)Minor GC,而此時(shí)會(huì)將S區(qū)的那60M對(duì)象和Eden區(qū)清空,然后再把新的60M對(duì)象放到S區(qū),這樣子循環(huán)反復(fù),我們可以發(fā)現(xiàn)系統(tǒng)不會(huì)觸發(fā)Full GC了。

    這樣就降低了因?yàn)閷?duì)象動(dòng)態(tài)年齡判斷機(jī)制導(dǎo)致的對(duì)象頻繁進(jìn)入老年代的問題,其實(shí)很多優(yōu)化無非就是讓短期存活的對(duì)象盡量都留在survivor區(qū)里,不要進(jìn)入老年代,這樣在Minor GC的時(shí)候這些對(duì)象都會(huì)被回收,不會(huì)進(jìn)入到老年代從而導(dǎo)致Full GC。

    對(duì)象年齡閾值應(yīng)該設(shè)置為多少才移動(dòng)到老年代比較合適

    本例中一次Minor GC要間隔二三十秒,大多數(shù)對(duì)象一般在幾秒內(nèi)就會(huì)變成垃圾,完全可以將默認(rèn)的15歲改小一點(diǎn),比如改為5歲,那么意味著對(duì)象要經(jīng)過5次Minor GC才會(huì)進(jìn)入老年代,整個(gè)時(shí)間也有一兩分鐘了,如果對(duì)象這么長時(shí)間都沒被回收,完全可以認(rèn)為這些對(duì)象是會(huì)存活的比較長的對(duì)象(例如緩存),可以移動(dòng)到老年代,而不是繼續(xù)一直占用survivor區(qū)空間,讓更多對(duì)象可以存放在年輕代。

    對(duì)于多大的對(duì)象直接進(jìn)入老年代(參數(shù) -xx:PretenureSizeThreshold),這個(gè)一般可以結(jié)合自己的系統(tǒng)看下有沒有什么大對(duì)象生成,預(yù)估下大對(duì)象的大小,一般來說設(shè)置為1M就差不多了,很少有超過1M的大對(duì)象,這些對(duì)象一般就是你系統(tǒng)初始化分配的緩存對(duì)象,比如大的緩存List,Map之類的對(duì)象。

    可以適當(dāng)調(diào)整JVM參數(shù)如下:

    -Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M

    通過ParNew+CMS優(yōu)化(JDK8主流互聯(lián)網(wǎng)公司用到的垃圾收集器組合)

    JDK8默認(rèn)的垃圾回收器是 -XX:+UseParallelGC(年輕代)和 -XX:+UseParallelOldGC(老年代),如果內(nèi)存較大(超過4個(gè)G,只是經(jīng)驗(yàn)值),系統(tǒng)對(duì)停頓時(shí)間比較敏感,我們可以使用 ParNew+CMS(-XX:+UseParNewGC -XX:+UseConcMarkSweepGC)

    之前做的參數(shù)調(diào)整之后,幾乎不會(huì)再發(fā)生Full GC,但這里有個(gè)前提就是當(dāng)系統(tǒng)壓力維持在每秒1000多單的情況下,才不會(huì)發(fā)生Full GC。

    但如果系統(tǒng)在搶購的30分鐘內(nèi),每過幾分鐘就會(huì)有一個(gè)峰值的訪問。峰值的訪問假設(shè)單臺(tái)機(jī)器要承受500單,甚至七八百單、上千單的訪問,在這種情況下就有可能會(huì)出現(xiàn)問題。

    在峰值情況下,比如之前是25秒的最后一秒的60M對(duì)象要挪動(dòng)到S區(qū),而在峰值情況下,訂單處理速度是比較慢的(一般單臺(tái)機(jī)器每秒能抗住300單,而如果一下子要抗住七八百單,性能和內(nèi)存會(huì)非常吃緊,會(huì)導(dǎo)致每個(gè)訂單的處理周期變長,一個(gè)訂單的執(zhí)行時(shí)間可能就要跨幾秒鐘),所以有可能在每25秒的最后幾秒內(nèi),這幾秒的對(duì)象都要挪動(dòng)到S區(qū),這些對(duì)象的大小就遠(yuǎn)遠(yuǎn)不止60M了,可能有一兩百M(fèi)甚至兩三百M(fèi),而我們的S區(qū)配置是200M,這樣就放不下這些對(duì)象,所以要將這兩三百M(fèi)對(duì)象直接挪動(dòng)到老年代,這樣有可能在二三十分鐘老年代就滿了,進(jìn)而觸發(fā)Full GC。

    然后其實(shí)在半小時(shí)后發(fā)生Full GC,這時(shí)候已經(jīng)過了搶購的最高峰期,后續(xù)可能幾小時(shí)才做一次Full GC。對(duì)于碎片整理,因?yàn)槎际?小時(shí)或幾個(gè)小時(shí)才做一次FullGC,是可以每做完一次就開始碎片整理,或者兩到三次之后再做一次也行。

    綜上,只要年輕代參數(shù)設(shè)置合理,老年代CMS的參數(shù)設(shè)置基本都可以用默認(rèn)值,如下所示:

    -Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -XX:+UseCMSCompactAtFullCollection // 開啟Full GC后進(jìn)行壓縮整理 -XX:CMSFullGCsBeforeCompaction=3 // 每做完3次Full GC后進(jìn)行一次碎片壓縮整理

    總結(jié)

    以上是生活随笔為你收集整理的一文搞定 JVM 面试,教你吊打面试官~的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    99色99| 天天操天天综合网 | 成人午夜电影在线播放 | 国产粉嫩在线观看 | 日韩欧美国产视频 | 午夜精品视频一区 | 久久精品成人 | 日韩精品免费一线在线观看 | 欧美一级电影片 | 青青久草在线 | 国产91区| 黄色精品在线看 | 五月开心色 | 免费看一级特黄a大片 | 精品国产成人av在线免 | 热99在线视频 | 免费韩国av | 欧美三人交 | 日韩av高清在线观看 | 亚洲欧美日韩在线看 | 狠狠干网址 | 亚洲国产小视频在线观看 | 91麻豆精品国产91久久久久久久久 | a特级毛片 | www.夜夜骑.com| 国产精品久久久久久久久久久久冷 | 四虎成人免费观看 | 久久久福利视频 | 91免费看黄 | 国产一区二区在线免费 | 精品久久久99 | 国产免费久久精品 | 免费观看日韩av | 日韩在线观看三区 | 欧美一级在线 | 精品一区二区三区四区在线 | 免费看黄的 | 久久久精选 | 在线观看va | 青草视频在线免费 | 久久国产精品视频 | 日韩免费b | 亚洲婷婷在线 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 91亚洲精品视频 | 久久亚洲福利视频 | 五月开心色 | 国产色视频123区 | 欧美日韩69 | 97精品国产97久久久久久春色 | 亚洲精品午夜视频 | 国产在线播放观看 | 综合伊人久久 | 午夜久久成人 | 中字幕视频在线永久在线观看免费 | 国产高清 不卡 | 亚洲人成人99网站 | 亚洲精品乱码白浆高清久久久久久 | 日韩理论片中文字幕 | 欧美精品一区二区三区四区在线 | 国产九色在线播放九色 | 久久av中文字幕片 | 久久久精品 一区二区三区 国产99视频在线观看 | 日韩精品不卡在线观看 | 99精品视频在线观看视频 | 亚洲综合在线五月 | 色吊丝在线永久观看最新版本 | 国产色a在线观看 | 99视频在线免费播放 | 成人一区二区三区中文字幕 | 国产精品久久嫩一区二区免费 | 91麻豆精品国产91久久久使用方法 | 国产小视频网站 | 精品美女国产在线 | 日韩国产欧美在线视频 | 中文字幕人成乱码在线观看 | 成人在线播放视频 | 丁香久久激情 | 日韩免 | 国产成人亚洲精品自产在线 | 国产精品资源在线观看 | 91免费看片黄 | 久久99久久99精品免观看粉嫩 | 麻豆超碰| 亚洲黄色av | 久久久久久久久久免费视频 | 激情综合色综合久久 | 不卡的av在线 | 亚洲国产字幕 | 天天摸天天干天天操天天射 | 天天干天天拍天天操 | 六月婷婷久香在线视频 | 日日射天天射 | 国产精品丝袜 | 天天玩天天干 | 中文字幕丰满人伦在线 | 国产三级午夜理伦三级 | 久久免费美女视频 | 啪啪免费试看 | 欧美午夜激情网 | 九九久久在线看 | 国产va精品免费观看 | 国产精品va视频 | 国产精品午夜8888 | 麻豆传媒电影在线观看 | 成人免费网站视频 | 天天操天天干天天操天天干 | 欧美日韩国产一区 | 精品国产福利在线 | 日韩精品一区二区在线视频 | 天天操天天拍 | 日本午夜在线观看 | 国内精品久久久久影院日本资源 | www.日日日.com| 亚洲精品乱码久久久久久蜜桃不爽 | 中文字字幕在线 | 成人亚洲欧美 | 欧美一区二区三区特黄 | 国产偷v国产偷∨精品视频 在线草 | 久久国产欧美日韩精品 | 亚洲美女免费视频 | 最近免费观看的电影完整版 | 99免费精品视频 | 九九久久久 | 欧美国产亚洲精品久久久8v | 久久国产精品免费一区二区三区 | 国产精品乱看 | 日韩网站在线免费观看 | 青青五月天 | 中文字幕电影高清在线观看 | 久久久18| 国产黄影院色大全免费 | 国产又粗又硬又长又爽的视频 | 91看片在线看片 | 一级特黄av | 天天干天天天 | www.黄色在线 | 成人免费电影 | 国产精品99久久久精品免费观看 | 久久九九精品久久 | 日韩a在线播放 | 99久久综合国产精品二区 | 国内精品久久久久久久97牛牛 | 九九在线视频 | 正在播放国产91 | 国产一级免费播放 | 国产女人40精品一区毛片视频 | 久久精品欧美日韩精品 | 999视频在线播放 | 中字幕视频在线永久在线观看免费 | 日日爽夜夜操 | 成人h在线播放 | 96久久欧美麻豆网站 | 欧美午夜a | 中文字幕影片免费在线观看 | 成年人黄色在线观看 | 黄网站app在线观看免费视频 | 亚洲国产精品视频 | 91三级视频| 久久伊人五月天 | 国产精品第一页在线 | 亚洲女人av | 日韩av中文 | 国产精品美女在线观看 | 亚洲三级在线免费观看 | 色吧av色av| 免费一级特黄录像 | 久久人人97超碰国产公开结果 | 综合色站导航 | 摸bbb搡bbb搡bbbb | 中文字幕电影网 | 久久久久久久久久久久久久电影 | 欧美一级性生活片 | www.天天色.com | 国产精品电影一区 | 99热亚洲精品 | 91色吧| 狠狠干天天色 | 国产精品久久久免费看 | av网站免费线看精品 | 久久久久久久久久福利 | 在线天堂中文www视软件 | 国内一区二区视频 | 久久精品视频网 | a黄色一级片 | 99久久爱 | 五月婷婷综合网 | 日韩免费在线网站 | 日日操网 | 久草久热 | 日韩一区二区在线免费观看 | 24小时日本在线www免费的 | 国产青草视频在线观看 | 日韩中文在线观看 | 成人国产精品免费观看 | 国产精品久久久久免费a∨ 欧美一级性生活片 | 久久成电影 | 激情av一区二区 | 久一久久 | 国产中出在线观看 | 97在线看 | 天天躁日日躁狠狠躁 | 九九热在线观看 | free,性欧美| 亚洲精品国偷拍自产在线观看蜜桃 | 日韩精品视频在线免费观看 | 日韩成人精品 | 免费在线观看成人av | 黄色成人在线观看 | 国内外成人在线视频 | 国产一区黄色 | 天天操天天干天天玩 | 亚洲精选视频免费看 | 国产 色| 婷婷六月天丁香 | 手机色在线 | 综合网中文字幕 | 97国产精品免费 | 青草视频在线免费 | 日韩在线观看视频一区二区三区 | www.av在线播放 | 九九热久久久 | 欧美成人在线网站 | 日韩精品中字 | 99精品视频观看 | 国产一区在线视频 | 亚洲综合视频在线 | 在线观看韩日电影免费 | 久久综合狠狠综合久久激情 | 中文字幕 国产 一区 | 91视频免费国产 | 国产美女视频 | 欧美在线free | 日韩在线影视 | 亚洲v欧美v国产v在线观看 | 久久婷婷精品 | 精品国产一区二区三区噜噜噜 | 国产一级a毛片视频爆浆 | 日韩最新在线视频 | 在线观看国产日韩 | 不卡中文字幕在线 | 精品黄色视| 久久av在线| 亚洲成av人片在线观看香蕉 | 国产精品99久久久久的智能播放 | 国产在线a视频 | 91精品系列 | 精品久久在线 | 天天草天天干 | avwww在线| 亚洲一区二区视频 | 91网址在线 | 丁香花在线观看免费完整版视频 | 99国产视频| 天天爽天天爽天天爽 | 国产精品入口麻豆 | 色五丁香 | 久久精品国产亚洲精品2020 | 国产免费一区二区三区最新 | 国产精品女同一区二区三区久久夜 | 在线中文字幕av观看 | 国产精品视频999 | 久久永久免费视频 | 亚洲国产成人精品在线观看 | 特级xxxxx欧美| 欧美视频日韩 | 国产精在线 | 激情偷乱人伦小说视频在线观看 | 91视频麻豆 | 一区二区三区播放 | 久久 地址 | 日韩av高清 | 国产私拍在线 | 9在线观看免费 | www.久久久精品 | 久久国产亚洲视频 | a级成人毛片 | 不卡的av在线 | 成人网中文字幕 | 三级黄色网址 | 国产精品 中文字幕 亚洲 欧美 | 99re6热在线精品视频 | 天天色天天 | 91精品国产高清自在线观看 | 久久久人人爽 | 午夜性生活片 | 91大神一区二区三区 | 毛片a级片 | 91精品老司机久久一区啪 | 国产精品久久久久久久久久尿 | 激情五月婷婷网 | 美女国产网站 | 久久福利精品 | 在线观看欧美成人 | 国产一区二区精品91 | 天天操天天干天天综合网 | 91av综合| 国产精品久久久久9999 | av网站播放 | 欧美男同网站 | 欧美成人在线网站 | 久免费 | 亚洲一区二区91 | 欧美视频www | 99久久久国产精品 | 在线视频观看亚洲 | 欧美性网站| 国产不卡精品 | 一区二区三区四区五区在线 | 国产亚州av | 国产精品嫩草在线 | 国产精品一区二区吃奶在线观看 | 国产乱码精品一区二区三区介绍 | 日批网站在线观看 | 人人爽久久久噜噜噜电影 | 日韩国产高清在线 | 国产伦精品一区二区三区无广告 | 亚洲成人资源 | 亚洲激情中文 | 日韩日韩日韩日韩 | 色综合国产 | 最近av在线 | 成人三级网址 | 中文字幕在线观看免费高清完整版 | 日韩精品久久久久久久电影99爱 | 男女拍拍免费视频 | 国产91av视频在线观看 | 久久综合九色 | 91精彩在线视频 | 免费毛片一区二区三区久久久 | 91亚洲精品国产 | 中文字幕国产在线 | 久久久久久久久精 | 日韩一区二区三区高清在线观看 | 99久久婷婷国产 | 毛片99 | 九九一级片 | 久久久久久久久久久久久国产精品 | 欧美日比视频 | 国产亚洲精品久久久久久久久久久久 | 中午字幕在线 | 天天se天天cao天天干 | 天天操天天干天天综合网 | 人人超在线公开视频 | 国产精品久久久久9999 | 99视频偷窥在线精品国自产拍 | 国产字幕av | 国产一区久久久 | 日韩激情片在线观看 | avwww在线观看 | 国产最新在线观看 | 成人手机在线视频 | 日日夜夜狠狠操 | 免费看的毛片 | 国产午夜在线 | 国产剧情一区二区在线观看 | 久久久久日本精品一区二区三区 | 日韩视频精品在线 | 天天色天| 久久成人综合视频 | 亚州视频在线 | 热久久免费视频精品 | 成人一级影视 | 午夜黄色一级片 | 国产精品日韩久久久久 | 日韩一区二区三区免费视频 | 午夜成人免费电影 | 在线a视频免费观看 | 国产精品成人免费精品自在线观看 | 日躁夜躁狠狠躁2001 | 成人免费网站在线观看 | 国产欧美最新羞羞视频在线观看 | 午夜电影久久久 | av大全在线观看 | 国产精品毛片一区二区三区 | 久久婷婷色综合 | 黄色网免费| 97超碰在线久草超碰在线观看 | 成年人在线看视频 | 黄色大片av | 久久激情视频免费观看 | 成人国产精品免费观看 | 男女精品久久 | 免费观看www小视频的软件 | 91视视频在线直接观看在线看网页在线看 | 亚洲精品国精品久久99热 | 中文字幕久久精品 | 在线观看免费成人 | 色视频在线 | 国产精品美女久久久久久网站 | 狠狠干婷婷色 | 久久99精品热在线观看 | 婷婷五天天在线视频 | 岛国一区在线 | 五月开心激情 | 久草综合视频 | 丁香电影小说免费视频观看 | 狠狠色婷婷丁香六月 | 亚洲精品国产精品国自产观看浪潮 | 超碰在线成人 | 国产精品一区二区三区免费看 | 中文字幕国产一区二区 | 日韩在线电影一区二区 | av黄色大片 | 免费高清在线视频一区· | 久久久高清免费视频 | 亚洲精品视频在线观看免费视频 | 中文字幕网站 | 欧美一区二区日韩一区二区 | 日韩av在线小说 | 久久社区视频 | 国产精品美女在线观看 | 精品视频专区 | 麻豆精品视频在线观看免费 | 日韩av成人在线观看 | 99久久精品国产一区二区三区 | 97成人在线观看视频 | 国产免费片 | 天天射天天干天天插 | 黄色www免费 | 成年人在线电影 | 欧美成人亚洲成人 | 最近日本mv字幕免费观看 | 在线播放视频一区 | 国产精品一级在线 | 亚洲人成人99网站 | 免费三级骚 | 欧美极品裸体 | 国产在线免费观看 | 中文av日韩| 日本爱爱片 | 日韩欧美视频一区 | 黄网站色成年免费观看 | 国产成人333kkk | 美女黄频| 成人久久免费 | 亚洲视频2 | 91爱爱免费观看 | 99视频网站| 午夜国产福利在线观看 | 麻豆你懂的 | 久久经典国产视频 | 色妞色视频一区二区三区四区 | 亚洲情感电影大片 | 99re久久精品国产 | 91精品国产综合久久福利不卡 | 91桃色免费观看 | 久久免费大片 | 亚洲视频在线观看网站 | 中文字幕超清在线免费 | 免费三级在线 | 成人av影视观看 | 久久刺激视频 | 日日碰狠狠躁久久躁综合网 | 成人精品福利 | 正在播放国产一区二区 | 亚洲精品在线电影 | 国产视频色 | 国产精品色婷婷视频 | 国产精品99爱 | 99国产精品久久久久老师 | 国产亚洲综合性久久久影院 | 亚洲综合色网站 | 亚洲一级黄色片 | 国产精品久久一区二区无卡 | 国产精品色在线 | 国产中文视 | 欧美精品生活片 | 91九色网址| 国产精品福利在线播放 | 一区二区三高清 | 国产精品18久久久久久久久 | 在线免费观看不卡av | 一区二区三高清 | 久草视频中文 | av线上看 | 奇米影视在线99精品 | 精品免费视频 | 日韩电影在线观看中文字幕 | 精品一区 在线 | 夜夜夜夜夜夜操 | 欧美性一级观看 | 一级a毛片高清视频 | 精品久久久影院 | 99国产免费网址 | 国产精品美女久久久久久网站 | 可以免费观看的av片 | 日韩中文字幕在线不卡 | 在线观看亚洲专区 | 国产最新在线观看 | 久色 网| 亚洲精品美女免费 | 国产亚洲精品久久久久秋 | 日本视频网 | 久久你懂得 | av资源中文字幕 | 国产精品久久久久久久久久久久午夜片 | 国产成人久久精品77777综合 | 91视视频在线直接观看在线看网页在线看 | 中文字幕中文字幕 | 91福利视频一区 | 成 人 黄 色视频免费播放 | 黄色a一级片 | 一级一片免费观看 | 国产日韩欧美视频 | 精品成人a区在线观看 | 在线观看免费成人 | 91精品一 | 99视| 一本一本久久a久久 | 国产成人精品亚洲日本在线观看 | 黄色av大片 | 国产精品一区二区av影院萌芽 | 91精品国产91热久久久做人人 | 久草视频免费在线播放 | 久久国产露脸精品国产 | 国产精品一区二区三区四区在线观看 | 日韩欧美在线中文字幕 | 日韩毛片在线一区二区毛片 | 精品在线观看免费 | 一区二区三区四区久久 | 日批在线观看 | 亚洲免费精彩视频 | 最近日本韩国中文字幕 | 天堂久久电影网 | 99精品99 | 五月婷婷电影网 | 亚洲在线网址 | 久久综合免费视频影院 | 午夜视频在线观看网站 | 四虎在线观看 | 精品福利视频在线 | 91亚洲成人| 天天夜夜狠狠操 | 四虎天堂 | 久久久久久久av麻豆果冻 | 国产一区二区三区高清播放 | 成人香蕉视频 | 日韩美精品视频 | 国产一级片在线播放 | 国产精品免费久久 | 日韩在线视频网址 | 国产婷婷色 | 国产精品久久网站 | 亚洲欧洲国产精品 | 国产丝袜 | 麻豆视频在线观看免费 | 国产精品一区二区你懂的 | 日本中文字幕影院 | 天天在线免费视频 | 亚洲精品456在线播放第一页 | 五月婷婷影院 | 国产精品18久久久久久vr | 五月天久久久 | 久久久久久久久久久久久久免费看 | 开心激情五月婷婷 | 中文字幕免费国产精品 | 午夜精品久久久久久久99无限制 | 伊人在线视频 | 97理论电影 | 精品国偷自产国产一区 | 在线观看中文av | 欧美午夜激情网 | 亚洲精品国产精品99久久 | 久久人人爽人人爽人人片av免费 | 一区二区三区四区影院 | 国产日韩精品在线观看 | 看片网站黄 | 精品亚洲在线 | 成年人在线观看网站 | 国产精品成人免费一区久久羞羞 | 久久久久久久久久久久久久电影 | 丁香六月在线观看 | 久久99亚洲网美利坚合众国 | 久久人人爽人人片 | 亚洲婷婷综合色高清在线 | 色综合久久久久久久 | 日韩和的一区二在线 | 日韩精品一区二区三区水蜜桃 | 亚洲一区欧美激情 | 亚洲黄色在线 | 一本一道久久a久久精品蜜桃 | 日韩色一区二区三区 | 免费av网站在线 | www.com久久久 | 最近中文字幕在线中文高清版 | 深爱婷婷激情 | 在线观看久久 | 午夜视频在线观看一区二区三区 | 国产精品一区二区你懂的 | 麻豆传媒视频在线免费观看 | 超碰97网站 | 国产免费人成xvideos视频 | 国产一级在线观看视频 | 手机av在线网站 | 欧美日本高清视频 | 99久精品 | 97人人视频 | 国产99久久精品一区二区300 | 精品在线免费视频 | 久久综合视频网 | av在线电影网站 | 亚洲国产一区在线观看 | 欧美日韩国语 | 中文字幕人成人 | 日韩在线视频一区二区三区 | 免费在线观看av片 | 欧美午夜久久 | 久久99亚洲精品久久 | 91网址在线 | 久久成人国产精品入口 | 月下香电影 | 成人一级在线 | 久久久久久免费网 | 亚洲一区动漫 | 色之综合网 | 亚洲va欧美va人人爽春色影视 | 日韩高清免费在线 | 国产一区二区视频在线 | 久久人人97超碰国产公开结果 | 一区二区影院 | 国产亚洲精品久久久久久久久久 | 婷婷六月中文字幕 | 成人av中文字幕 | 丁香九月婷婷综合 | 日韩av中文在线观看 | 午夜av在线免费 | 欧美日韩免费一区 | 国产成人精品在线播放 | 一二三久久久 | 成人黄色免费在线观看 | 国产99精品在线观看 | 97人人人| 色a网 | 国产亚洲观看 | 在线一区二区三区 | 国产精品久免费的黄网站 | 91中文字幕在线视频 | 毛片基地黄久久久久久天堂 | 中文字幕一区二区三区精华液 | www.99久久.com | 成人在线观看免费视频 | 麻豆影视在线观看 | 久久99国产精品二区护士 | 免费在线观看黄网站 | 欧美亚洲xxx | 国产在线视频一区二区 | 久久超级碰 | 久久午夜电影 | 久久精品国产亚洲精品2020 | 久久综合色影院 | 在线成人看片 | 国产精品白虎 | 国产视频九色蝌蚪 | 91视频免费看网站 | 久久久国产精品网站 | 黄色小说在线观看视频 | 超碰97中文 | 中文字幕传媒 | 三级黄色免费 | adc在线观看 | 99久久这里有精品 | 国内精品久久久久久中文字幕 | 国产三级国产精品国产专区50 | 密桃av在线 | 97成人超碰| 国内久久精品 | 亚洲国内精品 | 精品91| 一区二区三区在线观看免费视频 | 久久视频网址 | 五月婷丁香 | avlulu久久精品 | 欧美精品久久人人躁人人爽 | 亚洲va在线va天堂va偷拍 | 欧美十八 | 亚洲亚洲精品在线观看 | 国产高清视频在线播放 | 久久草网 | a色视频| 久久国产精品二国产精品中国洋人 | 欧美人人爱 | 久久综合久久综合九色 | 亚洲va在线va天堂va偷拍 | 99视频精品在线 | 四川妇女搡bbbb搡bbbb搡 | 日韩欧美视频免费在线观看 | 天天操操操操操 | 国产精品美女久久久久久 | 久久观看| 久久久久亚洲精品成人网小说 | 91九色视频在线观看 | 新版资源中文在线观看 | 91久久精品日日躁夜夜躁国产 | 一区二区视频电影在线观看 | 黄色片毛片 | 日本中文字幕视频 | 国产一区二区日本 | 我要看黄色一级片 | 国产成人免费在线观看 | 欧美成年网站 | 天天射天天搞 | 欧美日韩国产高清视频 | 亚洲精品国久久99热 | 国产精品一区二区久久久久 | 在线亚洲小视频 | 91一区二区三区在线观看 | 永久免费精品视频 | 一区二区三区在线观看 | 欧美日韩大片在线观看 | 中文字幕丝袜一区二区 | 欧美91av| 婷婷在线网 | 国产精品久久久久久久久久ktv | 中文字幕久久久精品 | 少妇超碰在线 | 国产一级精品绿帽视频 | 久久国产精品二国产精品中国洋人 | 黄网站色成年免费观看 | 五月婷婷激情网 | 国产黄色精品 | 久久黄色免费 | 久久在线精品视频 | 亚洲免费在线观看视频 | 91av电影| www.干| 玖玖在线资源 | 欧美色操| 91看片淫黄大片一级在线观看 | 免费观看久久 | 久久免费久久 | 2023年中文无字幕文字 | 免费一级毛毛片 | 欧洲在线免费视频 | 欧美日韩不卡在线观看 | 婷婷六月中文字幕 | 亚洲美女在线国产 | 在线免费高清一区二区三区 | 久久香蕉国产精品麻豆粉嫩av | 久久视频中文字幕 | 韩国精品福利一区二区三区 | 欧美在线aa | 国产玖玖在线 | 最近在线中文字幕 | 日韩肉感妇bbwbbwbbw | 欧美天天综合网 | 午夜精品久久久 | 久久成人精品电影 | 五月婷婷中文字幕 | 日韩亚洲精品电影 | 中文字幕第一页在线播放 | 91精品久久久久久久久 | 伊人午夜视频 | 国产精品永久免费观看 | 丁香激情综合久久伊人久久 | 日韩,精品电影 | 午夜三级大片 | 欧美aaaxxxx做受视频 | 超碰日韩在线 | 69视频国产 | 在线视频福利 | 国产精品视频久久久 | 天天操天天射天天爱 | 午夜精品一区二区三区在线 | 日韩女同一区二区三区在线观看 | 不卡的av中文字幕 | 91av蜜桃 | 97在线视频免费 | 精品国产91亚洲一区二区三区www | av电影中文字幕在线观看 | 午夜av在线播放 | 九九热中文字幕 | 狠狠地操| 青青草国产精品视频 | 久久99在线视频 | 国内揄拍国内精品 | 91成品人影院 | 国产在线不卡一区 | 亚洲精品在线观看不卡 | 中文字幕色在线 | 国产亚洲成av人片在线观看桃 | 日韩黄色在线电影 | 毛片一二区 | 色多多视频在线观看 | 国产成人三级一区二区在线观看一 | 欧美日韩免费一区二区 | 91麻豆产精品久久久久久 | 中文字幕日韩高清 | 狠狠躁日日躁狂躁夜夜躁av | 成人免费视频在线观看 | 免费的黄色av | 婷婷视频在线观看 | 特级黄色一级 | 国产在线91精品 | 日本精品视频免费 | 日韩成人一级大片 | 国产女人18毛片水真多18精品 | 婷婷免费视频 | 国产老熟 | 黄色av一级片 | 午夜999| 狠狠色噜噜狠狠狠狠 | 久久国产视频网 | 探花视频在线观看免费 | 五月婷婷久草 | 亚洲最新视频在线 | 欧美日韩国产在线观看 | 麻豆影视在线免费观看 | 日韩激情影院 | 日韩av手机在线观看 | 日韩理论电影在线观看 | 日韩精品无 | 免费人做人爱www的视 | 欧美日韩一区二区在线 | 亚洲成人家庭影院 | 国产黄色片久久久 | 97在线观看免费高清 | 国产在线1区 | 日韩一区二区三区高清在线观看 | 久久国产精品视频 | 日本中文字幕在线播放 | 久草在线播放视频 | 国产黄色片免费在线观看 | 国产资源在线视频 | 99在线精品视频观看 | 最近能播放的中文字幕 | 亚洲区另类春色综合小说 | 国产黄a三级三级三级三级三级 | 91成人精品观看 | 欧美一区影院 | 三级av中文字幕 | 久久论理 | 成人日韩av| 亚洲污视频 | 韩国三级一区 | 在线黄网站 | 日本aaa在线观看 | 96视频免费在线观看 | 国产中出在线观看 | 国内视频| 欧美巨大荫蒂茸毛毛人妖 | 欧美人体xx | 夜夜骑日日 | 黄色91免费观看 | 日韩精品久久久久久久电影竹菊 | 精品一区二区综合 | 国产精品99久久久久久有的能看 | 97**国产露脸精品国产 | 久久艹国产 | 一区二区不卡 | 天天摸天天弄 | 国产中文伊人 | 99视频在线观看视频 | 伊人色综合久久天天网 | 国产亚洲资源 | 97爱| 午夜久久久久久久久久影院 | 免费观看成年人视频 | 国产剧情一区二区 | 成人在线视频观看 | 在线看免费 | 日韩理论在线播放 | 亚洲日b视频 | 亚洲精品欧美视频 | 天天鲁一鲁摸一摸爽一爽 | 中文字幕在线久一本久 | 日韩精品一区不卡 | 91桃色在线播放 | 欧美日韩xx | 狠狠操.com| 国产视频第二页 | 中文字幕亚洲精品日韩 | 欧美孕妇与黑人孕交 | 久久综合色一综合色88 | 久久久久成人精品 | 久青草视频在线观看 | 精品国产伦一区二区三区观看方式 | 日韩一区二区三区高清免费看看 | 天天插日日插 | 玖玖精品视频 | 国产精品一区二区在线看 | 精品国产人成亚洲区 | 在线看v片成人 | 黄色av电影在线 | 久久艹精品 | 国内精品久久久久久中文字幕 | 亚洲一区二区三区在线看 | 欧美日韩国产二区 | 在线观看亚洲精品 | 黄色aaa级片 | 国产视频精品久久 | 国产免费视频在线 | 精品国产激情 | 欧美精品久久久久久久久久久 | 色视频在线观看 | 人人爽人人爽人人爽人人爽 | 久久国产精品99久久久久 | 午夜色站| 在线观看视频99 | 91麻豆精品国产91久久久久久 | 69精品在线观看 | 日韩一区二区在线免费观看 | 天天射天天爽 | 国产精品久久久久久一区二区三区 | 麻豆视频免费入口 | 免费a网 | 天天夜夜亚洲 | 日韩乱色精品一区二区 | 日韩精品一区二区免费视频 | 一本一本久久a久久精品牛牛影视 | 十八岁以下禁止观看的1000个网站 | 911久久香蕉国产线看观看 | 九九热在线免费观看 | 亚洲国产影院av久久久久 | 中文字幕第| 99久久精品免费一区 | 99视频在线精品国自产拍免费观看 | 国产精品18毛片一区二区 | 国产精品久久久久高潮 | 国产亚洲精品久久久久久大师 | 精品伊人久久久 | 中文字幕在线观看视频一区二区三区 | 天天爱天天射天天干天天 | 人人澡人人爽欧一区 | 久草免费色站 | 97在线观看免费观看高清 | av在线成人 | 婷婷久久精品 | 国产小视频在线 | 亚洲精品午夜国产va久久成人 | wwwwwww黄 | www色综合 | 91亚色在线观看 | 黄色大片日本 | 欧美另类v | 国产香蕉97碰碰久久人人 | 久草在线费播放视频 | 久久综合久久伊人 | 91视频国产免费 | 麻豆国产精品永久免费视频 | 亚洲精品国产自产拍在线观看 | 国产成人一区二 | 五月天激情视频在线观看 | 久久久精品视频网站 | 热久久免费视频精品 | 天堂v中文 | 国产日韩欧美在线观看视频 | 成人亚洲欧美 | 六月丁香婷| 免费a网 | 日韩专区中文字幕 | 97碰碰精品嫩模在线播放 | 美女视频黄免费网站 | 久久久精品免费观看 | 在线观看成人av | 99久久精品国产一区二区三区 | 丁香婷婷激情国产高清秒播 | 久久香蕉影视 | 色噜噜在线观看视频 | 91看片成人| 久久电影国产免费久久电影 | 国产精品久久久久久久久久久不卡 | 在线观看午夜av | 亚洲精品自拍视频在线观看 | 天天操综合 | 狠狠综合 | 日韩欧美一区视频 | 三级在线视频观看 | 久久免费激情视频 | 亚洲成人动漫在线观看 | 六月色 | 久久久人人人 | 亚洲精品国产综合99久久夜夜嗨 | 久久激情五月激情 | 麻豆视频免费网站 | 一区二区三区四区五区在线 | 成人h电影在线观看 | 久久国产精品精品国产色婷婷 | 天天色播 | 亚洲精品女 | 欧美人交a欧美精品 | 午夜精品福利在线 | 久久精品亚洲国产 | 黄色精品视频 | 国产一在线精品一区在线观看 | 亚洲第一av在线播放 | 国产精品青草综合久久久久99 | 69国产在线观看 | 国产精品久久久久影视 | 黄色片视频免费 | 九九热免费精品视频 | 国产精品黄网站在线观看 | 久久爱影视i | 国产精品久久久久久久午夜片 | 免费视频区 | 国产精品大全 | 久久国产精品免费观看 | 亚洲色图激情文学 |