<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          小白都能看得懂的java虛擬機內(nèi)存模型

          共 9500字,需瀏覽 19分鐘

           ·

          2020-01-01 23:22

          ?來自:CSDN(作者:土豆是我的最愛)

          原文鏈接:

          https://blog.csdn.net/qq_37141773/article/details/103138476#comments


          一、虛擬機


          66b1f41b4922c45418c2b517d0a63ce6.webp


          同樣的java代碼在不同平臺生成的機器碼肯定是不一樣的,因為不同的操作系統(tǒng)底層的硬件指令集是不同的。


          同一個java代碼在windows上生成的機器碼可能是0101.......,在linux上生成的可能是1100......,那么這是怎么實現(xiàn)的呢?


          不知道同學們還記不記得,在下載jdk的時候,我們在oracle官網(wǎng),基于不同的操作系統(tǒng)或者位數(shù)版本要下載不同的jdk版本,也就是說針對不同的操作系統(tǒng),jdk虛擬機有不同的實現(xiàn)。


          那么虛擬機又是什么東西呢,如圖是從軟件層面屏蔽不同操作系統(tǒng)在底層硬件與指令上的區(qū)別,也就是跨平臺的由來。


          說到這里同學們可能還是有點不太明白,說的還是太宏觀了,那我們來了解下java虛擬機的組成。


          二、虛擬機組成

          d59f516bedf5accc5ae04043681e9cab.webp


          1.棧

          我們先講一下其中的一塊內(nèi)存區(qū)域,大家都知道棧是存儲局部變量的,也是線程獨有的區(qū)域,也就是每一個線程都會有自己獨立的棧區(qū)域。


          public?class?Math?{
          ????public?static?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");
          ????}
          }


          說起棧大家都不會陌生,數(shù)據(jù)結構中就有學,這里線程棧中存儲數(shù)據(jù)的部分使用的就是棧,先進后出。


          大家都知道每個方法都有自己的局部變量,比如上圖中main方法中的math,compute方法中的a b c,那么java虛擬機為了區(qū)分不同方法中局部變量作用域范圍的內(nèi)存區(qū)域,每個方法在運行的時候都會分配一塊獨立的棧幀內(nèi)存區(qū)域,我們試著按上圖中的程序來簡單畫一下代碼執(zhí)行的內(nèi)存活動。


          3891d263f7a6b39390e4de67fb256d8f.webp


          執(zhí)行main方法中的第一行代碼是,棧中會分配main()方法的棧幀,并存儲math局部變量,,接著執(zhí)行compute()方法,那么棧又會分配compute()的棧幀區(qū)域。


          這里的棧存儲數(shù)據(jù)的方式和數(shù)據(jù)結構中學習的棧是一樣的,先進后出。當compute()方法執(zhí)行完之后,就會出棧被釋放,也就符合先進后出的特點,后調(diào)用的方法先出棧。


          棧幀

          那么棧幀內(nèi)部其實不只是存放局部變量的,它還有一些別的東西,主要由四個部分組成。


          94afc58b182a7775993697cb0959b57d.webp


          那么要講這個就會涉及到更底層的原理--字節(jié)碼。我們先看下我們上面代碼的字節(jié)碼文件。


          a395e62174af578e5e54d4d8794d1239.webp


          看著就是一個16字節(jié)的文件,看著像亂碼,其實每個都是有對應的含義的,oracle官方是有專門的jvm字節(jié)碼指令手冊來查詢每組指令對應的含義的。那我們研究的,當然不是這個。


          jdk有自帶一個javap的命令,可以將上述class文件生成一種更可讀的字節(jié)碼文件。


          c19cb42f7d11a6e3d6c8fb2fc69d6a79.webp


          我們使用javap -c命令將class文件反編譯并輸出到TXT文件中。


          Compiled from "Math.java"
          public?class?com.example.demo.test1.Math?{
          ??public?static?int?initData;
          ?
          ??public?static?com.example.demo.bean.User user;
          ?
          ??public?com.example.demo.test1.Math();
          ????Code:
          ???????0: aload_0
          ???????1: invokespecial #1??????????????????// Method java/lang/Object."":()V
          ???????4: return
          ?
          ??public?int?compute();
          ????Code:
          ???????0: iconst_1
          ???????1: istore_1
          ???????2: iconst_2
          ???????3: istore_2
          ???????4: iload_1
          ???????5: iload_2
          ???????6: iadd
          ???????7: bipush 10
          ???????9: imul
          ??????10: istore_3
          ??????11: iload_3
          ??????12: ireturn
          ?
          ??public?static?void?main(java.lang.String[])
          ;
          ????Code:
          ???????0: new???????????#2??????????????????// class com/example/demo/test1/Math
          ???????3: dup
          ???????4: invokespecial #3??????????????????// Method "":()V
          ???????7: astore_1
          ???????8: aload_1
          ???????9: invokevirtual #4??????????????????// Method compute:()I
          ??????12: pop
          ??????13: getstatic #5??????????????????// Field java/lang/System.out:Ljava/io/PrintStream;
          ??????16: ldc #6??????????????????// String test
          ??????18: invokevirtual #7??????????????????// Method java/io/PrintStream.println:(Ljava/lang/String;)V
          ??????21: return
          ?
          ??static?{};
          ????Code:
          ???????0: sipush 666
          ???????3: putstatic #8??????????????????// Field initData:I
          ???????6: new???????????#9??????????????????// class com/example/demo/bean/User
          ???????9: dup
          ??????10: invokespecial #10?????????????????// Method com/example/demo/bean/User."":()V
          ??????13: putstatic #11?????????????????// Field user:Lcom/example/demo/bean/User;
          ??????16: return
          }


          此時的jvm指令碼就清晰很多了,大體結構是可以看懂的,類、靜態(tài)變量、構造方法、compute()方法、main()方法。


          其中方法中的指令還是有點懵,我們舉compute()方法來看一下:


          Code:
          ???????0: iconst_1
          ???????1: istore_1
          ???????2: iconst_2
          ???????3: istore_2
          ???????4: iload_1
          ???????5: iload_2
          ???????6: iadd
          ???????7: bipush 10
          ???????9: imul
          ??????10: istore_3
          ??????11: iload_3
          ??????12: ireturn


          這幾行代碼就是對應的我們代碼中compute()方法中的四行代碼。大家都知道越底層的代碼,代碼實現(xiàn)的行數(shù)越多,因為他會包含一些java代碼在運行時底層隱藏的一些細節(jié)原理。


          那么一樣的,這個jvm指令官方也是有手冊可以查閱的,網(wǎng)上也有很多翻譯版本,大家如果想了解可自行百度。


          這里我只講解本博文設計代碼中的部分指令含義:


          0. 將int類型常量1壓入操作數(shù)棧


          0: iconst_1


          這一步很簡單,就是將1壓入操作數(shù)棧?

          6aa3fa2a9e5ea347b8e715f0bb48565c.webp


          1.? 將int類型值存入局部變量1?


          1: istore_1

          ??

          局部變量1,在我們代碼中也就是第一個局部變量a,先給a在局部變量表中分配內(nèi)存,然后將int類型的值,也就是目前唯一的一個1存入局部變量a


          e792591dd312b4858116d61ec91c1502.webp


          2. 將int類型常量2壓入操作數(shù)棧


          2: iconst_2


          3.? 將int類型值存入局部變量2?


          3: istore_2


          這兩行代碼就和前兩行類似了。

          74062632f6fa902d22efda2800fcbe08.webp

          4. 從局部變量1中裝載int類型值


          4: iload_1


          5. 從局部變量2中裝載int類型值?


          5: iload_2


          這兩個代碼是將局部變量1和2,也就是a和b的值裝載到操作數(shù)棧中


          aba1ced7552ddb2ee7985bec7efcda61.webp

          6. 執(zhí)行int類型的加法


          6: iadd


          iadd指令一執(zhí)行,會將操作數(shù)棧中的1和2依次從棧底彈出并相加,然后把運算結果3在壓入操作數(shù)棧底。


          7bab5dda673abb987910c606877ae3b5.webp


          7. 將一個8位帶符號整數(shù)壓入棧


          7: bipush 10


          ?這個指令就是將10壓入棧


          b669d597ed0fdf049540b1cadd8a7e65.webp

          8. 執(zhí)行int類型的乘法


          9: imul


          這里就類似上面的加法了,將3和10彈出棧,把結果30壓入棧


          e7edcb2d9af9886394f1fa8a48699b93.webp

          ?

          9. 將將int類型值存入局部變量3?


          10: istore_3


          這里大家就不陌生了吧,和第二步第三步是一樣的,將30存入局部變量3,也就是c


          0f90557f1391fe6aee3ffbfe4168c471.webp


          10. 從局部變量3中裝載int類型值


          11: iload_3


          這個前面也說了

          217a6fe7fa9503d9436f22a5c57d05b6.webp

          11. 返回int類型值


          12: ireturn


          這個就不用多說了,就是將操作數(shù)棧中的30返回


          到這里就把我們compute()方法講解完了,講完有沒有對局部變量表和操作數(shù)棧的理解有所加深呢?說白了賦值號=后面的就是操作數(shù),在這些操作數(shù)進行賦值,運算的時候需要內(nèi)存存放,那就是存放在操作數(shù)棧中,作為臨時存放操作數(shù)的一小塊內(nèi)存區(qū)域。


          接下來我們再說說方法出口。


          方法出口說白了不就是方法執(zhí)行完了之后要出到哪里,那么我們知道上面compute()方法執(zhí)行完之后應該回到main()方法第三行那么當main()方法調(diào)用compute()的時候,compute()棧幀中的方法出口就存儲了當前要回到的位置,那么當compute()方法執(zhí)行完之后,會根據(jù)方法出口中存儲的相關信息回到main()方法的相應位置。


          那么main()方同樣有自己的棧幀,在這里有些不同的地方我們講一下。


          我們上面已經(jīng)知道局部變量會存放在棧幀中的局部變量表中,那么main()方法中的math會存入其中,但是這里的math是一個對象,我們知道new出來的對象是存放在堆中的


          d96f1e32463c50595cfb85733fb2aa1d.webp


          那么這個math變量和堆中的對象有什么聯(lián)系呢?是同一個概念么?


          當然不是的,局部變量表中的math存儲的是堆中那個math對象在堆中的內(nèi)存地址


          2.程序計數(shù)器

          程序計數(shù)器也是線程私有的區(qū)域,每個線程都會分配程序計數(shù)器的內(nèi)存,是用來存放當前線程正在運行或者即將要運行的jvm指令碼對應的地址,或者說行號位置。


          上述代碼中每個指令碼前面都有一個行號,你就可以把它看作當前線程執(zhí)行到某一行代碼位置的一個標識,這個值就是程序計數(shù)器的值。


          那么jvm虛擬機為什么要設置程序計數(shù)器這個結構呢?就是為了多線程的出現(xiàn),多線程之間的切換,當一個程序被掛起的時候,總是要恢復的,那么恢復到哪個位置呢,總不能又重新開始執(zhí)行吧,那么程序計數(shù)器就解決了這個問題。


          3.方法區(qū)

          在jdk1.8之前,有一個名稱叫做持久帶/永久代,很多同學應該聽過,在jdk1.8之后,oracle官方改名為元空間。存放常量、靜態(tài)變量、類元信息。


          public?static?int?initData = 666;


          這個initData就是靜態(tài)變量,毋庸置疑是存放在方法區(qū)的


          public?static?User user = new?User();


          那么這個user就有點不一樣了,user變量放在方法區(qū),new的User是存放在堆中的


          到這里我們就能意識到棧,堆,方法區(qū)之間都是有聯(lián)系的。

          c35824e6a8b68907d982babf9e30b938.webp


          棧中的局部變量,方法區(qū)中的靜態(tài)變量,如果是對象類型的話都會指向堆中new出來中的對象,那么紅色的聯(lián)系代表什么呢?我們先來了解一下對象。


          對象組成

          你對對象的了解有多少呢,天天用對象,你是否知道對象在虛擬機中的存儲結構呢?


          對象在內(nèi)存中存儲的布局可以分為3塊區(qū)域:對象頭(Header)、實例數(shù)據(jù)(Instance Data)對齊填充(Padding)。下圖是普通對象實例與數(shù)組對象實例的數(shù)據(jù)結構:


          9bffc52cdb46eeb96a8c6f7575c747cf.webp


          對象頭


          HotSpot虛擬機的對象頭包括兩部分信息:


          Mark Word

          ? ? ? ? 第一部分markword,用于存儲對象自身的運行時數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標志、線程持有的鎖、偏向線程ID、偏向時間戳等,這部分數(shù)據(jù)的長度在32位和64位的虛擬機(未開啟壓縮指針)中分別為32bit和64bit,官方稱它為“MarkWord”。


          Klass Pointer

          ? ? ? ? 對象頭的另外一部分是klass類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例.


          數(shù)組長度(只有數(shù)組對象有)

          ? ? ? ? 如果對象是一個數(shù)組, 那在對象頭中還必須有一塊數(shù)據(jù)用于記錄數(shù)組長度.


          實例數(shù)據(jù)

          ? ? ? ? 實例數(shù)據(jù)部分是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內(nèi)容。無論是從父類繼承下來的,還是在子類中定義的,都需要記錄起來。


          對齊填充

          ? ? ? ? 第三部分對齊填充并不是必然存在的,也沒有特別的含義,它僅僅起著占位符的作用。由于HotSpot VM的自動內(nèi)存管理系統(tǒng)要求對象起始地址必須是8字節(jié)的整數(shù)倍,換句話說,就是對象的大小必須是8字節(jié)的整數(shù)倍。而對象頭部分正好是8字節(jié)的倍數(shù)(1倍或者2倍),因此,當對象實例數(shù)據(jù)部分沒有對齊時,就需要通過對齊填充來補全。


          其中的klass類型指針就是那條紅色的聯(lián)系,那是怎么聯(lián)系的呢?


          new?Thread().start();


          a93332c6637ad8cce781483fc26f9359.webp


          類加載其實最終是以類元信息的形式存儲在方法區(qū)中的,math和math2都是由同一個類new出來的,當對象被new時,都會在對象頭中存儲一個指向類元信息的指針,這就是Klass? Pointer.


          到這里我們就講解了棧,程序計數(shù)器和方法區(qū),下面我們簡單介紹一下本地方法區(qū),最后再終點講解堆。


          4.本地方法棧

          實際上現(xiàn)在本地方法棧已經(jīng)用的比較少了,大家應該都有聽過本地方法吧


          如何經(jīng)常用的線程類


          new?Thread().start();


          public?synchronized void?start() {
          ????????if?(threadStatus != 0)
          ????????????throw?new?IllegalThreadStateException();
          ????????group.add(this);
          ????????boolean started = false;
          ????????try?{
          ????????????start0();
          ????????????started = true;
          ????????} finally?{
          ????????????try?{
          ????????????????if?(!started) {
          ????????????????????group.threadStartFailed(this);
          ????????????????}
          ????????????} catch?(Throwable ignore) {
          ????????????}
          ????????}
          ????}


          其中底層調(diào)用了一個start0()的方法


          private?native?void?start0();


          這個方法沒有實現(xiàn),但又不是接口,是使用native修飾的,是屬于本地方法,底層通過C語言實現(xiàn)的,那java代碼里為什么會有C語言實現(xiàn)的本地方法呢?


          大家都知道JAVA是問世的,在那之前一個公司的系統(tǒng)百分之九十九都是使用C語言實現(xiàn)的,但是java出現(xiàn)后,很多項目都要轉(zhuǎn)為java開發(fā),那么新系統(tǒng)和舊系統(tǒng)就免不了要有交互,那么就需要本地方法來實現(xiàn)了,底層是調(diào)用C語言中的dll庫文件,就類似于java中的jar包,當然,如今跨語言的交互就很多了,比如thrift,http接口方式,webservice等,當時并沒有這些方式,就只能通過本地方法來實現(xiàn)了。


          那么本地方法始終也是方法,每個線程在運行的時候,如果有運行到本地方法,那么必然也要產(chǎn)生局部變量等,那么就需要存儲在本地方法棧了。如果沒有本地方法,也就沒有本地方法棧了。


          5.堆

          最后我們講堆,堆是最重要的一塊內(nèi)存區(qū)域,我相信大部分人對堆都不陌生。但是對于它的內(nèi)部結構,運作細節(jié)想要搞清楚也沒那么簡單。


          7fbf17efb97d86b58c3a0f6365734b1b.webp


          對于這個基本組成大家應該都有所了解,對就是由年輕代老年代組成,年輕代又分為伊甸園區(qū)survivor區(qū),survivor區(qū)中又有from區(qū)to區(qū).


          我們new出來的對象大家都知道是放在堆中,那具體放在堆中的哪個位置呢?


          其實new出來的對象一般都放在Eden區(qū),那么為什么叫伊甸園區(qū)呢,伊甸園就是亞當夏娃住的地方,不就是造人的地方么?所以我們new出來的對象就是放在這里的,那當Eden區(qū)滿了之后呢?


          假設我們給對分配600M內(nèi)存,這個是可以通過參數(shù)調(diào)節(jié)的,我們后文再講。那么老年代默認是占2/3的,也就是差不多400M,那年輕代就是200M,Eden區(qū)160M,Survivor區(qū)40M。


          GC

          1c0d94483fc3813d1a8ce8d472a2222c.webp


          一個程序只要在運行,那么就不會不停的new對象,那么總有一刻Eden區(qū)會放滿,那么一旦Eden區(qū)被放滿之后,虛擬機會干什么呢?沒錯,就是gc,不過這里的gc屬于minor gc,就是垃圾收集,來收集垃圾對象并清理的,那么什么是垃圾對象呢?


          好比我們上面說的math對象,我們假設我們是一個web應用程序,main線程執(zhí)行完之后程序不會結束,但是main方法結束了,那么main()方法棧幀會被釋放,局部變量會被釋放,但是局部變量對應的堆中的對象還是依然存在的,但是又沒有指針指向它,那么它就是一個垃圾對象,那就應該被回收掉了,之后如果還會new Math對象,也不會用這個之前的了,因為已經(jīng)無法找到它了,如果留著這個對象只會占用內(nèi)存,顯然是不合適的。


          這里就涉及到了一個GC Root根以及可達性分析算法的概念,也是面試偶爾會被問到的。


          可達性分析算法是將GC Roots對象作為起點,從這些起點開始向下搜索引用的對象,找到的對象都標記為非垃圾對象,其余未標記的都是垃圾對象。


          那么GC Roots根對象又是什么呢,GC Roots根就是判斷一個對象是否可以回收的依據(jù),只要能通過GC Roots根向下一直搜索能搜索到的對象,那么這個對象就不算垃圾對象,而可以作為GC Roots根的有線程棧的本地變量,靜態(tài)變量,本地方法棧的變量等等,說白了就是找到和根節(jié)點有聯(lián)系的對象就是有用的對象,其余都認為是垃圾對象來回收。


          02cc46b833ce625b3663302c04a91a5a.webp


          經(jīng)歷了第一次minor gc后,沒有被清理的對象就會被移到From區(qū),如上圖。


          22ad643b06adaf8b694868cf19cb1881.webp


          上面在說對象組成的時候有寫到,在對象頭的Mark Word中有存儲GC分代年齡,一個對象每經(jīng)歷一次gc,那么它的gc分代年齡就會+1,如上圖。


          6ed08b47772066c6ccaf7ef707ff7660.webp


          那么如果第二次新的對象又把Eden區(qū)放滿了,那么又會執(zhí)行minor gc,但是這次會連著From區(qū)一起gc,然后將Eden區(qū)和From區(qū)存活的對象都移到To區(qū)域,對象頭中分代年齡都+1,如上圖。


          779e106837c42dcdf22a307636632111.webp


          那么當?shù)谌蜤den區(qū)又滿的時候,minor gc就是回收Eden區(qū)和To區(qū)域了,TEden區(qū)和To區(qū)域還活著的對象就會都移到From區(qū),如上圖。說白了就是Survivor區(qū)中總有一塊區(qū)域是空著的,存活的對象存放是在From區(qū)和To區(qū)輪流存放,也就是互相復制拷貝,這也就是垃圾回收算法中的復制-回收算法。


          如果一個對象經(jīng)歷了一個限值15次gc的時候,就會移至老年代。那如果還沒有到限值,F(xiàn)rom區(qū)或者To區(qū)域也放不下了,就會直接挪到老年代,這只是舉例了兩種常規(guī)規(guī)則,還有其他規(guī)則也是會把對象存放至老年代的。


          那么隨著應用程序的不斷運行,老年代最終也是會滿的,那么此時也會gc,此時的gc就是Full gc了。


          GC案例

          下面我們通過一個簡單的演示案例來更加清楚的了解GC。

          ?

          public?class?HeapTest?{
          ????byte[] a = new?byte[1024*100];
          ????public?static?void?main(String[] args) throws InterruptedException {
          ????????ArrayList heapTest = new?ArrayList<>();
          ????????while(true) {
          ????????????heapTest.add(new?HeapTest());
          ????????????Thread.sleep(10);
          ????????}
          ????}
          }


          這塊代碼很明顯,就是一個死循環(huán),不斷的往list中添加new出來的對象。


          我們這里使用jdk自帶的一個jvm調(diào)優(yōu)工具jvisualvm來觀察一下這個代碼執(zhí)行的的內(nèi)存結構。


          運行代碼打開之后我們可以看到這樣的界面:


          622a9b59b893991268fbd279c98c8004.webp


          我們在左邊的應用程序中可以看到我們運行的這個代碼,右邊是它的一些jvm,內(nèi)存信息,我們這里不關注,我們需要用到的是最后一個Visual GC面板,這是一個插件,如果你的打開沒有這一欄的話,可以再工具欄的插件中進行下載安裝。


          ?打開visual GC,我們先看一下界面大概的布局,


          802fcd3a6c6f5933d0b55404b5d07135.webp


          其中老年代(Olc),伊甸園區(qū)(Eden),S0(From),S1(To)幾個區(qū)域的內(nèi)存和動態(tài)分配圖都是清晰可見,以一對應的。


          c4b37440a07e53426f8b177d84332dd9.webp


          我們選擇中間一張圖給大家對應一下上面所講的內(nèi)容:


          1:對象放入Eden區(qū)

          2:Eden區(qū)滿發(fā)生minor gc

          3:第二步的存活對象移至From(Survivor 0)區(qū)

          4:Eden區(qū)再滿發(fā)生minor gc

          5:第四步存活的對象移至To(Survivor 1)區(qū)


          9fafd07f9c8a7c5f3bb66c608ea95c0f.webp


          這里可以注意到From和To區(qū)域和我們上面所說移至,總有一個是空的。?


          7433f934e94d0b65f2f13f48638f2ea7.webp


          大家還可以注意到老年代這里,都是一段一段的直線,中間是突然的增加,這就是在minor gc中一批一批符合規(guī)則的對象被批量移入老年代。


          ?那當我們老年代滿了會發(fā)生什么呢?當然是我們上面說過的Full GC,但是你仔細看我們寫的這個程序,我們所有new出來的HeapTest對象都是存放在heapLists中的,那就會被這個局部變量所引用,那么Full GC就不會有什么垃圾對象可以回收,可是內(nèi)存又滿了,那怎么辦?


          84027a284efd22f99b2dc240a193433f.webp


          沒錯,就是我們就算沒見過也總聽過的OOM。

          到這里jvm內(nèi)存模型簡單介紹就結束了,看到這里還不點個贊嘛!?


          推薦閱讀:


          9157ac1877348d2a1a052a3add16ea31.webp喜歡我可以給我設為星標哦9157ac1877348d2a1a052a3add16ea31.webp

          414a4a5e46c99fd5084bfc5cb2bc8617.webp

          好文章,我?在看?

          7acd39fdd702b38e440a83059792708d.webp
          瀏覽 85
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久另类TS人妖一区二区免费 | 亚洲精品字幕久久久久 | 天堂Av在线成人电影 | 成人18禁秘 啪啪网站网站 | 操逼视频免费看 |