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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

面经——JVM

發布時間:2024/2/28 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 面经——JVM 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

面經——JVM


目錄

  • JVM運行時內存劃分?PC+虛擬機棧+本地方法棧+堆+方法區+JDK1.7與1.8區別
  • 創建一個對象的步驟
  • 介紹下 Java 內存區域(運行時數據區)
  • Java 對象的創建過程(五步,建議能默寫出來并且要知道每一步虛擬機做了什么)
  • 對象的訪問定位的兩種方式(句柄和直接指針兩種方式)
  • 如何判斷對象是否死亡(兩種方法)。
  • 簡單的介紹一下強引用、軟引用、弱引用、虛引用(虛引用與軟引用和弱引用的區別、使用軟引用能帶來的好處)。
  • 如何判斷一個常量是廢棄常量
  • 如何判斷一個類是無用的類
  • 垃圾收集有哪些算法,各自的特點?
  • HotSpot 為什么要分為新生代和老年代?
  • 常見的垃圾回收器有哪些?
  • 介紹一下 CMS,G1 收集器。
  • Minor Gc 和 Full GC 有什么不同呢?
  • 類加載過程
  • 注:題目從牛客 Java部門面經整理而來,題目的解釋很多轉載自 JavaGuide 大佬的文章和參考其他一些博客,因為總結的太好了,就照搬了。
    2020秋招面經大匯總!(崗位劃分)
    Java 學習/面試指南


    1. JVM運行時內存劃分?PC+虛擬機棧+本地方法棧+堆+方法區+JDK1.7與1.8區別

    2. 創建一個對象的步驟

    3. 介紹下 Java 內存區域(運行時數據區)

    4. Java 對象的創建過程(五步,建議能默寫出來并且要知道每一步虛擬機做了什么)

    5. 對象的訪問定位的兩種方式(句柄和直接指針兩種方式)

    一 概述

    對于 Java 程序員來說,在虛擬機自動內存管理機制下,不再需要像 C/C++程序開發程序員這樣為每一個 new 操作去寫對應的 delete/free 操作,不容易出現內存泄漏和內存溢出問題。正是因為 Java 程序員把內存控制權利交給 Java 虛擬機,一旦出現內存泄漏和溢出方面的問題,如果不了解虛擬機是怎樣使用內存的,那么排查錯誤將會是一個非常艱巨的任務。

    二 運行時數據區域

    Java 虛擬機在執行 Java 程序的過程中會把它管理的內存劃分成若干個不同的數據區域。JDK. 1.8 和之前的版本略有不同,下面會介紹到。
    JDK 1.8 之前:

    JDK 1.8 :

    線程私有的:

    • 程序計數器
    • 虛擬機棧
    • 本地方法棧

    線程共享的:

    • 方法區
    • 直接內存 (非運行時數據區的一部分)
    2.1 程序計數器

    程序計數器是一塊較小的內存空間,可以看作是當前線程所執行的字節碼的行號指示器。字節碼解釋器工作時通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等功能都需要依賴這個計數器來完成。

    另外,為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各線程之間計數器互不影響,獨立存儲,我們稱這類內存區域為“線程私有”的內存。

    從上面的介紹中我們知道程序計數器主要有兩個作用:

  • 字節碼解釋器通過改變程序計數器來依次讀取指令,從而實現代碼的流程控制,如:順序執行、選擇、循環、異常處理。
  • 在多線程的情況下,程序計數器用于記錄當前線程執行的位置,從而當線程被切換回來的時候能夠知道該線程上次運行到哪兒了。
  • 注意:程序計數器是唯一一個不會出現 OutOfMemoryError 的內存區域,它的生命周期隨著線程的創建而創建,隨著線程的結束而死亡。

    2.2 Java 虛擬機棧

    與程序計數器一樣,Java 虛擬機棧也是線程私有的,它的生命周期和線程相同,描述的是 Java 方法執行的內存模型,每次方法調用的數據都是通過棧傳遞的。

    Java 內存可以粗糙的區分為堆內存(Heap)和棧內存 (Stack),其中棧就是現在說的虛擬機棧,或者說是虛擬機棧中局部變量表部分。 (實際上,Java 虛擬機棧是由一個個棧幀組成,而每個棧幀中都擁有:局部變量表、操作數棧、動態鏈接、方法出口信息。)

    局部變量表主要存放了編譯器可知的各種數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference 類型,它不同于對象本身,可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置)。

    Java 虛擬機棧會出現兩種錯誤:StackOverFlowError 和 OutOfMemoryError。

    • StackOverFlowError: 若 Java 虛擬機棧的內存大小不允許動態擴展,那么當線程請求棧的深度超過當前 Java 虛擬機棧的最大深度的時候,就拋出 StackOverFlowError 錯誤。
    • OutOfMemoryError: 若 Java 虛擬機棧的內存大小允許動態擴展,且當線程請求棧時內存用完了,無法再動態擴展了,此時拋出 OutOfMemoryError 錯誤。

    Java 虛擬機棧也是線程私有的,每個線程都有各自的 Java 虛擬機棧,而且隨著線程的創建而創建,隨著線程的死亡而死亡。

    擴展:那么方法/函數如何調用?
    Java 棧可用類比數據結構中棧,Java 棧中保存的主要內容是棧幀,每一次函數調用都會有一個對應的棧幀被壓入 Java 棧,每一個函數調用結束后,都會有一個棧幀被彈出。

    Java 方法有兩種返回方式:

  • return 語句。
  • 拋出異常。
  • 不管哪種返回方式都會導致棧幀被彈出。

    2.3 本地方法棧

    和虛擬機棧所發揮的作用非常相似,區別是: 虛擬機棧為虛擬機執行 Java 方法 (也就是字節碼)服務,而本地方法棧則為虛擬機使用到的 Native 方法服務。 在 HotSpot 虛擬機中和 Java 虛擬機棧合二為一。

    本地方法被執行的時候,在本地方法棧也會創建一個棧幀,用于存放該本地方法的局部變量表、操作數棧、動態鏈接、出口信息。

    方法執行完畢后相應的棧幀也會出棧并釋放內存空間,也會出現 StackOverFlowError 和 OutOfMemoryError 兩種錯誤。

    2.4 堆

    Java 虛擬機所管理的內存中最大的一塊,Java 堆是所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例以及數組都在這里分配內存。

    Java 堆是垃圾收集器管理的主要區域,因此也被稱作GC 堆(Garbage Collected Heap)。從垃圾回收的角度,由于現在收集器基本都采用分代垃圾收集算法,所以 Java 堆還可以細分為:新生代和老年代。再細致一點有:Eden 空間、From Survivor、To Survivor 空間等。進一步劃分的目的是更好地回收內存,或者更快地分配內存。

    在 JDK 7 版本及JDK 7 版本之前,堆內存被通常被分為下面三部分:

  • 新生代內存(Young Generation)
  • 老生代(Old Generation)
  • 永生代(Permanent Generation)
  • JDK 8 版本之后方法區(HotSpot 的永久代)被徹底移除了(JDK1.7 就已經開始了),取而代之是元空間,元空間使用的是直接內存。


    上圖所示的 Eden 區、兩個 Survivor 區都屬于新生代(為了區分,這兩個 Survivor 區域按照順序被命名為 from 和 to),中間一層屬于老年代。

    大部分情況,對象都會首先在 Eden 區域分配,在一次新生代垃圾回收后,如果對象還存活,則會進入 s0 或者 s1,并且對象的年齡還會加 1(Eden 區->Survivor 區后對象的初始年齡變為 1),當它的年齡增加到一定程度(默認為 15 歲),就會被晉升到老年代中。對象晉升到老年代的年齡閾值,可以通過參數 -XX:MaxTenuringThreshold 來設置。

    修正(issue552):“Hotspot遍歷所有對象時,按照年齡從小到大對其所占用的大小進行累積,當累積的某個年齡大小超過了survivor區的一半時,取這個年齡和MaxTenuringThreshold中更小的一個值,作為新的晉升年齡閾值”。

    動態年齡計算的代碼如下

    uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {//survivor_capacity是survivor空間的大小size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);size_t total = 0;uint age = 1;while (age < table_size) {total += sizes[age];//sizes數組是每個年齡段對象大小if (total > desired_survivor_size) break;age++;}uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;... }

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

    • OutOfMemoryError: GC Overhead Limit Exceeded : 當JVM花太多時間執行垃圾回收并且只能回收很少的堆空間時,就會發生此錯誤。
    • java.lang.OutOfMemoryError: Java heap space :假如在創建新的對象時, 堆內存中的空間不足以存放新創建的對象, 就會引發 java.lang.OutOfMemoryError: Java heap space 錯誤。(和本機物理內存無關,和你配置的對內存大小有關!)
    2.5 方法區

    方法區與 Java 堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。雖然 Java 虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做 Non-Heap(非堆),目的應該是與 Java 堆區分開來。

    方法區也被稱為永久代。很多人都會分不清方法區和永久代的關系,為此我也查閱了文獻。

    2.5.1 方法區和永久代的關系

    《Java 虛擬機規范》只是規定了有方法區這么個概念和它的作用,并沒有規定如何去實現它。那么,在不同的 JVM 上方法區的實現肯定是不同的了。 方法區和永久代的關系很像 Java 中接口和類的關系,類實現了接口,而永久代就是 HotSpot 虛擬機對虛擬機規范中方法區的一種實現方式。 也就是說,永久代是 HotSpot 的概念,方法區是 Java 虛擬機規范中的定義,是一種規范,而永久代是一種實現,一個是標準一個是實現,其他的虛擬機實現并沒有永久代這一說法。

    2.5.2 常用參數

    JDK 1.8 之前永久代還沒被徹底移除的時候通常通過下面這些參數來調節方法區大小

    -XX:PermSize=N //方法區 (永久代) 初始大小
    -XX:MaxPermSize=N //方法區 (永久代) 最大大小,超過這個值將會拋出 OutOfMemoryError 異常:java.lang.OutOfMemoryError: PermGen

    相對而言,垃圾收集行為在這個區域是比較少出現的,但并非數據進入方法區后就“永久存在”了。

    JDK 1.8 的時候,方法區(HotSpot 的永久代)被徹底移除了(JDK1.7 就已經開始了),取而代之是元空間,元空間使用的是直接內存。

    下面是一些常用參數:

    -XX:MetaspaceSize=N //設置 Metaspace 的初始(和最小大小)
    -XX:MaxMetaspaceSize=N //設置 Metaspace 的最大大小

    與永久代很大的不同就是,如果不指定大小的話,隨著更多類的創建,虛擬機會耗盡所有可用的系統內存。

    2.5.3 為什么要將永久代 (PermGen) 替換為元空間 (MetaSpace) 呢?

    整個永久代有一個 JVM 本身設置固定大小上限,無法進行調整,而元空間使用的是直接內存,受本機可用內存的限制,雖然元空間仍舊可能溢出,但是比原來出現的幾率會更小。
    當你元空間溢出時會得到如下錯誤: java.lang.OutOfMemoryError: MetaSpace

    你可以使用 -XX:MaxMetaspaceSize 標志設置最大元空間大小,默認值為 unlimited,這意味著它只受系統內存的限制。-XX:MetaspaceSize 調整標志定義元空間的初始大小,如果未指定此標志,則 Metaspace 將根據運行時的應用程序需求動態地重新調整大小。

    元空間里面存放的是類的元數據,這樣加載多少類的元數據就不由 MaxPermSize 控制了, 而由系統的實際可用空間來控制,這樣能加載的類就更多了。

    在 JDK8,合并 HotSpot 和 JRockit 的代碼時, JRockit 從來沒有一個叫永久代的東西, 合并之后就沒有必要額外的設置這么一個永久代的地方了。


    2.6 運行時常量池

    運行時常量池是方法區的一部分。Class 文件中除了有類的版本、字段、方法、接口等描述信息外,還有常量池信息(用于存放編譯期生成的各種字面量和符號引用)

    既然運行時常量池是方法區的一部分,自然受到方法區內存的限制,當常量池無法再申請到內存時會拋出 OutOfMemoryError 錯誤。

    JDK1.7 及之后版本的 JVM 已經將運行時常量池從方法區中移了出來,在 Java 堆(Heap)中開辟了一塊區域存放運行時常量池。


    2.7 直接內存

    直接內存并不是虛擬機運行時數據區的一部分,也不是虛擬機規范中定義的內存區域,但是這部分內存也被頻繁地使用。而且也可能導致 OutOfMemoryError 錯誤出現。

    JDK1.4 中新加入的 NIO(New Input/Output) 類,引入了一種基于通道(Channel) 與緩存區(Buffer) 的 I/O 方式,它可以直接使用 Native 函數庫直接分配堆外內存,然后通過一個存儲在 Java 堆中的 DirectByteBuffer 對象作為這塊內存的引用進行操作。這樣就能在一些場景中顯著提高性能,因為避免了在 Java 堆和 Native 堆之間來回復制數據。

    本機直接內存的分配不會受到 Java 堆的限制,但是,既然是內存就會受到本機總內存大小以及處理器尋址空間的限制。


    三 HotSpot 虛擬機對象探秘

    通過上面的介紹我們大概知道了虛擬機的內存情況,下面我們來詳細的了解一下 HotSpot 虛擬機在 Java 堆中對象分配、布局和訪問的全過程。

    3.1 對象的創建

    下圖便是 Java 對象的創建過程,我建議最好是能默寫出來,并且要掌握每一步在做什么。

    Step1:類加載檢查
    虛擬機遇到一條 new 指令時,首先將去檢查這個指令的參數是否能在常量池中定位到這個類的符號引用,并且檢查這個符號引用代表的類是否已被加載過、解析和初始化過。如果沒有,那必須先執行相應的類加載過程。

    Step2:分配內存
    類加載檢查通過后,接下來虛擬機將為新生對象分配內存。對象所需的內存大小在類加載完成后便可確定,為對象分配空間的任務等同于把一塊確定大小的內存從 Java 堆中劃分出來。分配方式有 “指針碰撞” 和 “空閑列表” 兩種,選擇哪種分配方式由 Java 堆是否規整決定,而 Java 堆是否規整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。

    內存分配的兩種方式:(補充內容,需要掌握)

    選擇以上兩種方式中的哪一種,取決于 Java 堆內存是否規整。而 Java 堆內存是否規整,取決于 GC 收集器的算法是"標記-清除",還是"標記-整理"(也稱作"標記-壓縮"),值得注意的是,復制算法內存也是規整的

    內存分配并發問題(補充內容,需要掌握)

    在創建對象的時候有一個很重要的問題,就是線程安全,因為在實際開發過程中,創建對象是很頻繁的事情,作為虛擬機來說,必須要保證線程是安全的,通常來講,虛擬機采用兩種方式來保證線程安全:

    • CAS+失敗重試: CAS 是樂觀鎖的一種實現方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。虛擬機采用 CAS 配上失敗重試的方式保證更新操作的原子性。
    • TLAB: 為每一個線程預先在 Eden 區分配一塊兒內存,JVM 在給線程中的對象分配內存時,首先在 TLAB 分配,當對象大于 TLAB 中的剩余內存或 TLAB 的內存已用盡時,再采用上述的 CAS 進行內存分配

    Step3:初始化零值
    內存分配完成后,虛擬機需要將分配到的內存空間都初始化為零值(不包括對象頭),這一步操作保證了對象的實例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數據類型所對應的零值。

    Step4:設置對象頭
    初始化零值完成之后,虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的 GC 分代年齡等信息。 這些信息存放在對象頭中。 另外,根據虛擬機當前運行狀態的不同,如是否啟用偏向鎖等,對象頭會有不同的設置方式。

    Step5:執行 init 方法
    在上面工作都完成之后,從虛擬機的視角來看,一個新的對象已經產生了,但從 Java 程序的視角來看,對象創建才剛開始, 方法還沒有執行,所有的字段都還為零。所以一般來說,執行 new 指令之后會接著執行 方法,把對象按照程序員的意愿進行初始化,這樣一個真正可用的對象才算完全產生出來。

    3.2 對象的內存布局

    在 Hotspot 虛擬機中,對象在內存中的布局可以分為 3 塊區域:對象頭、實例數據對齊填充

    Hotspot 虛擬機的對象頭包括兩部分信息,第一部分用于存儲對象自身的自身運行時數據(哈希碼、GC 分代年齡、鎖狀態標志等等),另一部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

    實例數據部分是對象真正存儲的有效信息,也是在程序中所定義的各種類型的字段內容。

    對齊填充部分不是必然存在的,也沒有什么特別的含義,僅僅起占位作用。 因為 Hotspot 虛擬機的自動內存管理系統要求對象起始地址必須是 8 字節的整數倍,換句話說就是對象的大小必須是 8 字節的整數倍。而對象頭部分正好是 8 字節的倍數(1 倍或 2 倍),因此,當對象實例數據部分沒有對齊時,就需要通過對齊填充來補全。

    3.3 對象的訪問定位

    建立對象就是為了使用對象,我們的 Java 程序通過棧上的 reference 數據來操作堆上的具體對象。對象的訪問方式由虛擬機實現而定,目前主流的訪問方式有①使用句柄②直接指針兩種

    1. 句柄: 如果使用句柄的話,那么 Java 堆中將會劃分出一塊內存來作為句柄池,reference 中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址信息;

    2. 直接指針: 如果使用直接指針訪問,那么 Java 堆對象的布局中就必須考慮如何放置訪問類型數據的相關信息,而 reference 中存儲的直接就是對象的地址。

    這兩種對象訪問方式各有優勢。使用句柄來訪問的最大好處是 reference 中存儲的是穩定的句柄地址,在對象被移動時只會改變句柄中的實例數據指針,而 reference 本身不需要修改。使用直接指針訪問方式最大的好處就是速度快,它節省了一次指針定位的時間開銷。

    四 重點補充內容

    4.1 String 類和常量池

    String 對象的兩種創建方式:

    String str1 = "abcd"; //先檢查字符串常量池中有沒有"abcd",如果字符串常量池中沒有,則創建一個,然后 str1 指向字符串常量池中的對象,如果有,則直接將 str1 指向"abcd""; String str2 = new String("abcd");//堆中創建一個新的對象 String str3 = new String("abcd");//堆中創建一個新的對象 System.out.println(str1==str2);//false System.out.println(str2==str3);//false

    這兩種不同的創建方法是有差別的。

    • 第一種方式是在常量池中拿對象;
    • 第二種方式是直接在堆內存空間創建一個新的對象。

    記住一點:只要使用 new 方法,便需要創建新的對象。

    再給大家一個圖應該更容易理解:

    String 類型的常量池比較特殊。它的主要使用方法有兩種:

    • 直接使用雙引號聲明出來的 String 對象會直接存儲在常量池中。
    • 如果不是用雙引號聲明的 String 對象,可以使用 String 提供的 intern 方法。String.intern() 是一個 Native 方法,它的作用是:如果運行時常量池中已經包含一個等于此 String 對象內容的字符串,則返回常量池中該字符串的引用;如果沒有,JDK1.7之前(不包含1.7)的處理方式是在常量池中創建與此 String 內容相同的字符串,并返回常量池中創建的字符串的引用,JDK1.7以及之后的處理方式是在常量池中記錄此字符串的引用,并返回該引用。
    String s1 = new String("計算機");String s2 = s1.intern();String s3 = "計算機";System.out.println(s2);//計算機System.out.println(s1 == s2);//false,因為一個是堆內存中的 String 對象一個是常量池中的 String 對象,System.out.println(s3 == s2);//true,因為兩個都是常量池中的 String 對象

    字符串拼接:

    String str1 = "str";String str2 = "ing";String str3 = "str" + "ing";//常量池中的對象String str4 = str1 + str2; //在堆上創建的新的對象 String str5 = "string";//常量池中的對象System.out.println(str3 == str4);//falseSystem.out.println(str3 == str5);//trueSystem.out.println(str4 == str5);//false


    盡量避免多個字符串拼接,因為這樣會重新創建對象。如果需要改變字符串的話,可以使用 StringBuilder 或者 StringBuffer。

    4.2 String s1 = new String(“abc”);這句話創建了幾個字符串對象?

    將創建 1 或 2 個字符串。如果池中已存在字符串常量“abc”,則只會在堆空間創建一個字符串常量“abc”。如果池中沒有字符串常量“abc”,那么它將首先在池中創建,然后在堆空間中創建,因此將創建總共 2 個字符串對象。

    驗證

    String s1 = new String("abc");// 堆內存的地址值String s2 = "abc";System.out.println(s1 == s2);// 輸出 false,因為一個是堆內存,一個是常量池的內存,故兩者是不同的。System.out.println(s1.equals(s2));// 輸出 true

    結果

    false true
    4.3 8 種基本類型的包裝類和常量池
    • Java 基本類型的包裝類的大部分都實現了常量池技術,即 Byte,Short,Integer,Long,Character,Boolean;這 5 種包裝類默認創建了數值[-128,127] 的相應類型的緩存數據,但是超出此范圍仍然會去創建新的對象。 為啥把緩存設置為[-128,127]區間?(參見issue/461)性能和資源之間的權衡。
    • 兩種浮點數類型的包裝類 Float,Double 并沒有實現常量池技術
    Integer i1 = 33;Integer i2 = 33;System.out.println(i1 == i2);// 輸出 trueInteger i11 = 333;Integer i22 = 333;System.out.println(i11 == i22);// 輸出 falseDouble i3 = 1.2;Double i4 = 1.2;System.out.println(i3 == i4);// 輸出 false

    Integer 緩存源代碼:

    /** *此方法將始終緩存-128 到 127(包括端點)范圍內的值,并可以緩存此范圍之外的其他值。 */public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}

    應用場景:

    • Integer i1=40;Java 在編譯的時候會直接將代碼封裝成 Integer i1=Integer.valueOf(40);,從而使用常量池中的對象。
    • Integer i1 = new Integer(40);這種情況下會創建新的對象。
    Integer i1 = 40;Integer i2 = new Integer(40);System.out.println(i1==i2);//輸出 false

    Integer 比較更豐富的一個例子:

    Integer i1 = 40;Integer i2 = 40;Integer i3 = 0;Integer i4 = new Integer(40);Integer i5 = new Integer(40);Integer i6 = new Integer(0);System.out.println("i1=i2 " + (i1 == i2));System.out.println("i1=i2+i3 " + (i1 == i2 + i3));System.out.println("i1=i4 " + (i1 == i4));System.out.println("i4=i5 " + (i4 == i5));System.out.println("i4=i5+i6 " + (i4 == i5 + i6)); System.out.println("40=i5+i6 " + (40 == i5 + i6));

    結果:

    i1=i2 true i1=i2+i3 true i1=i4 false i4=i5 false i4=i5+i6 true 40=i5+i6 true

    解釋:

    語句 i4 == i5 + i6,因為+這個操作符不適用于 Integer 對象,首先 i5 和 i6 進行自動拆箱操作,進行數值相加,即 i4 == 40。然后 Integer 對象無法與數值進行直接比較,所以 i4 自動拆箱轉為 int 值 40,最終這條語句轉為 40 == 40 進行數值比較。


    6. 如何判斷對象是否死亡(兩種方法)。

    7. 簡單的介紹一下強引用、軟引用、弱引用、虛引用(虛引用與軟引用和弱引用的區別、使用軟引用能帶來的好處)。

    8. 如何判斷一個常量是廢棄常量

    9. 如何判斷一個類是無用的類

    10. 垃圾收集有哪些算法,各自的特點?

    11. HotSpot 為什么要分為新生代和老年代?

    12. 常見的垃圾回收器有哪些?

    13. 介紹一下 CMS,G1 收集器。

    14. Minor Gc 和 Full GC 有什么不同呢?

    本文導火索


    當需要排查各種內存溢出問題、當垃圾收集成為系統達到更高并發的瓶頸時,我們就需要對這些“自動化”的技術實施必要的監控和調節。

    1 揭開 JVM 內存分配與回收的神秘面紗

    Java 的自動內存管理主要是針對對象內存的回收和對象內存的分配。同時,Java 自動內存管理最核心的功能是 堆內存中對象的分配與回收

    Java 堆是垃圾收集器管理的主要區域,因此也被稱作GC 堆(Garbage Collected Heap).從垃圾回收的角度,由于現在收集器基本都采用分代垃圾收集算法,所以 Java 堆還可以細分為:新生代和老年代:再細致一點有:Eden 空間、From Survivor、To Survivor 空間等。進一步劃分的目的是更好地回收內存,或者更快地分配內存。

    堆空間的基本結構:

    上圖所示的 eden 區、s0(“From”) 區、s1(“To”) 區都屬于新生代,tentired 區屬于老年代。大部分情況,對象都會首先在 Eden 區域分配,在一次新生代垃圾回收后,如果對象還存活,則會進入 s1(“To”),并且對象的年齡還會加 1(Eden 區->Survivor 區后對象的初始年齡變為 1),當它的年齡增加到一定程度(默認為 15 歲),就會被晉升到老年代中。對象晉升到老年代的年齡閾值,可以通過參數 -XX:MaxTenuringThreshold 來設置。經過這次GC后,Eden區和"From"區已經被清空。這個時候,“From"和"To"會交換他們的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To”。不管怎樣,都會保證名為To的Survivor區域是空的。Minor GC會一直重復這樣的過程,直到“To”區被填滿,"To"區被填滿之后,會將所有對象移動到老年代中。

    1.1 對象優先在 eden 區分配

    目前主流的垃圾收集器都會采用分代回收算法,因此需要將堆內存分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集算法。

    大多數情況下,對象在新生代中 eden 區分配。當 eden 區沒有足夠空間進行分配時,虛擬機將發起一次 Minor GC.下面我們來進行實際測試以下。

    在測試之前我們先來看看 Minor GC 和 Full GC 有什么不同呢?

    • 新生代 GC(Minor GC):指發生新生代的的垃圾收集動作,Minor GC 非常頻繁,回收速度一般也比較快。
    • 老年代 GC(Major GC/Full GC):指發生在老年代的 GC,出現了 Major GC 經常會伴隨至少一次的 Minor GC(并非絕對),Major GC 的速度一般會比 Minor GC 的慢 10 倍以上。

    測試:

    public class GCTest {public static void main(String[] args) {byte[] allocation1, allocation2;allocation1 = new byte[30900*1024];//allocation2 = new byte[900*1024];} }

    通過以下方式運行:

    添加的參數:-XX:+PrintGCDetails

    運行結果 (紅色字體描述有誤,應該是對應于 JDK1.7 的永久代):

    從上圖我們可以看出 eden 區內存幾乎已經被分配完全(即使程序什么也不做,新生代也會使用 2000 多 k 內存)。假如我們再為 allocation2 分配內存會出現什么情況呢?

    allocation2 = new byte[900*1024];


    簡單解釋一下為什么會出現這種情況: 因為給 allocation2 分配內存的時候 eden 區內存幾乎已經被分配完了,我們剛剛講了當 Eden 區沒有足夠空間進行分配時,虛擬機將發起一次 Minor GC.GC 期間虛擬機又發現 allocation1 無法存入 Survivor 空間,所以只好通過 分配擔保機制 把新生代的對象提前轉移到老年代中去,老年代上的空間足夠存放 allocation1,所以不會出現 Full GC。執行 Minor GC 后,后面分配的對象如果能夠存在 eden 區的話,還是會在 eden 區分配內存。可以執行如下代碼驗證:

    public class GCTest {public static void main(String[] args) {byte[] allocation1, allocation2,allocation3,allocation4,allocation5;allocation1 = new byte[32000*1024];allocation2 = new byte[1000*1024];allocation3 = new byte[1000*1024];allocation4 = new byte[1000*1024];allocation5 = new byte[1000*1024];} }
    1.2 大對象直接進入老年代

    大對象就是需要大量連續內存空間的對象(比如:字符串、數組)。大對象對虛擬機的內存分配來說就是一個壞消息,經常出現大對象容易導致內存還有不少空間時就提前觸發垃圾收集以獲取足夠的連續空間來“安置”它們。

    為什么要這樣呢?

    為了避免為大對象分配內存時由于分配擔保機制帶來的復制而降低效率。

    虛擬機提供了一個-XX:PretenureSizeThreshold參數,令大于這個設置值的對象直接在老年代分配。這樣做的目的是避免在Eden區及兩個Survivor區之間發生大量的內存復制(新生代采用復制算法收集內存)。

    注意 PretenureSizeThreshold參數只對Serial和ParNew兩款收集器有效,Parallel Scavenge收集器不認識這個參數,Parallel Scavenge收集器一般并不需要設置。如果遇到必須使用此參數的場合,可以考慮ParNew加CMS的收集器組合。

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

    既然虛擬機采用了分代收集的思想來管理內存,那么內存回收時就必須能識別哪些對象應放在新生代,哪些對象應放在老年代中。為了做到這一點,虛擬機給每個對象一個對象年齡(Age)計數器。

    如果對象在 Eden 出生并經過第一次 Minor GC 后仍然能夠存活,并且能被 Survivor 容納的話,將被移動到 Survivor 空間中,并將對象年齡設為 1.對象在 Survivor 中每熬過一次 MinorGC,年齡就增加 1 歲,當它的年齡增加到一定程度(默認為 15 歲),就會被晉升到老年代中。對象晉升到老年代的年齡閾值,可以通過參數 -XX:MaxTenuringThreshold 來設置。

    1.4 動態對象年齡判定

    為了更好的適應不同程序的內存情況,虛擬機不是永遠要求對象年齡必須達到了某個值才能進入老年代,如果 Survivor 空間中相同年齡所有對象大小的總和大于 Survivor 空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無需達到要求的年齡。

    2 對象已經死亡?

    堆中幾乎放著所有的對象實例,對堆垃圾回收前的第一步就是要判斷那些對象已經死亡(即不能再被任何途徑使用的對象)。

    2.1 引用計數法

    給對象中添加一個引用計數器,每當有一個地方引用它,計數器就加 1;當引用失效,計數器就減 1;任何時候計數器為 0 的對象就是不可能再被使用的。

    這個方法實現簡單,效率高,但是目前主流的虛擬機中并沒有選擇這個算法來管理內存,其最主要的原因是它很難解決對象之間相互循環引用的問題。 所謂對象之間的相互引用問題,如下面代碼所示:除了對象 objA 和 objB 相互引用著對方之外,這兩個對象之間再無任何引用。但是他們因為互相引用對方,導致它們的引用計數器都不為 0,于是引用計數算法無法通知 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;} }
    2.2 可達性分析算法

    這個算法的基本思想就是通過一系列的稱為 “GC Roots” 的對象作為起點,從這些節點開始向下搜索,節點所走過的路徑稱為引用鏈,當一個對象到 GC Roots 沒有任何引用鏈相連的話,則證明此對象是不可用的。

    2.3 再談引用

    無論是通過引用計數法判斷對象引用數量,還是通過可達性分析法判斷對象的引用鏈是否可達,判定對象的存活都與“引用”有關。

    JDK1.2 之前,Java 中引用的定義很傳統:如果 reference 類型的數據存儲的數值代表的是另一塊內存的起始地址,就稱這塊內存代表一個引用。

    JDK1.2 以后,Java 對引用的概念進行了擴充,將引用分為強引用、軟引用、弱引用、虛引用四種(引用強度逐漸減弱)

    1.強引用(StrongReference)

    以前我們使用的大部分引用實際上都是強引用,這是使用最普遍的引用。如果一個對象具有強引用,那就類似于必不可少的生活用品,垃圾回收器絕不會回收它。當內存空間不足,Java 虛擬機寧愿拋出 OutOfMemoryError 錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足問題。

    2.軟引用(SoftReference)

    如果一個對象只具有軟引用,那就類似于可有可無的生活用品。如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。

    軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收,JAVA 虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。

    3.弱引用(WeakReference)

    如果一個對象只具有弱引用,那就類似于可有可無的生活用品。弱引用與軟引用的區別在于:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由于垃圾回收器是一個優先級很低的線程, 因此不一定會很快發現那些只具有弱引用的對象。

    弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java 虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。

    4.虛引用(PhantomReference)

    "虛引用"顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。

    虛引用主要用來跟蹤對象被垃圾回收的活動。

    虛引用與軟引用和弱引用的一個區別在于: 虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序如果發現某個虛引用已經被加入到引用隊列,那么就可以在所引用的對象的內存被回收之前采取必要的行動。

    特別注意,在程序設計中一般很少使用弱引用與虛引用,使用軟引用的情況較多,這是因為軟引用可以加速 JVM 對垃圾內存的回收速度,可以維護系統的運行安全,防止內存溢出(OutOfMemory)等問題的產生。

    2.4 不可達的對象并非“非死不可”

    即使在可達性分析法中不可達的對象,也并非是“非死不可”的,這時候它們暫時處于“緩刑階段”,要真正宣告一個對象死亡,至少要經歷兩次標記過程;可達性分析法中不可達的對象被第一次標記并且進行一次篩選,篩選的條件是此對象是否有必要執行 finalize 方法。當對象沒有覆蓋 finalize 方法,或 finalize 方法已經被虛擬機調用過時,虛擬機將這兩種情況視為沒有必要執行。

    被判定為需要執行的對象將會被放在一個低優先級隊列中進行第二次標記,除非這個對象與引用鏈上的任何一個對象建立關聯,否則就會被真的回收。

    2.5 如何判斷一個常量是廢棄常量

    運行時常量池主要回收的是廢棄的常量。那么,我們如何判斷一個常量是廢棄常量呢?

    假如在常量池中存在字符串 “abc”,如果當前沒有任何 String 對象引用該字符串常量的話,就說明常量 “abc” 就是廢棄常量,如果這時發生內存回收的話而且有必要的話,“abc” 就會被系統清理出常量池。

    注意:JDK1.7 及之后版本的 JVM 已經將運行時常量池從方法區中移了出來,在 Java 堆(Heap)中開辟了一塊區域存放運行時常量池。

    2.6 如何判斷一個類是無用的類

    方法區主要回收的是無用的類,那么如何判斷一個類是無用的類的呢?

    判定一個常量是否是“廢棄常量”比較簡單,而要判定一個類是否是“無用的類”的條件則相對苛刻許多。類需要同時滿足下面 3 個條件才能算是 “無用的類” :

  • 該類所有的實例都已經被回收,也就是 Java 堆中不存在該類的任何實例。
  • 加載該類的 ClassLoader 已經被回收。
  • 該類對應的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
  • 虛擬機可以對滿足上述 3 個條件的無用類進行回收,這里說的僅僅是“可以”,而并不是和對象一樣不使用了就會必然被回收。

    3 垃圾收集算法

    3.1 標記-清除算法

    當堆中的有效內存空間(available memory)被耗盡的時候,就會停止整個程序(也被稱為stop the world),然后進行兩項工作,第一項則是標記,第二項則是清除。

  • 標記:從引用根節點開始標記所有的被引用的對象。標記的過程其實就是遍歷所有的GC Roots,然后將所有GC Roots可達的對象標記為存活的對象
  • 清除:遍歷整個堆,把未標記的對象清除。
  • 它是最基礎的收集算法,后續的算法都是對其不足進行改進得到。這種垃圾收集算法會帶來兩個明顯的問題:

  • 效率問題(stop the world)
  • 空間問題(標記清除后會產生大量不連續的碎片)
  • 3.2 復制算法

    為了解決效率問題,“復制”收集算法出現了。它可以將內存分為大小相同的兩塊,每次使用其中的一塊。當這一塊的內存使用完后,就將還存活的對象復制到另一塊去,然后再把使用的空間一次清理掉。這樣就使每次的內存回收都是對內存區間的一半進行回收。復制算法不會產生空間碎片。

    復制算法的缺點:

  • 浪費了一半的內存
  • 如果對象的存活率很高,我們可以極端一點,假設100%存活,那么我們需要將所有對象都復制一遍,并將所有引用地址重置一遍,復制這一工作所花費的時間,在對象存活率達到一定程度時,將會變得不可忽視。
  • 所以從以上的描述中不難看出,復制算法要想使用,最起碼對象的存活率要非常低才行,而且最重要的是,我們必須克服50%內存的浪費。

    3.3 標記-整理算法

    根據老年代的特點提出的一種標記算法,標記過程仍然與“標記-清除”算法一樣,但后續步驟不是直接對可回收對象回收,而是讓所有存活的對象向一端移動,然后直接清理掉端邊界以外的內存。

    標記整理算法的缺點:

  • 效率不高,不僅要標記所有存活對象,還要整理所有存活對象的引用地址。從效率上來說,標記整理算法要低于復制算法。
  • 3.4 分代收集算法

    當前虛擬機的垃圾收集都采用分代收集算法,這種算法沒有什么新的思想,只是根據對象存活周期的不同將內存分為幾塊。一般將 java 堆分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集算法。

    比如在新生代中,每次收集都會有大量對象死去,所以可以選擇復制算法,只需要付出少量對象的復制成本就可以完成每次垃圾收集。而老年代的對象存活幾率是比較高的,而且沒有額外的空間對它進行分配擔保,所以我們必須選擇“標記-清除”或“標記-整理”算法進行垃圾收集。

    延伸面試問題: HotSpot 為什么要分為新生代和老年代?

    根據上面的對分代收集算法的介紹回答。

    4 垃圾收集器



    如果說收集算法是內存回收的方法論,那么垃圾收集器就是內存回收的具體實現。

    雖然我們對各個收集器進行比較,但并非要挑選出一個最好的收集器。因為直到現在為止還沒有最好的垃圾收集器出現,更加沒有萬能的垃圾收集器,我們能做的就是根據具體應用場景選擇適合自己的垃圾收集器。試想一下:如果有一種任何場景下都適用的完美收集器存在,那么我們的 HotSpot 虛擬機就不會實現那么多不同的垃圾收集器了。

    4.1 Serial 收集器

    串行收集器是最古老,最穩定以及效率高的收集器,只使用一個線程去回收但其在進行垃圾收集過程中可能會產生較長的停頓(Stop-The-world狀態)。雖然在收集垃圾過程中需要暫停所有其他的工作線程,但是它簡單高效,對于限定單個CPU環境來說,沒有線程交互的開銷可以獲得最高的單線程垃圾收集效率,因此Serial垃圾收集器依然是Java虛擬機運行在Client模式下默認的新生代垃圾收集器。

    對應JVM參數是:-XX:+UseSerialGC
    開啟后會使用:Serial(young區用)+SerialOld(Old區用)的收集器組合
    表示:新生代、老年代都會使用串行回收收集器,新生代使用復制算法,老年代使用標記-整理算法

    4.2 ParNew 收集器

    ParNew(并行)收集器:使用多線程進行垃圾回收,在垃圾收集時,會Stop-the-World暫停其他所有的工作線程直到它收集結束。
    ParNew收集器其實就是Seria收集器新生代的并行多線程版本,最常見的應用場景是配合老年代的CMS GC工作,其余的行為和Serial收集器完全一樣,ParNew垃圾收集器在垃圾收集過程中同樣也要暫停所有其他的工作線程。它是很多Java虛擬機運行在Server模式下新生代的默認垃圾收集器。

    常用對應JVM參數:-XX:+UseParNewGC,啟用ParNew收集器,只影響新生代的收集,不影響老年代。開啟參數后,會使用:ParNew(Young區用)+SerialOld的收集器組合,新生代使用復制算法,老年代采用標記-整理算法

    但是,ParNew+Tenured這樣的搭配,java8已經不再推薦。

    -XX:ParallelGCThreads 限制線程數量,默認開啟和CPU數目相同的線程數。

    并行和并發概念補充:

    • 并行(Parallel) :指多條垃圾收集線程并行工作,但此時用戶線程仍然處于等待狀態。
    • 并發(Concurrent):指用戶線程與垃圾收集線程同時執行(但不一定是并行,可能會交替執行),用戶程序在繼續運行,而垃圾收集器運行在另一個 CPU 上。
    4.3 Parallel Scavenge 收集器

    Parallel Scavenge收集器類似ParNew也是一個新生代垃圾收集器,使用復制算法,也是一個并行的多線程的垃圾收集器,俗稱吞吐量優先收集器。總結就是串行收集器在新生代和老年代的并行化。

    -XX:+UseParallelGC
    使用 Parallel 收集器+ 老年代串行
    -XX:+UseParallelOldGC
    使用 Parallel 收集器+ 老年代并行

    它重點關注的是:可控制的吞吐量(Thoughput=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),也即比如程序運行100分鐘,垃圾收集時間1分鐘,吞吐量就是99%)。高吞吐量意味著高效利用CPU的時間,它多用于在后臺運算而不需要太多交互的任務。

    自適應調節策略也是ParallelScavenge收集器與ParNew收集器的一個重要區別。(自適應調節策略:虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間(-XX:MaxGCPauseMillis)或最大的吞吐量。

    常用JVM參數:-XX:+UseParaIIeIGC或-XX:+UseParaIIeIOldGC(可互相激活)使用ParallelScanvenge收集器開啟該參數后:新生代使用復制算法,老年代使用標記·整理算法

    補充:
    -XX:ParaIIeIGCThreads=數字N,表示啟動多少個GC線程
    cpu>8,N=5/8
    cpu<8,N=實際個數

    4.4.Serial Old 收集器

    SerialOld是 Serial垃圾收集器老年代版本,它同樣是個單線程的收集器,使用標記-整理算法,這個收集器也主要是運行在Client默認的java虛擬機默認的年老代垃圾收集器。

    在Server模式下,主要有兩個用途(了解版本己經到8及以后):

  • 在JDK1.5之前版本中與新生代的Parallel Scavenge收集器搭配使用。(ParallelScavenge+SerialOld)
  • 作為老年代版中使用CMS收集器的后備垃圾收集方案。
  • 4.5 Parallel Old 收集器

    ParallelOld收集器是ParaScavenge的老年代版本,使用多線程的標記-整理算法,Parallel Old收集器在JDK1.6才開始提供。

    在JDK1.6之前,新生代使用ParallelScavenge收集器只能搭配年老代的SerialOld收集器,只能保證新生代的吞吐量優先,無法保證整體的吞吐量。

    ParallelOld正是為了在年老代同樣提供吞吐量優先的垃圾收集器,如果系統對吞吐量要求比較高,JDK1.8后可以優先考慮新生代ParallelScavenge和年老代Parallel Old收集器的搭配策略。在JDK1.8及后(Parallel Scavenge+Parallel Old)

    JVM常用參數:
    -XX:+UseParallelOldGC使用ParallelOld收集器,設置該參數后,新生代Parallel+老年代ParallelOld

    4.6 CMS 收集器

    CMS收集器(ConcurrentMarkSweep:并發標記清除)是一種以獲取最短回收停頓時間為目標的收集器。適合應用在互聯網站或者B/S系統的服務器上,這類應用尤其重視服務器的響應速度,希望系統停頓時間最短。

    CMS非常適合堆內存大、CPU核數多的服務器端應用,也是G1出現之前大型應用的首選收集器。


    Concurrent Mark Sweep 并發標記清除,并發收集低停頓,并發指的是與用戶線程一起執行。

    開啟該收集器的JVM參數:-XX:+UseConcMarkSreepGC,開啟該參數后會自動將-XX:+UseParNewGC打開。開啟該參數后,使用ParNew(Young區用)+CMS(Old區用)+SerialOld的收集器組合,SerialOld將作為CMS出錯的后備收集器。

    從名字中的Mark Sweep這兩個詞可以看出,CMS 收集器是一種 “標記-清除”算法實現的,它的運作過程相比于前面幾種垃圾收集器來說更加復雜一些。整個過程分為四個步驟:

  • 初始標記(CMS initial mark):只是標記一下GC Roots能直接關聯的對象,速度很快,仍然需要暫停所有的工作線程
  • 并發標記(CMS concurrent mark)和用戶線程一起:進行GC Roots跟蹤的過程,和用戶線程一起工作,不需要暫停工作線程。主要標記過程,標記全部對象
  • 重新標記(CMS remark):為了修正并發標記期間,因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄,仍然需要暫停所有的工作線程。由于并發標記時,用戶線程仍然運行,因此在正式清理前,再做修正。
  • 并發清除(CMS concurrent sweep)和用戶線程一起:清除GC Roots不可達對象,和用戶線程一起工作,不需要暫停工作線程。基于標記結果,直接清理對象。
  • 由于耗時最長的并發標記和并發清除過程中,垃圾收集線程可以和用戶一起并發工作,所以總體上來看CMS收集器的內存回收和用戶線程是一起并發地執行。

    CMS優點:并發收集低停頓

    CMS缺點:

  • 并發執行,對cpu資源壓力大:由于并發進行,CMS在收集與應用線程會同時會增加對堆內存的占用,也就是說,CMS必須要在老年代堆內存用盡之前完成垃圾回收,否則CMS回收失敗時,將觸發擔保機制,串行老年代收集器將會以STW的方式進行上次GC,從而造成較大停頓時間。
  • 采用的標記清除算法會導致大量碎片:標記清除算法無法整理空間碎片,老年代空間會隨著應用時長被逐步耗盡,最后將不得不通過擔保機制對堆內存進行壓縮。CMS也提供了參數 -XX:MSFuIIGCsBeForeCompaction (默認0,即每次都進行內存整理)來指定多少次CMS收集之后,進行一次壓縮的FullGC。
  • 4.7 G1 收集器

    G1(Garbage-Frist)收集器,是一款面向服務端應用的收集器。

    從官網的描述中,我們知道G1是一種服務器端的垃圾收集器,應用在多處理器和大容量內存環境中,在提高吞吐量的同時,盡可能的滿足垃圾收集暫停時間的要求。另外,它還具有以下特性:

  • 像CMS收集器一樣,能與應用程序線程并發執行
  • 整理空閑空間更快
  • 需要更多的時間來預測GC停頓時間
  • 不希望犧牲大量的吞吐性能
  • 不需要更大的Java Heap
  • G1收集器的設計目標是取代CMS收集器,它同CMS相比,在以下方面表現更出色:

  • G1是一個有整理內存過程的垃圾收集器,不會產生很多內存碎片
  • G1的Stop-The-World(STW)更可控,G1在停頓時間上添加了預測機制,用戶可以指定期望停頓時間。
  • CMS垃圾集器雖然減少了暫停應用程序的運行時間,但是它還是存在著內存碎片問題。于是,為了去除內存碎片問題,同時又保留CMS垃圾收集器低暫停時間的優點,JAVA7發布了一個新的垃圾收集器-G1垃圾收集器。

    G1是在2012年才在jdk1.7u4中可用。oracle官方計劃在jdk9中將G1變成默認的垃圾收集器以替代CMS。它是一款面向服務端應用的收集器,主要應用在多CPU和大內存服務器環境下,極大的減少垃圾收集的停頓時間,全面提升服務器的性能,逐步替換java8以前的CMS收集器。

    主要改變是Eden,Survivor和Tenured等內存區域不再是連續的了,而是變成了一個個大小一樣的region,每個region從1M到32M不等。一個region有可能屬于Eden,Survivor或者Tenured內存區域。

    G1特點
  • G1能充分利用多CPU、多核環境硬件優勢,盡量縮短STW
  • G1整體上采用標記-整理算法,局部是通過復制算法,不會產生內存碎片。
  • 宏觀上看G1之中不再區分年輕代和老年代。把內存劃分成多個獨立的了區域(Region),可以近似理解為一個圍棋的棋盤。
  • G1收集器里面講整個的內存區都混合在一起了,但其本身依然在小范圍內要進行年輕代和老年代的區分,保留了新生代和老年代,但它們不再是物理隔離的,而是一部分Region的集合且不需要Region是連續的,也就是說依然會采用不同的GC方式來處理不同的區域。
  • G1雖然也是分代收集器,但整個內存分區不存在物理上的年輕代與老年代的區別,也不需要完全獨立的survivor(tospace)堆做復制
    準備。GI只有邏輯上的分代概念,或者說每個分區都可能隨G1的運行在不同代之間前后切換;
  • G1底層原理
  • Region區域化垃圾收集器

  • 區域化內存劃片Region,整體編為了一系列不連續的內存區域,避免了全內存區的GC操作。
  • 核心思想是將整個堆內存區域分成大小相同的子區域(Region),在JVM啟動時會自動設置這些子區域的大小,在堆的使用上,G1并不要求對象的存儲一定是物理上連續的只要邏輯上連續即可,每個分區也不會固定地為某個代服務,可以按需在年輕代和老年代之間切換。啟動時可以通過參數-XX:G1HeapRegionSize=n可指定分區大小(1MB~32MB,且必須是2的冪),默認將整堆劃分為2048個分區。
  • 大小范圍在1MB~32MB,最多能設置2048個區域,也即能夠支持的最大內存為:32MBx2048=65536MB=64G內存
  • G1算法將堆劃分為若干個區域(Region),它仍然屬于分代收集器

  • 這些Region的一部分包含新生代,新生代的垃圾收集依然采用暫停所有應用線程的方式,將存活對象拷貝到老年代或者survivor空間。

  • 這些Region的一部分包含老年代,G1收集器通過將對象從一個區域復制到另外一個區域,完成了清理工作。這就意味著,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有CMS內存碎片問題的存在了。

  • 在G1中,還有一種特殊的區域,叫Humongous(巨大的)區域
    如果一個對象占用的空間超過了分區容量50%以上,G1收集器就認為這是一個巨型對象。這些巨型對象默認直接會被分配在年老代,但是如果它是一個短期存在的巨型對象,就會對垃圾收集器造成負面影響。為了解決這個問題,G1劃分了一個Humongous區,它用來專門存放巨型對象。如果一個H區裝不下一個巨型對象,那么G1會尋找連續的H分區來存儲。為了能找到連續的H區,有時候不得不啟動Full GC。

  • G1回收步驟

    G1 收集器下的 Young GC

    針對 Eden 區進行收集,Eden 區耗盡后會被觸發,主要是小區域收集 + 形成連續的內存塊,避免內存碎片。

    • Eden 區的數據移動到 Survivor 區,假如出現 Survivor 區空間不夠,Eden 區數據會部分晉升到 Old 區
    • Survivor 區的數據移動到新的 Survivor 區,部分數據晉升到 Old
    • 最后 Eden 區收拾干凈了,GC結束,用戶的應用程序繼續執行



    運行過程:

  • 初始標記:只標記 GC Roots 能直接關聯到的對象
  • 并發標記:進行 GC Roots Tracing 的過程
  • 最終標記:修正并發標記期間,因程序運行導致標記發生變化的那一部分對象
  • 根據時間來進行價值最大化的回收
  • 和CMS相比的優勢

    比起CMS有兩個優勢:

  • G1不會產生內存碎片。
  • G1可以精確控制停頓。該收集器是把整個堆(新生代、老年代)劃分成多個固定大小的區域,每次根據運行 停頓的時間去收集垃圾最多的區域。
  • 4.8 小結


    15. 類加載過程

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

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

    1. 加載

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

  • 通過全類名獲取定義此類的二進制字節流
  • 將字節流所代表的靜態存儲結構轉換為方法區的運行時數據結構
  • 在內存中生成一個代表該類的 Class 對象,作為方法區這些數據的訪問入口
    虛擬機規范多上面這3點并不具體,因此是非常靈活的。比如:“通過全類名獲取定義此類的二進制字節流” 并沒有指明具體從哪里獲取、怎樣獲取。比如:比較常見的就是從 ZIP 包中讀取(日后出現的JAR、EAR、WAR格式的基礎)、其他文件生成(典型應用就是JSP)等等。
  • 一個非數組類的加載階段(加載階段獲取類的二進制字節流的動作)是可控性最強的階段,這一步我們可以去完成還可以自定義類加載器去控制字節流的獲取方式(重寫一個類加載器的 loadClass() 方法)。數組類型不通過類加載器創建,它由 Java 虛擬機直接創建。

    類加載器、雙親委派模型也是非常重要的知識點,這部分內容會在后面的文章中單獨介紹到。

    加載階段和連接階段的部分內容是交叉進行的,加載階段尚未結束,連接階段可能就已經開始了。

    2. 驗證

    3. 準備

    準備階段是正式為類變量分配內存并設置類變量初始值的階段,這些內存都將在方法區中分配。對于該階段有以下幾點需要注意:

  • 這時候進行內存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨著對象一塊分配在 Java 堆中。
  • 這里所設置的初始值"通常情況"下是數據類型默認的零值(如0、0L、null、false等),比如我們定義了public static int value=111 ,那么 value 變量在準備階段的初始值就是 0 而不是111(初始化階段才會賦值)。特殊情況:比如給 value 變量加上了 fianl 關鍵字public static final int value=111 ,那么準備階段 value 的值就被賦值為 111。
  • 基本數據類型的零值:

    4. 解析

    解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用限定符7類符號引用進行。

    符號引用就是一組符號來描述目標,可以是任何字面量。直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。在程序實際運行時,只有符號引用是不夠的,舉個例子:在程序執行方法時,系統需要明確知道這個方法所在的位置。Java 虛擬機為每個類都準備了一張方法表來存放類中所有的方法。當需要調用一個類的方法的時候,只要知道這個方法在方發表中的偏移量就可以直接調用該方法了。通過解析操作符號引用就可以直接轉變為目標方法在類中方法表的位置,從而使得方法可以被調用。

    綜上,解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程,也就是得到類或者字段、方法在內存中的指針或者偏移量。

    5. 初始化

    初始化是類加載的最后一步,也是真正執行類中定義的 Java 程序代碼(字節碼),初始化階段是執行類構造器 <clinit> ()方法的過程。

    對于<clinit>() 方法的調用,虛擬機會自己確保其在多線程環境中的安全性。因為<clinit>() 方法是帶鎖線程安全,所以在多線程環境下進行類初始化的話可能會引起死鎖,并且這種死鎖很難被發現。

    對于初始化階段,虛擬機嚴格規范了有且只有5種情況下,必須對類進行初始化:

  • 當遇到 new 、 getstatic、putstatic或invokestatic 這4條直接碼指令時,比如 new 一個類,讀取一個靜態字段(未被 final 修飾)、或調用一個類的靜態方法時。
  • 使用 java.lang.reflect 包的方法對類進行反射調用時 ,如果類沒初始化,需要觸發其初始化。
  • 初始化一個類,如果其父類還未初始化,則先觸發該父類的初始化。
  • 當虛擬機啟動時,用戶需要定義一個要執行的主類 (包含 main 方法的那個類),虛擬機會先初始化這個類。
  • 當使用 JDK1.7 的動態動態語言時,如果一個 MethodHandle 實例的最后解析結構為 REF_getStatic、REF_putStatic、REF_invokeStatic、的方法句柄,并且這個句柄沒有初始化,則需要先觸發器初始化。
  • 6. 類加載器總結

    JVM 中內置了三個重要的 ClassLoader,除了 BootstrapClassLoader 其他類加載器均由 Java 實現且全部繼承自java.lang.ClassLoader:

    • BootstrapClassLoader(啟動類加載器) :最頂層的加載類,由C++實現,負責加載 %JAVA_HOME%/lib目錄下的jar包和類或者或被 -Xbootclasspath參數指定的路徑中的所有類。
    • ExtensionClassLoader(擴展類加載器) :主要負責加載目錄 %JRE_HOME%/lib/ext 目錄下的jar包和類,或被 java.ext.dirs 系統變量所指定的路徑下的jar包。
    • AppClassLoader(應用程序類加載器) :面向我們用戶的加載器,負責加載當前應用classpath下的所有jar包和類。

    7. 雙親委派模型介紹

    每一個類都有一個對應它的類加載器。系統中的 ClassLoder 在協同工作的時候會默認使用 雙親委派模型 。即在類加載的時候,系統會首先判斷當前類是否被加載過。已經被加載的類會直接返回,否則才會嘗試加載。加載的時候,首先會把該請求委派該父類加載器的 loadClass() 處理,因此所有的請求最終都應該傳送到頂層的啟動類加載器 BootstrapClassLoader 中。當父類加載器無法處理時,才由自己來處理。當父類加載器為null時,會使用啟動類加載器 BootstrapClassLoader 作為父類加載器。

    8. 雙親委派模型的好處

    雙親委派模型保證了Java程序的穩定運行,可以避免類的重復加載(JVM 區分不同類的方式不僅僅根據類名,相同的類文件被不同的類加載器加載產生的是兩個不同的類),也保證了 Java 的核心 API 不被篡改。如果沒有使用雙親委派模型,而是每個類加載器加載自己的話就會出現一些問題,比如我們編寫一個稱為 java.lang.Object 類的話,那么程序運行的時候,系統就會出現多個不同的 Object 類。

    總結

    以上是生活随笔為你收集整理的面经——JVM的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    狠狠色噜噜狠狠 | 午夜精品一二区 | 九九激情视频 | 白丝av免费观看 | 午夜精选视频 | 色综合天天狠狠 | 亚洲天堂网在线播放 | 欧美一级日韩免费不卡 | 免费av片在线 | 91丨porny丨九色 | 久久激五月天综合精品 | 国产亚洲综合在线 | 色播五月激情综合网 | 欧美日韩高清一区二区 国产亚洲免费看 | av免费黄色 | 久久精品人人做人人综合老师 | 一区av在线播放 | 97视频在线免费 | 精品久久久久久久久久国产 | 欧美日韩二三区 | 8090yy亚洲精品久久 | 激情av网| 91热在线 | 91少妇精拍在线播放 | 国产伦精品一区二区三区… | 伊人在线视频 | 亚洲天堂社区 | 成人av久久| 欧美一级免费黄色片 | 日韩精品在线一区 | 国产系列 在线观看 | 欧美aaa级片| 黄色一级网| 亚洲国产精品久久久久 | 91麻豆精品国产午夜天堂 | 国产视频午夜 | 欧美在线视频一区二区三区 | 一区二区 久久 | 中文字幕在线免费看 | 亚洲精品免费在线播放 | 国产成人精品日本亚洲999 | 天天操天天操天天操天天操天天操天天操 | 91精品国自产在线 | 欧美不卡在线 | 91影视成人 | 美女网站视频一区 | 久久久国产一区二区三区 | 欧美日韩国产精品久久 | 国产成人一区在线 | 国产 字幕 制服 中文 在线 | 制服丝袜一区二区 | 免费一级片久久 | 国产精品久久久久久久久久 | 国产精品久久9 | 波多野结衣视频一区二区三区 | 久久久久久久看片 | 中文字幕日本电影 | 日本公妇色中文字幕 | 中字幕视频在线永久在线观看免费 | 欧美在线99 | 爱av在线网 | 日日操天天操狠狠操 | 欧美日韩一区二区三区视频 | 亚洲蜜桃在线 | 亚洲经典中文字幕 | 午夜久久久影院 | 久久久久久久久国产 | 久久久久久久久久久网站 | 91在线看 | 久久久久久久久毛片 | 亚洲一区在线看 | 一区二区三区电影在线播 | 久久精品站 | 天天操天天操天天操天天 | 国产区在线看 | 狠狠干夜夜 | 97成人资源 | 久久99精品一区二区三区三区 | 久久久久夜色 | 亚洲激情在线观看 | 久久久免费精品国产一区二区 | 国产一二三四在线观看视频 | 国产手机视频在线 | 国产精品一区二区三区免费看 | 久久久久久国产精品免费 | 国产免费亚洲高清 | 久久系列 | 亚洲欧美日韩精品久久久 | 国产乱老熟视频网88av | 欧美日本在线观看视频 | 久久中文字幕在线视频 | 日韩另类在线 | 91在线视频观看免费 | 在线91网| 日韩精品一区二区在线观看 | 免费网站色 | 天天做天天爱夜夜爽 | 免费观看丰满少妇做爰 | 天天天天爽 | 国产99久久久国产精品免费二区 | 91免费观看视频网站 | 色在线观看网站 | av福利电影 | 激情视频在线观看网址 | 久久手机视频 | 人人干网 | 99久久国产免费看 | 免费高清在线观看成人 | 免费观看久久久 | 欧美日韩中文另类 | 四季av综合网站 | 99热只有精品在线观看 | 亚洲精品黄网站 | 丁香六月婷婷开心婷婷网 | 欧美性生交大片免网 | 欧美日韩aaaa| 麻豆传媒在线免费看 | 婷婷性综合 | av久久在线 | 午夜123 | 亚洲精品字幕 | 久久9视频 | 97国产精品久久 | 欧美激情综合五月色丁香 | 91精品国自产在线观看 | 亚洲精品久久视频 | 一级特黄aaa大片在线观看 | 国产精品一区二区三区四 | 国产精品久久久免费看 | www久久精品 | 黄色毛片视频 | 在线观看小视频 | 日本一区二区三区视频在线播放 | 国产一区网址 | 成人久久毛片 | 亚洲精品网址在线观看 | 一本色道久久综合亚洲二区三区 | 婷婷www| 久久久国产成人 | 久久色中文字幕 | 婷婷色视频 | av免费看在线 | 婷婷在线资源 | 亚洲综合激情网 | 久久精品日韩 | 五月天久久久 | 九九九九九国产 | 中文字幕在线免费观看视频 | 亚洲精品欧美视频 | 天天草av| 欧美久久久一区二区三区 | 人人干人人添 | 中文字幕乱码视频 | 亚洲精品小区久久久久久 | 国产99精品在线观看 | 国产精品久久久久久久午夜片 | 狠狠狠色 | 在线看的毛片 | 在线观看视频你懂 | 久草视频视频在线播放 | 亚洲精品视频在线播放 | 黄色视屏在线免费观看 | 亚洲天天综合 | 天天干,天天草 | 日韩av免费一区二区 | 国产亚洲视频在线 | 999成人免费视频 | 国产精品免费久久久久 | 激情深爱 | 不卡视频国产 | 日韩精品一区二区三区在线播放 | 久久经典国产视频 | 三三级黄色片之日韩 | 日日夜夜精品 | 久久国产精品一区二区三区四区 | 亚洲国产视频在线 | 在线免费精品视频 | 久久久.com| 色综合亚洲精品激情狠狠 | 欧美色精品天天在线观看视频 | 一区二区在线影院 | 欧美日韩高清一区二区 国产亚洲免费看 | 成人小视频在线观看免费 | 欧美精品少妇xxxxx喷水 | 九九久久久久久久久激情 | 日本高清中文字幕有码在线 | 欧美日韩国产二区三区 | 国产在线视频在线观看 | 天天夜夜狠狠操 | 国产专区精品视频 | 亚洲国产片色 | 波多野结衣理论片 | 人人讲下载| 91香蕉久久 | 免费观看一区二区 | 四虎影视精品成人 | 欧美激情第八页 | 国产美女视频免费 | 亚洲国产精品视频 | 在线国产视频一区 | 在线免费观看黄色av | 97av影院| 麻豆系列在线观看 | 亚洲精品美女久久17c | 欧美成人精品欧美一级乱黄 | www.夜夜干.com| 91精品中文字幕 | 国产手机视频在线播放 | av中文字幕免费在线观看 | 丁香高清视频在线看看 | 在线导航av | 亚洲精品乱码久久久久久高潮 | 色婷婷99| 久久免费视频精品 | 亚洲国产69 | 最近乱久中文字幕 | 中文字幕国产精品 | 日韩一区在线播放 | 久久久久久国产一区二区三区 | 91麻豆精品国产91久久久久久 | 免费av 在线 | a特级毛片 | 免费看v片 | 99热在线观看 | 国产视频中文字幕在线观看 | 奇米777777 | 一本一本久久a久久精品综合小说 | 国产成人黄色网址 | 日韩影片在线观看 | 四虎影视成人永久免费观看视频 | 日韩欧美国产视频 | 中文字幕在线网 | 中文字幕视频三区 | 91网站在线视频 | 日韩精品欧美专区 | 日日日日日 | av免费在线网站 | 最近高清中文在线字幕在线观看 | 日韩精品专区在线影院重磅 | 国产精品久久久久久婷婷天堂 | 丁香婷五月 | 久久96 | 视频在线精品 | 在线观看国产亚洲 | 中文字幕久久久精品 | 91视频在线自拍 | 久久久久草 | 免费高清在线视频一区· | 日韩艹| 国产91精品一区二区绿帽 | 国产日韩精品一区二区 | 九色精品免费永久在线 | 亚洲片在线资源 | 一区三区在线欧 | 狠狠色丁香婷婷综合橹88 | 国产五月天婷婷 | a爱爱视频| 日韩色av色资源 | www激情网 | 国产精品理论片 | 99热精品在线观看 | 亚洲91精品 | 国产在线一区二区 | 美女网站视频免费都是黄 | 日本高清dvd | 日韩久久久久久久 | 中文字幕在线观看视频免费 | 欧美va天堂在线电影 | 夜夜操天天摸 | 欧美嫩草影院 | 麻豆国产电影 | 午夜精品一区二区三区在线视频 | 欧美日韩国产一区二区三区在线观看 | av中文字幕亚洲 | 久久艹久久 | 国产在线一区观看 | 久久永久免费视频 | 国产精品二区在线观看 | 日本三级久久久 | 91网站在线视频 | 99热官网| 九九99 | 免费色网| 久久精品国产v日韩v亚洲 | 在线播放 亚洲 | 9999精品免费视频 | 国产剧情久久 | 久久精品女人毛片国产 | 黄网站大全| 国产夫妻性生活自拍 | 久久久久久激情 | 在线看片视频 | 久久夜视频 | 国产精品网红直播 | 天天操天天添天天吹 | 国产国产人免费人成免费视频 | 黄色综合| 综合五月婷婷 | 97超碰精品 | 西西444www大胆高清视频 | 91综合视频在线观看 | 黄色在线观看免费网站 | 久久黄页 | 欧美精品久久久久久久久久久 | 综合网婷婷| 午夜久久福利视频 | 草久在线| 国产小视频网站 | 久久免费一 | 欧美精品xx| 国产福利在线不卡 | 中文字幕亚洲不卡 | 人人干干人人 | 国产精品高清免费在线观看 | 国产精品人人做人人爽人人添 | 亚洲精品a区 | 又色又爽又激情的59视频 | 制服丝袜在线91 | 日本三级香港三级人妇99 | 国产专区一 | 国产精品久久久久久久久久久久 | 亚洲成人资源网 | 日本久久精品视频 | 国产69精品久久久久9999apgf | 最新免费av在线 | 久久精品国产一区二区三区 | 成人h动漫在线看 | 国产视频 亚洲精品 | 久久国产精品一区二区 | 少妇搡bbbb搡bbb搡aa | www激情久久| 成人av网站在线观看 | av成人在线观看 | 岛国av在线不卡 | 国产免费又黄又爽 | 亚洲人久久 | 亚洲欧洲一级 | 免费黄色激情视频 | 久久综合五月天婷婷伊人 | 波多野结衣视频一区 | 一本一本久久a久久精品综合妖精 | 免费看黄20分钟 | 日韩高清国产精品 | 美女视频黄色免费 | 激情丁香月 | av中文字幕在线电影 | 国产日产精品一区二区三区四区的观看方式 | 麻豆免费精品视频 | 亚洲另类视频在线观看 | 国产99在线免费 | 免费三级av | 日韩大片在线免费观看 | 久久久精品免费看 | 91av在线国产 | 久久久www成人免费毛片 | 99久久www | 夜色.com| 亚洲一区视频在线播放 | 欧美一区二区三区在线视频观看 | 亚洲一级特黄 | 在线а√天堂中文官网 | 欧美视频二区 | 在线电影 你懂得 | 成人av网站在线 | 激情图片qvod | 一区二区欧美在线观看 | 免费看一级特黄a大片 | 青青河边草手机免费 | 欧洲成人av | 日本成址在线观看 | 91成人精品一区在线播放69 | 免费中文字幕视频 | 日韩精品免费一线在线观看 | 激情视频亚洲 | 91日本在线播放 | 高清在线一区二区 | 亚洲综合色网站 | 成人av片免费看 | 中文字幕av电影下载 | 久久免费美女视频 | 91视频免费网站 | 免费黄a | 久久久久亚洲精品男人的天堂 | 亚洲一级黄色av | 欧美精品一区二区在线观看 | 在线播放一区二区三区 | 国产精品久久久久久久久免费 | 亚洲午夜精品久久久久久久久 | 不卡av免费在线观看 | 在线观看免费av网 | 黄色成人影院 | 久草在线视频中文 | 久久国产精品久久w女人spa | 豆豆色资源网xfplay | 极品久久久久 | 国产精品视频99 | 久久精品一二区 | 久久专区 | 四虎国产精品免费观看视频优播 | 国产成人av在线影院 | 91视频88av| 亚洲精品激情 | 日韩激情片在线观看 | 婷婷伊人五月天 | 国产精品久久久久久久久久久久午 | 欧美日本中文字幕 | 国内外成人在线 | 久久综合毛片 | 水蜜桃亚洲一二三四在线 | 国产精品99精品久久免费 | 最新av网址在线 | 亚洲精品欧美视频 | 欧美aaa级片 | 免费在线观看不卡av | 亚洲艳情| 91在线中字| 中文字幕有码在线播放 | 欧美乱大交 | 色婷婷亚洲婷婷 | www.久艹| 欧美黑人性爽 | 亚洲精品字幕在线 | 亚洲视频综合 | 91精品视频导航 | av品善网| 天天摸天天弄 | 成年人免费在线观看 | 青青草国产成人99久久 | 欧美疯狂性受xxxxx另类 | 久久久久久国产精品久久 | 亚洲在线视频免费观看 | 久久久色| 日韩综合色| 国产亚洲人成网站在线观看 | 超碰97在线人人 | 国产成人三级在线观看 | 欧美激情综合五月色丁香小说 | 黄色软件在线观看免费 | .精品久久久麻豆国产精品 亚洲va欧美 | 国产亚洲精品久久久久久 | 国产免费黄视频在线观看 | 国产精品初高中精品久久 | 黄色成人av | 色综合久久五月 | 久久视频在线观看中文字幕 | 日韩精品不卡在线观看 | 色老板在线| 国产精品1区2区 | 久久精品播放 | 久久久999精品视频 国产美女免费观看 | 国产精品久久久久久一二三四五 | 国产精品视频免费观看 | 欧美日韩中文国产 | 亚州国产精品视频 | 欧美日韩精品在线播放 | 久久国产精品免费看 | 国产精品嫩草55av | 久久精品电影院 | 久久免费黄色 | 在线免费观看一区二区三区 | 天堂在线一区二区三区 | 国产中文字幕视频在线观看 | 成人在线中文字幕 | 国产黄色在线网站 | 亚洲视频在线免费观看 | 久久艹欧美 | 日日夜夜天天久久 | 99热九九这里只有精品10 | 草久草久| 成年人黄色在线观看 | 国产日韩高清在线 | 日本午夜免费福利视频 | 五月丁婷婷| 久草国产视频 | 4hu视频 | 欧美激情视频三区 | 天天色棕合合合合合合 | 亚洲首页| av在线影视| 黄色的视频网站 | 色橹橹欧美在线观看视频高清 | 久久综合加勒比 | 天天舔天天搞 | 激情久久综合网 | 色播五月激情综合网 | 一级黄色毛片 | 久久久www免费电影网 | 久草在线资源观看 | 91激情| 最近日本中文字幕a | 亚洲欧美视频在线观看 | 亚洲精品色婷婷 | 久久社区视频 | av一级片 | av亚洲产国偷v产偷v自拍小说 | 日日夜夜天天综合 | 亚洲人久久久 | 免费三级骚 | 免费福利小视频 | 日韩午夜电影 | 久久久综合| 免费看的毛片 | 色综合久久久久久中文网 | 成人黄色小说在线观看 | 国产免费观看久久黄 | 超碰在线人人艹 | 91精品在线免费视频 | 日韩午夜电影院 | 美女网站免费福利视频 | av日韩国产 | 国产精品一区二区免费看 | 美女久久久久久久久久 | 亚洲国产视频a | 国产精品久久久久久影院 | 午夜影院三级 | 天天弄天天干 | 日韩精品免费在线观看 | 亚洲黄色小说网 | 天天插一插 | 日本精品久久久久中文字幕 | 日韩在线中文字幕 | 六月色丁香 | 久久久久久高潮国产精品视 | 亚洲成人av电影 | 欧美一区二区三区在线观看 | 国产一级免费观看视频 | 久久精品资源 | 久色免费视频 | 91亚洲综合 | 超碰免费观看 | 精品久久免费 | 天天干天天搞天天射 | 欧美精品亚洲精品日韩精品 | 久久精品综合 | 免费福利片 | 蜜桃av观看 | 免费视频在线观看网站 | 一区二区不卡高清 | 精品视频资源站 | 久久69av| 狠狠色伊人亚洲综合网站野外 | 国产高清在线免费 | 久久这里只有精品久久 | www蜜桃视频 | 日本精品久久久久久 | 久久国产精品视频观看 | 欧美黑人xxxx猛性大交 | 色婷婷激情四射 | 99久久这里有精品 | 日本不卡123区 | 国产在线小视频 | www.夜夜骑.com | 亚洲丝袜一区二区 | 国产专区免费 | 午夜久久久影院 | www.夜夜爽 | 五月婷婷久久综合 | 免费av网址大全 | 亚洲一区二区天堂 | 中文字幕精品www乱入免费视频 | 久久久久久久久艹 | 亚洲国产视频网站 | 久久这里 | 日本特黄特色aaa大片免费 | 亚洲精选在线观看 | 91一区一区三区 | 国模精品一区二区三区 | 久久九九精品 | 国产成人一区二 | 91av蜜桃| 日韩精品一区二区在线 | 国产成人精品一区二区三区在线 | 国产精品女人久久久 | 亚洲黄色在线播放 | 蜜臀av性久久久久av蜜臀妖精 | 国产精品久久人 | 国产成人在线综合 | 热re99久久精品国产66热 | 国产小视频在线 | 日韩精品视频在线观看免费 | 国产精品夜夜夜一区二区三区尤 | 免费看的黄色小视频 | 久久黄网站 | 日日夜夜噜 | 亚洲综合激情五月 | 久久tv视频| 久草www| 色综合天天在线 | 高潮毛片无遮挡高清免费 | 成人理论电影 | 国产精品黄色影片导航在线观看 | 亚洲一区二区精品 | 不卡视频一区二区三区 | 欧美久久久久久久 | 福利精品在线 | 91麻豆精品国产91久久久久久久久 | 亚洲一区二区三区精品在线观看 | 亚洲涩综合 | www.天天色.com| 在线看片一区 | 中文字幕日韩在线播放 | 国产裸体永久免费视频网站 | 国产视频导航 | 中文字幕资源网 国产 | 日韩在线观看视频中文字幕 | 亚洲欧美视频在线播放 | 黄色成人影院 | 亚洲激情五月 | 亚洲精品影院在线观看 | 亚洲黄色一级电影 | 亚洲精品久久久久中文字幕二区 | 一本一道久久a久久精品 | 亚洲精品 在线视频 | 亚洲综合激情五月 | 国产视频在线看 | 日日夜夜操操操操 | 久久婷婷一区二区三区 | 欧美亚洲国产一卡 | 亚洲黄色软件 | 久久网址 | 91精品久久久久久久久 | 国产亚洲精品bv在线观看 | 成人午夜黄色 | 麻豆视频免费播放 | 国产91在线看 | 久久国产精彩视频 | 国产视频网站在线观看 | 亚洲九九 | 久久成人一区 | 亚洲综合色网站 | 婷婷在线观看视频 | www.69xx| 成人av在线网址 | 日韩免费在线观看网站 | 日韩电影在线观看中文字幕 | 久99久精品 | 少妇bbw搡bbbb搡bbbb | 中文av网| 在线看日韩av | 成人黄色片在线播放 | 中文字幕在线观看亚洲 | av天天在线观看 | 日本不卡一区二区三区在线观看 | 久久夜色精品国产欧美一区麻豆 | 日韩超碰 | 在线黄色av电影 | 一级黄色片毛片 | 热久久国产 | 久久一区国产 | 国产成人一区二区三区影院在线 | 亚洲精品视频在线观看网站 | 久久精品永久免费 | www国产亚洲 | 91香蕉视频黄| 亚洲女同ⅹxx女同tv | 日韩精品免费一区二区在线观看 | 婷婷激情欧美 | 久久影视中文字幕 | 久久国产区 | 国产一区电影在线观看 | 夜夜骑日日操 | 999一区二区三区 | 亚洲天堂视频在线 | 欧美 激情 国产 91 在线 | 13日本xxxxxⅹxxx20 | 99热最新 | 国产成人精品av在线观 | 亚洲人成网站精品片在线观看 | 激情网站 | 九九热99视频 | 欧美日韩不卡一区二区 | 亚洲一级二级三级 | 日韩欧美在线视频一区二区 | 国产91探花| 国产精品欧美一区二区 | 成年人在线免费视频观看 | 亚洲乱码国产乱码精品天美传媒 | 97视频网址| 中文字幕在线视频网站 | 成人av在线一区二区 | av一区在线播放 | 久久天天躁夜夜躁狠狠躁2022 | 天天爽夜夜爽人人爽一区二区 | 青青河边草手机免费 | 日日爽视频 | 久久视频这里只有精品 | 日韩在线观看免费 | 精品亚洲视频在线 | 国产大尺度视频 | 成人免费看片网址 | 日批在线观看 | 免费看一级特黄a大片 | 丁五月婷婷| 九九天堂 | 久久久久网址 | 国产亚洲精品久久久久5区 成人h电影在线观看 | 色综合夜色一区 | 欧美午夜a| av片子在线观看 | 免费av成人在线 | 五月天网页 | 国产九九九视频 | 亚洲 欧美日韩 国产 中文 | 日韩视频区 | 国产精品一区久久久久 | 免费www视频 | 欧美日韩视频免费看 | 欧美激情精品久久久久久 | 天天操天天是 | 国产超碰在线观看 | 91av色| 天天操天天摸天天射 | 丰满少妇一级片 | 成人在线免费视频 | 久久一级电影 | 久久国产精品免费一区 | 99久久精品视频免费 | av亚洲产国偷v产偷v自拍小说 | 中文字幕丝袜美腿 | 久久成人人人人精品欧 | 国产日本亚洲 | www.五月天色| 亚洲激精日韩激精欧美精品 | 久久只有精品 | 久久玖 | 337p日本欧洲亚洲大胆裸体艺术 | 亚洲成人午夜在线 | 中文字幕在线观看亚洲 | 在线观看久 | 成人在线视频在线观看 | 欧美日韩中文字幕综合视频 | 97中文字幕| 综合色在线 | 国产精品无 | 少妇高潮流白浆在线观看 | 国产你懂的在线 | 可以免费看av | 亚洲精选久久 | 色婷婷综合在线 | 久久精品国产免费看久久精品 | 精品亚洲二区 | 久草新在线 | 日本爱爱免费 | 国产96精品| 中文字幕999 | 午夜精品视频一区 | 国产精品福利av | 日韩三级精品 | 午夜精品久久久久 | 免费一级片观看 | 992tv又爽又黄的免费视频 | 亚洲国产日韩欧美 | 天天操天天综合网 | av在线网站大全 | 激情视频区 | 国产区久久 | 天天射天天色天天干 | 最近中文字幕在线中文高清版 | 亚洲欧美成人在线 | 欧美一区二视频在线免费观看 | 久久精品艹 | 国产精品久久久久久久久久 | 色综合亚洲精品激情狠狠 | 亚洲视频六区 | 久久精品精品电影网 | 久久久亚洲精品 | 国产一级片观看 | 成人精品一区二区三区电影免费 | 亚洲韩国一区二区三区 | 91高清完整版在线观看 | 国产成人一区二区三区影院在线 | 97精品国产97久久久久久免费 | 蜜桃久久久 | 又黄又刺激视频 | 国产手机在线播放 | 亚洲小视频在线观看 | 91九色蝌蚪国产 | 可以免费观看的av片 | 亚洲精品自在在线观看 | 国产色网站 | 久久99国产精品二区护士 | 伊人天堂网 | 久久久免费精品国产一区二区 | 天天插天天 | 超碰av在线| 色欲综合视频天天天 | av片在线观看免费 | 最近中文字幕高清字幕在线视频 | 在线导航福利 | 日韩欧美在线不卡 | 免费看十八岁美女 | 久久综合久久八八 | 一区二区三区免费 | 久草在线 | 国产a精品| 综合视频在线 | 免费合欢视频成人app | 在线亚洲欧美日韩 | 日本福利视频在线 | 国产一区免费视频 | 免费一级片在线 | 国产色视频一区二区三区qq号 | 日韩久久片 | av资源免费在线观看 | 91av原创| 天天综合网 天天综合色 | 亚洲视频资源在线 | 欧美性春潮 | 97品白浆高清久久久久久 | 婷婷综合网 | 国产91精品久久久久 | 一区二区三区高清在线观看 | 免费视频91 | 亚洲另类视频 | 国产在线观看免费av | 久久免费视频在线 | 麻豆视频在线 | 国产丝袜高跟 | 97色免费视频 | 日韩av偷拍| 色播五月激情综合网 | 亚洲成人频道 | 亚洲成av人影院 | 国产精品v a免费视频 | 久久国产精品精品国产色婷婷 | 亚洲天天看 | 麻豆精品视频在线观看免费 | 中文字幕 在线 一 二 | 中文字幕久久久精品 | 国产伦精品一区二区三区在线 | 三级午夜片| 在线观看免费福利 | 国产h在线观看 | 中文字幕 二区 | 中文字幕观看在线 | 久久国产高清视频 | 涩涩网站在线看 | 99久久精品国产毛片 | 91精品婷婷国产综合久久蝌蚪 | 成人av一区二区在线观看 | 天天天天天天天天操 | 成人小视频在线免费观看 | 中文字幕在线观看免费高清电影 | 国产精品99久久久久久久久 | 久久久久久久久久久久影院 | 亚洲精品在线视频观看 | 亚洲天堂网站 | 亚洲国产精品人久久电影 | 亚洲国产精品传媒在线观看 | 美女黄视频免费看 | www国产精品com | 高清国产午夜精品久久久久久 | 久久国产精品99精国产 | 91精品免费视频 | 超碰在线人人艹 | 精品一区二区影视 | 国产精品一区二区 91 | 免费观看性生交 | 色网免费观看 | 91精品国产91p65 | 狠狠操导航 | 精品麻豆入口免费 | 97在线视频免费看 | 成年人app网址 | 91视频在线观看大全 | 国产精品女 | 亚洲国产欧美一区二区三区丁香婷 | 亚洲国产中文字幕 | 精品理论片 | 欧美久久电影 | 九九久久在线看 | 在线免费观看涩涩 | 亚洲日本精品视频 | 天天撸夜夜操 | 美女黄频视频大全 | 成年人免费在线观看网站 | 欧美日韩不卡一区二区三区 | 亚洲毛片一区二区三区 | 久久激情五月激情 | 亚洲国产97在线精品一区 | 亚洲国产网址 | 中文字幕亚洲五码 | 国产又粗又硬又长又爽的视频 | 99热99re6国产在线播放 | 亚洲精品在线观看网站 | 欧美一区二区伦理片 | www.夜夜操.com| av天天在线观看 | 国产一区久久久 | 一区二区伦理电影 | 国产一级视频在线免费观看 | 91正在播放 | 国产不卡片 | 色视频成人在线观看免 | 99精品网站| 日本黄色免费网站 | 日本在线观看一区 | 国产一区在线观看免费 | 国产高清在线永久 | 97成人超碰| 国产精品一区二区中文字幕 | 婷婷六月中文字幕 | 天天干天天操天天干 | 91精品国产网站 | 精品美女在线观看 | 亚洲一级国产 | 97成人资源 | 毛片888| 久久久久久久久久福利 | 麻豆视屏 | 嫩草91影院 | 视频一区二区视频 | 激情综合五月 | 色视频在线免费观看 | 久草热视频| 国产另类av | 伊人午夜 | 久久大片 | 国产成人免费高清 | 9ⅰ精品久久久久久久久中文字幕 | 精品福利片 | 97超碰在线久草超碰在线观看 | 国产精品女人久久久 | 国产精品美女久久久久久久久久久 | 91精品国产一区二区在线观看 | 国产精品美女久久久久久久久 | 国产精品大尺度 | 99看视频在线观看 | 毛片美女网站 | 国产精品免费高清 | 欧美精品黑人性xxxx | 黄色av成人在线观看 | 国产成人久久av免费高清密臂 | 久久伊人91 | 99这里只有久久精品视频 | 日本久久久久久 | 免费在线观看一区 | 亚洲伦理电影在线 | 最新精品视频在线 | 国产首页 | 久久久久久免费网 | 亚洲成av人片在线观看香蕉 | 天天爱天天操 | 久久99在线视频 | 国际精品久久久久 | 韩国av一区二区三区 | 国产在线999 | 波多野结衣视频网址 | 欧美精品免费一区二区 | 91九色视频导航 | 久久这里只有精品视频首页 | 丁香激情综合久久伊人久久 | 九九在线精品视频 | 欧美成人视 | 亚洲综合在线五月天 | 久久久久成人免费 | 在线亚洲天堂网 | 97视频精品 | 国产精品久久久免费 | 日本超碰在线 | 开心激情网五月天 | aaa亚洲精品一二三区 | 超碰在线日本 | 欧美美女视频在线观看 | 免费观看一级视频 | 天天爽天天搞 | 日本乱视频 | 波多野结衣一区二区 | 天天综合网天天 | 99热超碰在线 | 香蕉视频免费在线播放 | 人人草人 | 在线看毛片网站 | 人人澡人人爽欧一区 | 国产精品18久久久久久久久 | 久久久久成人精品亚洲国产 | 久久久这里有精品 | 色综合天天射 | 国产精品尤物 | 手机色站 | 97在线观 | 欧美91成人网 | 国产精品欧美日韩在线观看 | 中文字幕在线播放第一页 | 久久久久国产精品免费免费搜索 | 色姑娘综合天天 | 国产精久久久 | 夜色在线资源 | 亚洲四虎在线 | 最新av免费在线观看 | 超级碰99 | 亚洲久久视频 | 在线黄色免费av | 9幺看片| 九色porny真实丨国产18 | 亚洲免费精彩视频 | 黄色影院在线免费观看 | av中文字幕电影 |