成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java垃圾收集器進化論:從Serial的青銅時代到Shenandoah的王者征途 ——突出技術發展脈絡,用時代隱喻增強畫面感

開發 前端
對于單核處理器或處理器核心數較少的環境,Serial由于沒有線程交互的開銷,專心做垃圾收集,單線程收集效率也是優于其他收集器。

常見的垃圾收集器

Serial收集器

在JDK1.3.1之前是HotSpot新生代收集器的唯一選擇,是一個單線程工作的收集器。

這里的單線程是指在進行垃圾收集時,必須暫停其他所有工作線程,直到它收集結束。

Serial/Serial old收集器運行示意圖Serial/Serial old收集器運行示意圖

雖然Serial收集器需要頻繁的暫停線程,來完成GC工作,但是它也是有著優于其他收集器的地方:簡單高效。

對于內存資源受限的環境,它是所有收集器里額外內存消耗最小的。

對于單核處理器或處理器核心數較少的環境,Serial由于沒有線程交互的開銷,專心做垃圾收集,單線程收集效率也是優于其他收集器。

所以Serial收集器對于運行在客戶端模式下的虛擬機來說是一個很好的選擇。

ParNew收集器

ParNew收集器實質上是Serial收集器的多線程并行版本,除了同時使用多線程進行垃圾收集之外,其余的行為包括Serial收集器可用的所有控制參數、收集算法、對象分配規則、回收策略等都與Serial收集器完全一致。

ParNew/Serial Old收集器運行示意圖ParNew/Serial Old收集器運行示意圖

JDK5時發布的首款真正意義上支持并發的垃圾收集器CMS,因無法與JDK1.4.0中已經存在的新生代收集器Parallel Scavenge配合工作,所以在JDK5中使用CMS來收集老年代時,新生代只能選擇ParNew和Serial其中一個,HotSpot將ParNew設置為默認新生代收集器,用于搭配CMS進行垃圾收集。也可以使用-XX:+/-UseParNewGC選項來強制指定或禁用它。

直到JDK9開始,ParNew+CMS收集器的組合就不再是官方推薦的服務端模式下的收集器解決方案了,并且直接取消了-XX:+/-UseParNewGC參數,這就意味著ParNew+CMS成為了固定搭配,不可以改變。

ParNew收集器在單核處理器的環境中,比Serial收集器性能差,甚至存在線程交互的開銷。

ParNew收集器通過超線程(Hyper-Threading)技術實現的偽雙核處理器環境都沒辦法保證性能高于Serial。

但是隨著被使用的處理器核心數增加,ParNew對于垃圾收集時系統資源的高效利用還是有好處的。

ParNew默認開啟的收集線程數和處理器核心數量相同,在處理器核心非常多的環境中,可以使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數。

Parallel Scavenge

Parallel Scavenge收集器也是一款新生代收集器,它同樣是基于標記-復制算法實現的收集器,支持并行收集的多線程收集器。

Parallel Scavenge收集器的目的是**達到一個可控制的吞吐量(Throughput)**,所謂的吞吐量就是處理器用于運行用戶代碼的時間與處理器總消耗時間的比值。

吞吐量運行用戶代碼時間運行用戶代碼時間運行垃圾收集時間。

Parallel Scavenge收集器提供了兩個參數用于精準控制吞吐量,分別是:

  • -XX:MaxGCPauseMillis:控制最大垃圾收集停頓時間。允許的值是一個大于0的整數
  • -XX:GCTimeRatio:設置吞吐量大小。允許值是一個大于0小于100的整數

Parallel Scavenge還提供了一個很重要的參數:

  • -XX:+UseAdaptiveSizePolicy:自適應垃圾收集策略,提供最適合的停頓時間或最大的吞吐量。開啟后不需要人工指定新生代的大小、Eden與Survivor的比例、晉升老年代對象大小等。

Serial Old

Serial收集器的老年代版本,同樣也是單線程收集器,基于標記-整理算法實現。主要用于在客戶端模式下的HotSpot虛擬機使用。

在服務端有兩種用途:

  • 在JDK5以及之前的版本中與Parallel Scavenge收集器搭配使用。
  • 作為CMS收集器發生失敗時的備用方案,即并發收集發生Concurrent Mode Failure。

Parallel Old

Parallel Old是Parallel Scavenge收集器的老年代版本,支持多線程并發收集,基于標記-整理算法實現。這個收集器是直到JDK6才開始提供。

直到Parallel Old的出現,Parallel Scavenge才有了組合,在注重吞吐量或者處理器資源比較稀缺的場合,都可以優先考慮Parallel Scavenge加Parallel Old收集器這個組合。

Parallel Scavenge/Parallel Old收集器運行示意圖Parallel Scavenge/Parallel Old收集器運行示意圖

Parallel Scavenge收集器架構中有PS MarkSweep收集器來進行老年代收集,并非直接調用Serial Old收集器,但是PS MarkSweep收集器與Serial Old的實現幾乎一樣。

CMS收集器

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

CMS收集器的出現,解決了Java互聯網網站或者B/S系統的服務端應用響應速度慢的問題。

CMS收集器基于標記-清除算法實現,它的運作過程有些復雜,整個過程分為4個步驟:

  • 初始標記(CMS initial mark):需要暫停用戶線程(Stop The World),但是初始標記僅僅只是標記一下GC Root能直接關聯的對象,速度很快。
  • 并發標記(CMS concurrent mark):不需要Stop The World,開始遍歷整個對象圖的過程,這個過程耗時較長,但是可以與垃圾收集線程一起并發運行。
  • 重新標記(CMS remark):需要Stop The World,修正并發標記期間因用戶線程繼續運作而導致標記產生變動的一部分對象的標記記錄,比初始標記耗時稍長一些。
  • 并發清除(CMS concurrent sweep):不需要Stop The World,清理標記階段判斷的已經死亡的對象

CMS收集器運行示意圖CMS收集器運行示意圖

CMS收集器是HotSpot虛擬機追求低停頓的第一次成功嘗試,但是也至少存在以下三個缺點:

  • CMS收集器對處理器資源非常敏感,即在并發階段,雖然不會導致用戶線程停頓,但是會因為占用了一部分線程導致應用程序停頓,降低總吞吐量。
    CMS默認啟動的回收線程數是(處理器核心數量+3)/4,如果處理器核心數量越多,自然占用資源比也會下降,但是當核心線程數不足4個,就會極大影響應用程序的運行。
    為了緩解這個情況,虛擬機提供了增量式并發(i-CMS)收集器,即采用搶占式讓收集器線程和用戶線程交替運行,但是效果很一般,JDK7被標記為deprecated,JDK9完全棄用。
  • CMS會產生“浮動垃圾”,必須在并發收集時預留一部分空間供并發收集時的程序運作使用。
    預留空間策略
    一定要注意-XX:CMSInitiatingOccupancyFraction參數的配置值,太高可能會觸發并發失敗,導致SerialOld代替CMS;太低會導致回收頻率過高,效率降低。

在JDK5的默認設置下,CMS當老年代使用了68%的空間就會被激活,這是一個偏保守的設置,如果在實際應用中老年代增長并不快,可以適當調高參數-XX:CMSInitiatingOccupancyFraction的值來提高CMS的觸發百分比,降低內存回收頻率,獲取更好的性能。

在JDK6時,CMS收集器的啟動閾值默認為92%,這會面臨另一種風險:要是CMS運行期間預留的內存無法滿足程序分配新對象的需要,就會出現一次并發失敗(Concurrent Mode Failure),這時候虛擬機將不得不啟動后備預案:凍結用戶線程執行,臨時啟用Serial Old收集器重新進行老年代的垃圾收集,但這樣停頓時間就很長了。

  • 什么是浮動垃圾?
    CMS在并發標記和并發清理階段,用戶線程是還在繼續運行的,程序在運行過程中伴隨的新垃圾對象的產生,但這一部分對象是出現在標記結束之后,因此CMS無法在當次收集中處理掉它們,只能在下一次垃圾收集時再清理掉,這一部分垃圾就稱為浮動垃圾。
  • 浮動垃圾會造成什么影響
    如果CMS在標記過程中,產生了大量的浮動垃圾,導致內存占用過多,就會觸發Concurrent Mode Failure失敗,此時會喚醒一次Full GC(Full GC是完全Stop The World的)。
  • CMS收集垃圾后會產生大量的空間碎片(基于標記-清除算法實現導致的),往往會出現老年代剩余空間很多,當無法找到足夠大的連續空間分配當前對象的情況,只能提前觸發一次Full GC。
  • 解決方案一:CMS提供了一個參數:-XX:+UseCMSCompactAtFullCollection開關參數(默認開啟,JDK9開始廢棄),用于在CMS不得不進行Full GC時開啟內存碎片整理,必須移動存活對象,在Shenandoah和ZGC出現之前是無法并發的,導致停頓時間變長。
  • 解決方案二:CMS提供了另一個參數:-XX:CMSFullGCsBeforeCompaction(JDK9開始廢棄),這個參數的作用就是要求CMS在執行過若干次不整理空間的Full GC之后,下一次進入Full GC前會進行碎片整理(默認值為0.表示每次進入Full GC時都會進行碎片整理)。

Garbage First收集器

Garbage First就是我們常說的G1收集器,是垃圾收集器技術發展歷史上的里程碑式的成果,開創了收集器面向局部收集的設計思路和基于Region的內存布局形式 G1是一款面向服務端應用的垃圾收集器,從JDK9開始成為了服務端模式下的默認垃圾收集器,CMS從此退出歷史舞臺,成為了Deprecate的收集器 G1從整體上看是基于標記-整理算法實現的收集器,但從局部(Region)上看又是基于標記-復制算法實現的。

可以使用參數-XX:+UseConcMarkSweepGC開啟CMS收集器的使用,但是會收到警告,提示CMS未來將會被廢棄。

G1停頓時間模型(Pause Prediction Model)

停頓時間模型:能夠支持指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間大概率不超過N毫秒。

G1的設計思路:

  • 面向堆內存任何部分來組成回收集(Collection Set,一般簡稱CSet)進行回收,衡量標準從分代轉移到哪塊內存中存放的垃圾數量最多,回收收益最大。這就是G1收集器的Mixed GC模式。
  • G1不再堅持固定大小以及固定數量的分代區域劃分,而是**把連續的Java堆劃分為多個大小相等的獨立區域(Region)**,每個Region都可以根據需要,扮演新生代的Eden空間、Survivor空間或者老年代空間,收集器根據扮演不同角色的Region采用不同的策略取處理。
    G1雖然保留了新生代和老年代的概念,但是新生代和老年代不再是固定的了,它們是一系列區域的動態集合(不需要連續,且新生代/老年代不再是固定區域,而是動態的)。
  • Region中還有一類特殊的區域Humongous Region,專門用來存儲大對象。G1認為只要大小超過一個Region容量一半的對象即可判定為大對象,每個Region的大小通過參數-XX:G1HeapRegionSize設定,取值范圍:1MB~32MB,且應為2的N次冪。
  • 超過Region容量一半的對象就會放入Humongous Region中,如果超過整個Region容量的超級大對象,就會被存放在N個連續的Humongous Region,G1的大多數行為都會把Humongous Region作為老年代的一部分來看待。
  • **每次收集的內存空間都是Region大小的整數倍(將Region作為單次回收的最小單元)**,這樣可以有計劃地避免在整個Java堆中進行全區域的垃圾收集。
    這就是垃圾優先(Garbage First)收集器的由來,這種機制保證了G1收集器在有限的時間內獲取盡可能高得收集效率。

G1跟蹤各個Region里面的垃圾堆積的價值,價值即回收所獲得的空間大小以及回收所需的時間。

在后臺維護一個優先級列表,每次根據用戶設定允許的收集停頓時間(-XX:MaxGCPauseMillis參數指定,默認值為200ms),優先處理回收價值收益最大的那些Region。

  • G1的記憶集設計

G1的記憶集本質上是哈希表,key是下一個Region的起始地址,value是一個集合(里面存儲的元素是卡表的索引地址),這是一種雙向記憶集結構,不僅記錄了我指向誰,也記錄了誰指向我,比原來的卡表實現更復雜,同時由于Region數量比傳統收集器的分代數量要多得多,因此G1收集器要比其他的傳統垃圾收集器有著更高的內存占用負擔(消耗堆內存的10%~20%來維持收集器工作)。

  • G1解決并發標記階段如何保證收集線程與用戶線程互不干擾運行?
    如果內存回收的速度趕不上內存分配的速度,G1收集器也要被迫凍結用戶線程執行,導致Full GC而產生長時間的Stop The World。

用戶線程持續運行過程中新對象的創建導致的內存分配問題,G1為每一個Region設計了兩個TAMS(Top At Mark Start)的指針,把Region中的一部分空間劃分出來用于并發回收過程中的新對象分配。

并發回收時,新分配的對象地址都必須在這兩個指針位置上,即在這兩個指針位置上的對象默認是存活的,不納入回收范圍。

原始快照算法

  • 停頓預測模型的設計

在垃圾收集過程中,G1收集器會記錄每個Region的回收耗時、每個Region記憶集里的臟卡數量等各個可測量的步驟花費的成本。

分析得出平均值、標準偏差、置信度等統計信息。

然后通過這些信息預測現在開始回收的話,由哪些Region組成回收集才可以不超過期望停頓時間的約束的情況下獲取最高的收益。

提供了-XX:MaxGCPauseMillis參數指定停頓時間,但是這個只是垃圾收集之前的期望值。

G1收集器的停頓預測模型是以**衰減均值(Decaying Average)**為理論基礎來實現的。

G1收集器的運行過程

該過程不計算用戶線程運行過程的動作,例如使用寫屏障去維護記憶集的操作。

  • 初始標記(Initial Marking):僅僅只是標記一下GC Roots能直接關聯到的對象,并且修改TAMS指針的值,讓下一階段用戶線程并發運行時,能正確在可用的Region中分配新對象。這個階段需要停頓線程,但是耗時很短,而且是借用進行MinorGC的時候同步完成的,所以G1收集器在這個階段并沒有額外的停頓。
  • 并發標記(Concurrent Marking):從GC Roots開始對堆中對象進行可達性分析,遞歸掃描整個堆里的對象圖,找出要回收的對象。這個階段與用戶線程并發執行,對象圖掃描完成之后,還要重新處理原始快照(SATB)記錄下在并發時有引用變動的對象。
  • 最終標記(Final Marking):處理并發階段結束后仍遺留下來的最后那少量的SATB記錄。這個階段需要對用戶線程暫停(非常短暫)。
  • 篩選回收(Live Data Counting and Evacuation):負責更新Region的統計數據,對各個Region的回收價值和成本進行排序,根據用戶所期望的停頓時間來制定回收計劃,可以自由選擇任意多個Region構成回收集,然后把決定回收的那一部分Region的存活對象復制到空的Region中,再清理掉整個舊的Region的全部空間。這個階段涉及到存活對象的移動,需要暫停用戶線程,由多條收集器線程并行完成。

G1收集器運行示意圖G1收集器運行示意圖

期望停頓時間說明:

  • 默認停頓時間為200毫秒。
  • 如果把停頓時間調得很低,很可能出現的結果就是由于停頓目標時間太短,導致每次選出的回收集只占堆內存很小的部分,收集器收集的速度逐漸跟不上分配器分配的蘇速度導致垃圾堆積。
  • 如果把停頓時間調得很高,很可能出現的結果就是由于停頓目標時間太長,導致垃圾回收過程對應用程序造成較長的停頓,從而影響系統的響應性能。

低延遲垃圾收集器

理解延遲的概念:隨著目前硬件性能的增長,內存的擴大,對延遲帶來的更多的是負面效果 例如:原來有8G內存,垃圾收集器最多也就收集8G垃圾,但是現在16G,垃圾收集器就要收集16G垃圾,收集8G垃圾和收集16G垃圾延遲自然是16G更久。

CMS和G1之前的全部收集器,工作時所有步驟都會產生Stop The World。

CMS和G1分別用增量更新和原始快照技術實現了標記階段的并發,不會因為管理的堆內存變大,要標記的對象變多而導致停頓時間增長。

各類收集器的并發情況各類收集器的并發情況

Shenandosh和ZGC幾乎整個工作過程都處于并發狀態,只有初始標記、最終標記階段有短暫的停頓,這部分的停頓時間基本上是固定的,與堆容量、堆中對象的數量沒有正比例關系。接下來就介紹一款低延遲垃圾收集器:Shenandoah收集器。

Shenandoah收集器

由于Shenandoah收集器不是Oracle公司的虛擬機團隊所開發的,因此目前只有OpenJDK12及以上支持Shenandoah收集器,OracleJDK并不支持 Shenandoah收集器實現的目標就是能在任何堆內存下都可以把垃圾收集的停頓時間限制在10毫秒以內的垃圾收集器。

Shenandoah是G1的下一代繼承者,在初始標記、并發標記等許多階段的處理思路都高度一致,甚至還共享了一部分實現代碼。

G1在并發失敗后作為逃生門的Full GC也是由于合并了Shenandoah的代碼才獲得的特性。

Shenandoah收集器相比于G1收集器做了如下改進:

  • Shenandoah收集器支持并發的整理算法,不僅可以實現多線程并行,還可以實現多線程并發。
    G1收集器的回收階段支持多線程并行,但是卻不能與用戶線程并發。
  • Shenandoah收集器默認不使用分代收集,即不會有專門的新生代或老年代的存在。
  • Shenandoah收集器放棄了記憶集,改用連接矩陣的全局數據結構記錄跨Region的引用關系,降低了處理跨代指針時的記憶集維護消耗,也降低了偽共享問題的發生概率什么是連接矩陣?可以簡單的理解為一張二維表格,如果RegionN有對象指向RegionM,就在表格的N行M列中打上一個標記,最終回收時通過這個表格就可以得出哪些Region之間產生了跨代引用。

image.pngimage.png

  • image.png

image.pngimage.png

假設:Region5中的對象Baz引用了Region3的Foo,Foo又引用了Region1的Bar,那么連接矩陣中的第5行第3列和第3行第1列就應該打上標記記。

Shenandoah收集器的工作過程:

  • 初始標記(Initial Marking):首先標記與GC Roots直接關聯的對象,這個階段也是Stop The World,但是停頓時間與堆大小無關,只與GC Roots的數量相關。
  • 并發標記(Concurrent Marking):遍歷對象圖,標記出全部可達的對象,這個階段是與用戶線程一起并發的,時間長短取決于堆中存活對象的數量以及對象圖的結構復雜度。
  • 最終標記(Final Marking):處理剩余的SATB掃描,并在這個階段統計出回收價值最高的Region,將這些Region構成一組回收集。最終標記階段也會有一小段短暫的停頓。
  • 并發清理(Concurrent Cleanup):這個階段用于清理那些整個區域內連一個存活對象都沒有找到的Region,這類Region被稱為Immediate Garbage Region。
  • 并發回收(Concurrent Evacuation):并發回收階段是Shenandoah與之前HotSpot中其他收集器的核心差異。在這個階段,需要先把回收集里面存活對象先復制一份到其他未被使用的Region中,復制對象這件事是并發進行的,Shenandoah將會通過讀屏障和被稱為Brooks Points的轉發指針來解決。并發回收階段運行時長取決于回收集的大小。
  • 初始引用更新(Initial Update Reference):并發回收階段復制對象結束后,還需要把堆中所有指向舊對象的引用修正到復制后的新地址,這個操作就叫做引用更新。但是初始引用更新并沒有做什么處理,這個階段只是為了建立一個線程集合點,確保所有并發回收階段中進行的收集線程都已經完成分配給它們的對象移動任務。這個階段停頓非常短暫。
  • 并發引用更新(Concurrent Update Reference):真正開始進行引用更新操作,并發引用更新只需要按照內存物理地址的順序,線性地搜索出引用類型,把舊值改為新值即可,這個階段是與用戶線程一起并發的,時間長短取決于內存中涉及到的引用數量的多少。
  • 最終引用更新(Final Update Reference):解決了堆中的引用更新之后,整個回收集中所有的Region已再無存活對象,這些Region都會變成Immediate Garbage Regions了,最后再調用一次并發清理過程來回收這些Region內存空間。

Shenandoah收集器的工作流程Shenandoah收集器的工作流程

Shenandoah收集器的工作流程

黃色區域表示被選入回收集的Region

綠色區域表示還存活的對象

藍色區域表示用戶線程可以用于分配對象的內存Region

支持并行整理的核心概念:Brooks Pointer

Brooks Pointer就是轉發指針(Forwarding Pointer),轉發指針是實現對象移動與用戶程序并發的一種解決方案

內存保護陷阱:而在轉發指針出現之前,都是采用的內存保護陷阱,一旦用戶程序訪問到歸屬于舊對象的內存空間就會產生自陷中斷,進入預設好的異常處理器中,再由其中的代碼邏輯把訪問轉發到復制后的新對象上。這個方案雖然能實現對象移動與用戶線程并發,但是如果沒有操作系統層面的支持,這種方案將導致用戶態頻繁切換到核心態,代價是非常大的。

轉發指針:在這種情況下,Brooks提出了新方案,即轉發指針,不需要再使用內存保護陷阱,而是在原有對象布局結構的最前面統一增加一個新的引用字段,在正常情況下(不產生并發移動),該引用指向對象自己,當發生對象并發移動時,就會將該引用從舊對象更新為移動后的新對象。這就可以將該對象的訪問轉發到新的副本對象上。

image.pngimage.png

轉發指針的缺點

  1. 每次對象訪問會帶來一次額外的轉向開銷,盡管這個開銷已經被優化到只有一行匯編指令,但是頻繁使用,代價也不可忽視。
    mov r13,QWORD PTR [r12+r14*8-0x8]這個開銷比內存保護陷阱的方案好多了。
  2. 轉發指針會產生多線程競爭問題,如果收集器線程與用戶線程發生的只是并發讀取,那無論讀到舊對象還是新對象,返回的結果都是一樣的。但是如果發生的是并發寫入,就必須保證寫操作只能發生在新復制的對象上。
    Shenandoah收集器通過CAS(Compare And Swap)操作來保證并發時對象的訪問正確性,只有其中之一能訪問成功,另一個必須等待,避免了兩者交替進行,導致寫錯誤。
  3. 轉發指針的執行頻率是需要注意的,這是因為對象訪問的頻率是非常高的,對于任何一門面向對象的編程語言,讀取、寫入、比較、為對象計算hash值、用對象加鎖等都屬于對象訪問的范疇,Shenandoah收集器在讀、寫屏障中都加入了額外的轉發處理,尤其是讀屏障所花費的代價比寫屏障要高得多(讀比寫出現的頻率高很多)。
    JDK13中Shenandoah的內存屏障模型改進為**基于引用訪問屏障(Load Reference Barrier)**的實現,省去了大量對原生類型、對象的比較、對象的加鎖等場景中設置內存屏障所帶來的消耗。

轉發指針的優點

  • 當對象擁有了一份新的副本時,只需要修改一處指針的值(即舊對象上轉發指針的引用位置), 使其指向新對象,便可將所有對該對象的訪問轉發到新的副本上。
    只要舊對象的內存存在,沒有清理掉,所有通過舊引用地址訪問的代碼都是可用的,都會轉發到新對象上繼續工作
    一旦清理,所有引用類型的變量的引用地址都會更新為新的地址。
責任編輯:武曉燕 來源: 愛編程的杰尼龜
相關推薦

2013-10-17 16:46:00

DevOps

2015-06-05 12:14:57

DevOps云應用開發Docker

2010-05-11 10:56:41

HTML 5

2009-10-28 09:11:17

2025-06-23 15:55:46

2012-11-14 16:12:17

2012-11-14 16:17:28

淘寶Tair

2009-11-18 10:11:10

路由器技術

2022-04-19 11:25:31

JVMZGC垃圾收集器

2016-12-23 14:43:37

2012-06-05 01:40:00

Java

2021-05-26 09:46:40

智能

2012-11-14 15:57:02

淘寶技術

2018-11-06 12:23:12

2011-07-21 14:54:26

java垃圾收集器

2022-07-25 10:15:29

垃圾收集器Java虛擬機

2020-09-01 15:08:11

新華三

2012-11-14 15:43:29

淘寶技術

2025-03-24 00:11:05

IO模型計算機
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 精品免费国产一区二区三区四区 | 在线免费观看成人 | 亚洲人精品 | 欧美激情久久久 | 一级毛片中国 | 亚洲精品www| 99av成人精品国语自产拍 | 一区二区免费 | 少妇无套高潮一二三区 | 91小视频| 黄色a级一级片 | 欧美日日 | 视频一区在线观看 | 欧美亚州| 日韩在线免费观看视频 | 亚洲乱码国产乱码精品精的特点 | 亚洲免费一 | 中文字幕综合 | 91久久国产综合久久 | 国产亚洲一区二区精品 | 欧美日韩久久 | 日韩精品在线一区 | 亚洲精品自拍视频 | 亚洲精品乱码久久久久久蜜桃91 | 亚洲国产成人精品久久久国产成人一区 | 国产精品视频不卡 | av免费网址 | 精品九九 | 日韩免费视频一区二区 | www亚洲精品 | 成人精品在线观看 | 国产精品自产拍 | 精品欧美乱码久久久久久 | av性色全交蜜桃成熟时 | 男女性毛片 | 奇米视频777| 国产精品日本一区二区在线播放 | 国产日韩一区二区三免费高清 | 亚洲国产成人精品女人久久久 | 91精品久久久久久久久久小网站 | 日韩电影中文字幕 |