久久精品国产精品国产精品污,男人扒开添女人下部免费视频,一级国产69式性姿势免费视频,夜鲁夜鲁很鲁在线视频 视频,欧美丰满少妇一区二区三区,国产偷国产偷亚洲高清人乐享,中文 在线 日韩 亚洲 欧美,熟妇人妻无乱码中文字幕真矢织江,一区二区三区人妻制服国产

歡迎訪問 生活随笔!

生活随笔

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

编程问答

深入理解GO语言:GC原理及源码分析

發布時間:2024/1/8 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解GO语言:GC原理及源码分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Go 中的runtime 類似 Java的虛擬機,它負責管理包括內存分配、垃圾回收、棧處理、goroutine、channel、切片(slice)、map 和反射(reflection)等。Go 的可執行文件都比相對應的源代碼文件要大很多,這是因為 Go 的 runtime 嵌入到了每一個可執行文件當中。

常見的幾種gc算法:

引用計數:對每個對象維護一個引用計數,當引用該對象的對象被銷毀時,引用計數減1,當引用計數器為0是回收該對象。

優點:對象可以很快的被回收,不會出現內存耗盡或達到某個閥值時才回收。

缺點:不能很好的處理循環引用,而且實時維護引用計數,有也一定的代價。

代表語言:Python、PHP、Swift

標記-清除:從根變量開始遍歷所有引用的對象,引用的對象標記為"被引用",沒有被標記的進行回收。

優點:解決了引用計數的缺點。

缺點:需要STW,即要暫時停掉程序運行。

代表語言:Golang(其采用三色標記法)

分代收集:按照對象生命周期長短劃分不同的代空間,生命周期長的放入老年代,而短的放入新生代,不同代有不能的回收算法和回收頻率。

優點:回收性能好

缺點:算法復雜

代表語言: JAVA

每種算法都不是完美的,都是折中的產物。

Gc流程圖:

Stack scan:收集根對象(全局變量,和G stack),開啟寫屏障。全局變量、開啟寫屏障需要STW,G stack只需要停止該G就好,時間比較少。

?Mark: 掃描所有根對象, 和根對象可以到達的所有對象, 標記它們不被回收

Mark Termination: 完成標記工作, 重新掃描部分根對象(要求STW)

Sweep: 按標記結果清掃span

從上圖中我們可以看到整個GC流程會進行兩次STW(Stop The World), 第一次是Mark階段的開始, 第二次是Mark Termination階段.
第一次STW會準備根對象的掃描, 啟動寫屏障(Write Barrier)和輔助GC(mutator assist).
第二次STW會重新掃描部分根對象, 禁用寫屏障(Write Barrier)和輔助GC(mutator assist).
需要注意的是, 不是所有根對象的掃描都需要STW, 例如掃描棧上的對象只需要停止擁有該棧的G.

三色標記

有黑、灰、白三個集合,每種顏色的含義:

白色:對象未被標記,gcmarkBits對應的位為0

灰色:對象已被標記,但這個對象包含的子對象未標記,gcmarkBits對應的位為1

黑色:對象已被標記,且這個對象包含的子對象也已標記,gcmarkBits對應的位為1

灰色和黑色的gcmarkBits都是1,如何區分二者呢?

標記任務有標記隊列,在標記隊列中的是灰色,不在標記隊里中的是黑色。標記過程見下圖:

?

上圖中根對象A是棧上分配的對象,H是堆中分配的全局變量,根對象A、H內部有分別引用了其他對象,而其他對象內部可能還引用額其他對象,各個對象見的關系如上圖所示。

  • 初始狀態下所有對象都是白色的。
  • 接著開始掃描根對象,A、H是根對象所以被掃描到,A,H變為灰色對象。
  • 接下來就開始掃描灰色對象,通過A到達B,B被標注灰色,A掃描結束后被標注黑色。同理J,K都被標注灰色,H被標注黑色。
  • 繼續掃描灰色對象,通過B到達C,C 被標注灰色,B被標注黑色,因為J,K沒有引用對象,J,K標注黑色結束
  • 最終,黑色的對象會被保留下來,白色對象D,E,F會被回收掉。
  • 屏障

    ????????????????????????

    上圖,假如B對象變黑后,又給B指向對象G,因為這個時候G對象已經掃描過了,所以G 對象還是白色,會被誤回收。怎么解決這個問題呢?

    最簡單的方法就是STW(stop the world)。也就是說,停止所有的協程。這個方法比較暴力會引起程序的卡頓,并不友好。讓GC回收器,滿足下面兩種情況之一時,可保對象不丟失. 所以引出強-弱三色不變式:

    強三色不變式:黑色不能引用白色對象。

    弱三色不變式:被黑色引用的白色對象都處于灰色保護。

    如何實現這個兩個公式呢?這就是屏障機制。

    GO1.5 采用了插入屏障、刪除屏障。到了GO1.8采用混合屏障。黑色對象的內存槽有兩種位置, 棧和堆. 棧空間的特點是容量小,但是要求相應速度快,因為函數調用彈出頻繁使用, 所以“插入屏障”機制,在棧空間的對象操作中不使用. 而僅僅使用在堆空間對象的操作中。

    插入屏障:插入屏障只對堆上的內存分配起作用,棧空間先掃描一遍然后啟動STW后再重新掃描一遍掃描后停止STW。如果在對象在插入平展期間分配內存會自動設置成灰色,不用再重新掃描。

    刪除屏障:刪除屏障適用于棧和堆,在刪除屏障機制下刪除一個節點該節點會被置成灰色,后續會繼續掃描該灰色對象的子對象。該方法就是精準度不夠高

    混合屏障:

    插入寫屏障和刪除寫屏障的短板:

    插入寫屏障:結束時需要STW來重新掃描棧,標記棧上引用的白色對象的存活;

    刪除寫屏障:回收精度低,GC開始時STW掃描堆棧來記錄初始快照,這個過程會保護開始時刻的所有存活對象。

    混合寫屏障規則

    具體操作:

    1、GC開始將棧上的對象全部掃描并標記為黑色(之后不再進行第二次重復掃描,無需STW),

    2、GC期間,任何在棧上創建的新對象,均為黑色。

    3、被刪除的對象標記為灰色。

    4、被添加的對象標記為灰色。

    滿足: 變形的弱三色不變式.

    偽代碼如下:

    添加下游對象(當前下游對象slot, 新下游對象ptr) {//1 標記灰色(當前下游對象slot) //只要當前下游對象被移走,就標記灰色//2 標記灰色(新下游對象ptr)//3當前下游對象slot = 新下游對象ptr }

    上面說到整個GC有兩次STW,采用混合屏障后可以大幅壓縮第二次STW的時間。

    Gc pacer

    觸發gc的時機:

    閾值gcTriggerHeap:默認內存擴大一倍,啟動gc

    定期gcTriggerTime:默認2min觸發一次gc,src/runtime/proc.go:forcegcperiod

    手動gcTriggerCycle:runtime.gc()

    當然了閥值是根據使用內存的增加動態變化的。假如前一次GC之后內存使用Hm(n-1)為1GB,默認GCGO=100,那么下一次會在接近Hg(2GB)的位置發起新一輪的GC。如下圖:

    Ht的時候開始GC,Ha的時候結束GC,Ha非常接近Hg。

    (1)如何保證在Ht開始gc時所有的span都被清掃完?

    除了有一個后臺清掃協程外,用戶的分配內存時也需要輔助清掃來保證在開啟下一輪的gc時span都被清掃完畢。假設有k page的span需要sweep,那么距離下一次gc還有Ht-Hm(n-1)的內存可供分配,那么平均每申請1byte內存需要清掃k/ Ht-Hm(n-1) page?的span。(k值會根據sweep進度更改)

    輔助清掃申請新span時才會檢查,,輔助清掃的觸發可以看cacheSpan函數, 觸發時G會幫助回收"工作量"頁的對象, 工作量的計算公式是:

    spanBytes * sweepPagesPerByte

    意思是分配的大小乘以系數sweepPagesPerByte, sweepPagesPerByte的計算在函數gcSetTriggerRatio中, 公式是:

    // 當前的Heap大小 heapLiveBasis := atomic.Load64(&memstats.heap_live) // 距離觸發GC的Heap大小 = 下次觸發GC的Heap大小 - 當前的Heap大小 heapDistance := int64(trigger) - int64(heapLiveBasis) heapDistance -= 1024 * 1024 if heapDistance < _PageSize {heapDistance = _PageSize } // 已清掃的頁數 pagesSwept := atomic.Load64(&mheap_.pagesSwept) // 未清掃的頁數 = 使用中的頁數 - 已清掃的頁數 sweepDistancePages := int64(mheap_.pagesInUse) - int64(pagesSwept) if sweepDistancePages <= 0 {mheap_.sweepPagesPerByte = 0 } else {// 每分配1 byte(的span)需要輔助清掃的頁數 = 未清掃的頁數 / 距離觸發GC的Heap大小mheap_.sweepPagesPerByte = float64(sweepDistancePages) / float64(heapDistance) }

    ?

    (2)如何保證在Ha時gc都被mark完?

    Gc在Ht開始,在到達Hg時盡量標記完所有的對象,除了后臺的標記協程外還需要在分配內存是進行輔助mark。從Ht到Hg的內存可以分配,這個時候還有scanWorkExpected的對象需要scan,那么平均分配1byte內存需要輔助mark量:scanWorkExpected/(Hg-Ht) 個對象,scanWorkExpected會根據mark進度更改。

    輔助標記的觸發可以查看上面的mallocgc函數, 觸發時G會幫助掃描"工作量"個對象, 工作量的計算公式是:

    debtBytes * assistWorkPerByte

    意思是分配的大小乘以系數assistWorkPerByte, assistWorkPerByte的計算在函數revise中, 公式是:

    // 等待掃描的對象數量 = 未掃描的對象數量 - 已掃描的對象數量 scanWorkExpected := int64(memstats.heap_scan) - c.scanWork if scanWorkExpected < 1000 {scanWorkExpected = 1000 } // 距離觸發GC的Heap大小 = 期待觸發GC的Heap大小 - 當前的Heap大小 // 注意next_gc的計算跟gc_trigger不一樣, next_gc等于heap_marked * (1 + gcpercent / 100) heapDistance := int64(memstats.next_gc) - int64(atomic.Load64(&memstats.heap_live)) if heapDistance <= 0 {heapDistance = 1 } // 每分配1 byte需要輔助掃描的對象數量 = 等待掃描的對象數量 / 距離觸發GC的Heap大小 c.assistWorkPerByte = float64(scanWorkExpected) / float64(heapDistance) c.assistBytesPerWork = float64(heapDistance) / float64(scanWorkExpected)

    ?根對象

    在GC的標記階段首先需要標記的就是"根對象", 從根對象開始可到達的所有對象都會被認為是存活的.
    根對象包含了全局變量, 各個G的棧上的變量等, GC會先掃描根對象然后再掃描根對象可到達的所有對象.

    Fixed Roots: 特殊的掃描工作 :

    fixedRootFinalizers: 掃描析構器隊列

    fixedRootFreeGStacks: 釋放已中止的G的棧

    Flush Cache Roots: 釋放mcache中的所有span, 要求STW

    Data Roots: 掃描可讀寫的全局變量

    BSS Roots: 掃描只讀的全局變量

    Span Roots: 掃描各個span中特殊對象(析構器列表)

    Stack Roots: 掃描各個G的棧

    標記階段(Mark)會做其中的"Fixed Roots", "Data Roots", "BSS Roots", "Span Roots", "Stack Roots".
    完成標記階段(Mark Termination)會做其中的"Fixed Roots", "Flush Cache Roots".

    對象掃描

    當拿到一個對象的p時如何找到該對象的span和heapbit。以下分析是基于go1.10

    我們在內存分配部分介紹過2 bit表示一個字,一個字節就可以表示4個字。2bit中一個表示是否被scan另一個表示該對象內是否有指針類型,根據地址p可以根據固定偏移計算出該p對應的hbit:

    func heapBitsForAddr(addr uintptr) heapBits {// 2 bits per work, 4 pairs per byte, and a mask is hard coded.off := (addr - mheap_.arena_start) / sys.PtrSizereturn heapBits{(*uint8)(unsafe.Pointer(mheap_.bitmap - off/4 - 1)), uint32(off & 3)} }

    查找p對應的span更簡單了,我們前面介紹過spans區域中就是記錄每個page對應的span結構,所以根據p對page取余計算出是第幾個page就可以找到對應的span指針了

    mheap_.spans[(p-mheap_.arena_start)>>_PageShift]

    以下分析是基于go1.11及之后

    Go1.11及以后的版本改用了稀疏索引的方式來管理整體的內存. 可以超過 512G 內存, 也可以允許內存空間擴展時不連續.在全局的 mheap struct 中有個 arenas 二階數組, 在 linux amd64 上,一階只有一個 slot, 二階有 4M 個 slot, 每個 slot 指向一個 heapArena 結構, 每個 heapArena 結構可以管理 64M 內存, 所以在新的版本中, go 可以管理 4M*64M=256TB 內存, 即目前 64 位機器中 48bit 的尋址總線全部 256TB 內存。可以通過指針加上一定得偏移量, 就知道屬于哪個 heap arean 64M 塊. 再通過對 64M 求余, 結合 spans 數組, 即可知道屬于哪個 mspan 了,結合 heapArean 的 bitmap 和每 8 個字節在 heapArean 中的偏移, 就可知道對象每 8 個字節是指針還是普通數據。

    源碼分析

    源碼分析引自:https://www.cnblogs.com/zkweb/p/7880099.html 講的很詳細:

    ?go觸發gc會從gcStart函數開始:

    // gcStart transitions the GC from _GCoff to _GCmark (if // !mode.stwMark) or _GCmarktermination (if mode.stwMark) by // performing sweep termination and GC initialization. // // This may return without performing this transition in some cases, // such as when called on a system stack or with locks held. func gcStart(mode gcMode, trigger gcTrigger) {// 判斷當前G是否可搶占, 不可搶占時不觸發GC// Since this is called from malloc and malloc is called in// the guts of a number of libraries that might be holding// locks, don't attempt to start GC in non-preemptible or// potentially unstable situations.mp := acquirem()if gp := getg(); gp == mp.g0 || mp.locks > 1 || mp.preemptoff != "" {releasem(mp)return}releasem(mp)mp = nil// 并行清掃上一輪GC未清掃的span// Pick up the remaining unswept/not being swept spans concurrently//// This shouldn't happen if we're being invoked in background// mode since proportional sweep should have just finished// sweeping everything, but rounding errors, etc, may leave a// few spans unswept. In forced mode, this is necessary since// GC can be forced at any point in the sweeping cycle.//// We check the transition condition continuously here in case// this G gets delayed in to the next GC cycle.for trigger.test() && gosweepone() != ^uintptr(0) {sweep.nbgsweep++}// 上鎖, 然后重新檢查gcTrigger的條件是否成立, 不成立時不觸發GC// Perform GC initialization and the sweep termination// transition.semacquire(&work.startSema)// Re-check transition condition under transition lock.if !trigger.test() {semrelease(&work.startSema)return}// 記錄是否強制觸發, gcTriggerCycle是runtime.GC用的// For stats, check if this GC was forced by the user.work.userForced = trigger.kind == gcTriggerAlways || trigger.kind == gcTriggerCycle// 判斷是否指定了禁止并行GC的參數// In gcstoptheworld debug mode, upgrade the mode accordingly.// We do this after re-checking the transition condition so// that multiple goroutines that detect the heap trigger don't// start multiple STW GCs.if mode == gcBackgroundMode {if debug.gcstoptheworld == 1 {mode = gcForceMode} else if debug.gcstoptheworld == 2 {mode = gcForceBlockMode}}// Ok, we're doing it! Stop everybody elsesemacquire(&worldsema)// 跟蹤處理if trace.enabled {traceGCStart()}// 啟動后臺掃描任務(G)if mode == gcBackgroundMode {gcBgMarkStartWorkers()}// 重置標記相關的狀態gcResetMarkState()// 重置參數work.stwprocs, work.maxprocs = gcprocs(), gomaxprocswork.heap0 = atomic.Load64(&memstats.heap_live)work.pauseNS = 0work.mode = mode// 記錄開始時間now := nanotime()work.tSweepTerm = nowwork.pauseStart = now// 停止所有運行中的G, 并禁止它們運行systemstack(stopTheWorldWithSema)// !!!!!!!!!!!!!!!!// 世界已停止(STW)...// !!!!!!!!!!!!!!!!// 清掃上一輪GC未清掃的span, 確保上一輪GC已完成// Finish sweep before we start concurrent scan.systemstack(func() {finishsweep_m()})// 清掃sched.sudogcache和sched.deferpool// clearpools before we start the GC. If we wait they memory will not be// reclaimed until the next GC cycle.clearpools()// 增加GC計數work.cycles++// 判斷是否并行GC模式if mode == gcBackgroundMode { // Do as much work concurrently as possible// 標記新一輪GC已開始gcController.startCycle()work.heapGoal = memstats.next_gc// 設置全局變量中的GC狀態為_GCmark// 然后啟用寫屏障// Enter concurrent mark phase and enable// write barriers.//// Because the world is stopped, all Ps will// observe that write barriers are enabled by// the time we start the world and begin// scanning.//// Write barriers must be enabled before assists are// enabled because they must be enabled before// any non-leaf heap objects are marked. Since// allocations are blocked until assists can// happen, we want enable assists as early as// possible.setGCPhase(_GCmark)// 重置后臺標記任務的計數gcBgMarkPrepare() // Must happen before assist enable.// 計算掃描根對象的任務數量gcMarkRootPrepare()// 標記所有tiny alloc等待合并的對象// Mark all active tinyalloc blocks. Since we're// allocating from these, they need to be black like// other allocations. The alternative is to blacken// the tiny block on every allocation from it, which// would slow down the tiny allocator.gcMarkTinyAllocs()// 啟用輔助GC// At this point all Ps have enabled the write// barrier, thus maintaining the no white to// black invariant. Enable mutator assists to// put back-pressure on fast allocating// mutators.atomic.Store(&gcBlackenEnabled, 1)// 記錄標記開始的時間// Assists and workers can start the moment we start// the world.gcController.markStartTime = now// 重新啟動世界// 前面創建的后臺標記任務會開始工作, 所有后臺標記任務都完成工作后, 進入完成標記階段// Concurrent mark.systemstack(startTheWorldWithSema)// !!!!!!!!!!!!!!!// 世界已重新啟動...// !!!!!!!!!!!!!!!// 記錄停止了多久, 和標記階段開始的時間now = nanotime()work.pauseNS += now - work.pauseStartwork.tMark = now} else {// 不是并行GC模式// 記錄完成標記階段開始的時間t := nanotime()work.tMark, work.tMarkTerm = t, twork.heapGoal = work.heap0// 跳過標記階段, 執行完成標記階段// 所有標記工作都會在世界已停止的狀態執行// (標記階段會設置work.markrootDone=true, 如果跳過則它的值是false, 完成標記階段會執行所有工作)// 完成標記階段會重新啟動世界// Perform mark termination. This will restart the world.gcMarkTermination(memstats.triggerRatio)}semrelease(&work.startSema) }

    接下來一個個分析gcStart調用的函數, 建議配合上面的"回收對象的流程"中的圖理解.

    函數gcBgMarkStartWorkers用于啟動后臺標記任務, 先分別對每個P啟動一個:

    // gcBgMarkStartWorkers prepares background mark worker goroutines. // These goroutines will not run until the mark phase, but they must // be started while the work is not stopped and from a regular G // stack. The caller must hold worldsema. func gcBgMarkStartWorkers() {// Background marking is performed by per-P G's. Ensure that// each P has a background GC G.for _, p := range &allp {if p == nil || p.status == _Pdead {break}// 如果已啟動則不重復啟動if p.gcBgMarkWorker == 0 {go gcBgMarkWorker(p)// 啟動后等待該任務通知信號量bgMarkReady再繼續notetsleepg(&work.bgMarkReady, -1)noteclear(&work.bgMarkReady)}} }

    這里雖然為每個P啟動了一個后臺標記任務, 但是可以同時工作的只有25%, 這個邏輯在協程M獲取G時調用的findRunnableGCWorker中:

    // findRunnableGCWorker returns the background mark worker for _p_ if it // should be run. This must only be called when gcBlackenEnabled != 0. func (c *gcControllerState) findRunnableGCWorker(_p_ *p) *g {if gcBlackenEnabled == 0 {throw("gcControllerState.findRunnable: blackening not enabled")}if _p_.gcBgMarkWorker == 0 {// The mark worker associated with this P is blocked// performing a mark transition. We can't run it// because it may be on some other run or wait queue.return nil}if !gcMarkWorkAvailable(_p_) {// No work to be done right now. This can happen at// the end of the mark phase when there are still// assists tapering off. Don't bother running a worker// now because it'll just return immediately.return nil}// 原子減少對應的值, 如果減少后大于等于0則返回true, 否則返回falsedecIfPositive := func(ptr *int64) bool {if *ptr > 0 {if atomic.Xaddint64(ptr, -1) >= 0 {return true}// We lost a raceatomic.Xaddint64(ptr, +1)}return false}// 減少dedicatedMarkWorkersNeeded, 成功時后臺標記任務的模式是Dedicated// dedicatedMarkWorkersNeeded是當前P的數量的25%去除小數點// 詳見startCycle函數if decIfPositive(&c.dedicatedMarkWorkersNeeded) {// This P is now dedicated to marking until the end of// the concurrent mark phase._p_.gcMarkWorkerMode = gcMarkWorkerDedicatedMode} else {// 減少fractionalMarkWorkersNeeded, 成功是后臺標記任務的模式是Fractional// 上面的計算如果小數點后有數值(不能夠整除)則fractionalMarkWorkersNeeded為1, 否則為0// 詳見startCycle函數// 舉例來說, 4個P時會執行1個Dedicated模式的任務, 5個P時會執行1個Dedicated模式和1個Fractional模式的任務if !decIfPositive(&c.fractionalMarkWorkersNeeded) {// No more workers are need right now.return nil}// 按Dedicated模式的任務的執行時間判斷cpu占用率是否超過預算值, 超過時不啟動// This P has picked the token for the fractional worker.// Is the GC currently under or at the utilization goal?// If so, do more work.//// We used to check whether doing one time slice of work// would remain under the utilization goal, but that has the// effect of delaying work until the mutator has run for// enough time slices to pay for the work. During those time// slices, write barriers are enabled, so the mutator is running slower.// Now instead we do the work whenever we're under or at the// utilization work and pay for it by letting the mutator run later.// This doesn't change the overall utilization averages, but it// front loads the GC work so that the GC finishes earlier and// write barriers can be turned off sooner, effectively giving// the mutator a faster machine.//// The old, slower behavior can be restored by setting// gcForcePreemptNS = forcePreemptNS.const gcForcePreemptNS = 0// TODO(austin): We could fast path this and basically// eliminate contention on c.fractionalMarkWorkersNeeded by// precomputing the minimum time at which it's worth// next scheduling the fractional worker. Then Ps// don't have to fight in the window where we've// passed that deadline and no one has started the// worker yet.//// TODO(austin): Shorter preemption interval for mark// worker to improve fairness and give this// finer-grained control over schedule?now := nanotime() - gcController.markStartTimethen := now + gcForcePreemptNStimeUsed := c.fractionalMarkTime + gcForcePreemptNSif then > 0 && float64(timeUsed)/float64(then) > c.fractionalUtilizationGoal {// Nope, we'd overshoot the utilization goalatomic.Xaddint64(&c.fractionalMarkWorkersNeeded, +1)return nil}_p_.gcMarkWorkerMode = gcMarkWorkerFractionalMode}// 安排后臺標記任務執行// Run the background mark workergp := _p_.gcBgMarkWorker.ptr()casgstatus(gp, _Gwaiting, _Grunnable)if trace.enabled {traceGoUnpark(gp, 0)}return gp }

    gcResetMarkState函數會重置標記相關的狀態:

    // gcResetMarkState resets global state prior to marking (concurrent // or STW) and resets the stack scan state of all Gs. // // This is safe to do without the world stopped because any Gs created // during or after this will start out in the reset state. func gcResetMarkState() {// This may be called during a concurrent phase, so make sure// allgs doesn't change.lock(&allglock)for _, gp := range allgs {gp.gcscandone = false // set to true in gcphaseworkgp.gcscanvalid = false // stack has not been scannedgp.gcAssistBytes = 0}unlock(&allglock)work.bytesMarked = 0work.initialHeapLive = atomic.Load64(&memstats.heap_live)work.markrootDone = false }

    stopTheWorldWithSema函數會停止整個世界, 這個函數必須在g0中運行:

    // stopTheWorldWithSema is the core implementation of stopTheWorld. // The caller is responsible for acquiring worldsema and disabling // preemption first and then should stopTheWorldWithSema on the system // stack: // // semacquire(&worldsema, 0) // m.preemptoff = "reason" // systemstack(stopTheWorldWithSema) // // When finished, the caller must either call startTheWorld or undo // these three operations separately: // // m.preemptoff = "" // systemstack(startTheWorldWithSema) // semrelease(&worldsema) // // It is allowed to acquire worldsema once and then execute multiple // startTheWorldWithSema/stopTheWorldWithSema pairs. // Other P's are able to execute between successive calls to // startTheWorldWithSema and stopTheWorldWithSema. // Holding worldsema causes any other goroutines invoking // stopTheWorld to block. func stopTheWorldWithSema() {_g_ := getg()// If we hold a lock, then we won't be able to stop another M// that is blocked trying to acquire the lock.if _g_.m.locks > 0 {throw("stopTheWorld: holding locks")}lock(&sched.lock)// 需要停止的P數量sched.stopwait = gomaxprocs// 設置gc等待標記, 調度時看見此標記會進入等待atomic.Store(&sched.gcwaiting, 1)// 搶占所有運行中的Gpreemptall()// 停止當前的P// stop current P_g_.m.p.ptr().status = _Pgcstop // Pgcstop is only diagnostic.// 減少需要停止的P數量(當前的P算一個)sched.stopwait--// 搶占所有在Psyscall狀態的P, 防止它們重新參與調度// try to retake all P's in Psyscall statusfor i := 0; i < int(gomaxprocs); i++ {p := allp[i]s := p.statusif s == _Psyscall && atomic.Cas(&p.status, s, _Pgcstop) {if trace.enabled {traceGoSysBlock(p)traceProcStop(p)}p.syscalltick++sched.stopwait--}}// 防止所有空閑的P重新參與調度// stop idle P'sfor {p := pidleget()if p == nil {break}p.status = _Pgcstopsched.stopwait--}wait := sched.stopwait > 0unlock(&sched.lock)// 如果仍有需要停止的P, 則等待它們停止// wait for remaining P's to stop voluntarilyif wait {for {// 循環等待 + 搶占所有運行中的G// wait for 100us, then try to re-preempt in case of any racesif notetsleep(&sched.stopnote, 100*1000) {noteclear(&sched.stopnote)break}preemptall()}}// 邏輯正確性檢查// sanity checksbad := ""if sched.stopwait != 0 {bad = "stopTheWorld: not stopped (stopwait != 0)"} else {for i := 0; i < int(gomaxprocs); i++ {p := allp[i]if p.status != _Pgcstop {bad = "stopTheWorld: not stopped (status != _Pgcstop)"}}}if atomic.Load(&freezing) != 0 {// Some other thread is panicking. This can cause the// sanity checks above to fail if the panic happens in// the signal handler on a stopped thread. Either way,// we should halt this thread.lock(&deadlock)lock(&deadlock)}if bad != "" {throw(bad)}// 到這里所有運行中的G都會變為待運行, 并且所有的P都不能被M獲取// 也就是說所有的go代碼(除了當前的)都會停止運行, 并且不能運行新的go代碼 }

    finishsweep_m函數會清掃上一輪GC未清掃的span, 確保上一輪GC已完成:

    // finishsweep_m ensures that all spans are swept. // // The world must be stopped. This ensures there are no sweeps in // progress. // //go:nowritebarrier func finishsweep_m() {// sweepone會取出一個未sweep的span然后執行sweep// 詳細將在下面sweep階段時分析// Sweeping must be complete before marking commences, so// sweep any unswept spans. If this is a concurrent GC, there// shouldn't be any spans left to sweep, so this should finish// instantly. If GC was forced before the concurrent sweep// finished, there may be spans to sweep.for sweepone() != ^uintptr(0) {sweep.npausesweep++}// 所有span都sweep完成后, 啟動一個新的markbit時代// 這個函數是實現span的gcmarkBits和allocBits的分配和復用的關鍵, 流程如下// - span分配gcmarkBits和allocBits// - span完成sweep// - 原allocBits不再被使用// - gcmarkBits變為allocBits// - 分配新的gcmarkBits// - 開啟新的markbit時代// - span完成sweep, 同上// - 開啟新的markbit時代// - 2個時代之前的bitmap將不再被使用, 可以復用這些bitmapnextMarkBitArenaEpoch() }

    clearpools函數會清理sched.sudogcache和sched.deferpool, 讓它們的內存可以被回收:

    func clearpools() {// clear sync.Poolsif poolcleanup != nil {poolcleanup()}// Clear central sudog cache.// Leave per-P caches alone, they have strictly bounded size.// Disconnect cached list before dropping it on the floor,// so that a dangling ref to one entry does not pin all of them.lock(&sched.sudoglock)var sg, sgnext *sudogfor sg = sched.sudogcache; sg != nil; sg = sgnext {sgnext = sg.nextsg.next = nil}sched.sudogcache = nilunlock(&sched.sudoglock)// Clear central defer pools.// Leave per-P pools alone, they have strictly bounded size.lock(&sched.deferlock)for i := range sched.deferpool {// disconnect cached list before dropping it on the floor,// so that a dangling ref to one entry does not pin all of them.var d, dlink *_deferfor d = sched.deferpool[i]; d != nil; d = dlink {dlink = d.linkd.link = nil}sched.deferpool[i] = nil}unlock(&sched.deferlock) }

    startCycle標記開始了新一輪的GC:

    // startCycle resets the GC controller's state and computes estimates // for a new GC cycle. The caller must hold worldsema. func (c *gcControllerState) startCycle() {c.scanWork = 0c.bgScanCredit = 0c.assistTime = 0c.dedicatedMarkTime = 0c.fractionalMarkTime = 0c.idleMarkTime = 0// 偽裝heap_marked的值如果gc_trigger的值很小, 防止后面對triggerRatio做出錯誤的調整// If this is the first GC cycle or we're operating on a very// small heap, fake heap_marked so it looks like gc_trigger is// the appropriate growth from heap_marked, even though the// real heap_marked may not have a meaningful value (on the// first cycle) or may be much smaller (resulting in a large// error response).if memstats.gc_trigger <= heapminimum {memstats.heap_marked = uint64(float64(memstats.gc_trigger) / (1 + memstats.triggerRatio))}// 重新計算next_gc, 注意next_gc的計算跟gc_trigger不一樣// Re-compute the heap goal for this cycle in case something// changed. This is the same calculation we use elsewhere.memstats.next_gc = memstats.heap_marked + memstats.heap_marked*uint64(gcpercent)/100if gcpercent < 0 {memstats.next_gc = ^uint64(0)}// 確保next_gc和heap_live之間最少有1MB// Ensure that the heap goal is at least a little larger than// the current live heap size. This may not be the case if GC// start is delayed or if the allocation that pushed heap_live// over gc_trigger is large or if the trigger is really close to// GOGC. Assist is proportional to this distance, so enforce a// minimum distance, even if it means going over the GOGC goal// by a tiny bit.if memstats.next_gc < memstats.heap_live+1024*1024 {memstats.next_gc = memstats.heap_live + 1024*1024}// 計算可以同時執行的后臺標記任務的數量// dedicatedMarkWorkersNeeded等于P的數量的25%去除小數點// 如果可以整除則fractionalMarkWorkersNeeded等于0否則等于1// totalUtilizationGoal是GC所占的P的目標值(例如P一共有5個時目標是1.25個P)// fractionalUtilizationGoal是Fractiona模式的任務所占的P的目標值(例如P一共有5個時目標是0.25個P)// Compute the total mark utilization goal and divide it among// dedicated and fractional workers.totalUtilizationGoal := float64(gomaxprocs) * gcGoalUtilizationc.dedicatedMarkWorkersNeeded = int64(totalUtilizationGoal)c.fractionalUtilizationGoal = totalUtilizationGoal - float64(c.dedicatedMarkWorkersNeeded)if c.fractionalUtilizationGoal > 0 {c.fractionalMarkWorkersNeeded = 1} else {c.fractionalMarkWorkersNeeded = 0}// 重置P中的輔助GC所用的時間統計// Clear per-P statefor _, p := range &allp {if p == nil {break}p.gcAssistTime = 0}// 計算輔助GC的參數// 參考上面對計算assistWorkPerByte的公式的分析// Compute initial values for controls that are updated// throughout the cycle.c.revise()if debug.gcpacertrace > 0 {print("pacer: assist ratio=", c.assistWorkPerByte," (scan ", memstats.heap_scan>>20, " MB in ",work.initialHeapLive>>20, "->",memstats.next_gc>>20, " MB)"," workers=", c.dedicatedMarkWorkersNeeded,"+", c.fractionalMarkWorkersNeeded, "\n")} }

    setGCPhase函數會修改表示當前GC階段的全局變量和是否開啟寫屏障的全局變量:

    //go:nosplit func setGCPhase(x uint32) {atomic.Store(&gcphase, x)writeBarrier.needed = gcphase == _GCmark || gcphase == _GCmarkterminationwriteBarrier.enabled = writeBarrier.needed || writeBarrier.cgo }

    gcBgMarkPrepare函數會重置后臺標記任務的計數:

    // gcBgMarkPrepare sets up state for background marking. // Mutator assists must not yet be enabled. func gcBgMarkPrepare() {// Background marking will stop when the work queues are empty// and there are no more workers (note that, since this is// concurrent, this may be a transient state, but mark// termination will clean it up). Between background workers// and assists, we don't really know how many workers there// will be, so we pretend to have an arbitrarily large number// of workers, almost all of which are "waiting". While a// worker is working it decrements nwait. If nproc == nwait,// there are no workers.work.nproc = ^uint32(0)work.nwait = ^uint32(0) }

    gcMarkRootPrepare函數會計算掃描根對象的任務數量:

    // gcMarkRootPrepare queues root scanning jobs (stacks, globals, and // some miscellany) and initializes scanning-related state. // // The caller must have call gcCopySpans(). // // The world must be stopped. // //go:nowritebarrier func gcMarkRootPrepare() {// 釋放mcache中的所有span的任務, 只在完成標記階段(mark termination)中執行if gcphase == _GCmarktermination {work.nFlushCacheRoots = int(gomaxprocs)} else {work.nFlushCacheRoots = 0}// 計算block數量的函數, rootBlockBytes是256KB// Compute how many data and BSS root blocks there are.nBlocks := func(bytes uintptr) int {return int((bytes + rootBlockBytes - 1) / rootBlockBytes)}work.nDataRoots = 0work.nBSSRoots = 0// data和bss每一輪GC只掃描一次// 并行GC中會在后臺標記任務中掃描, 完成標記階段(mark termination)中不掃描// 非并行GC會在完成標記階段(mark termination)中掃描// Only scan globals once per cycle; preferably concurrently.if !work.markrootDone {// 計算掃描可讀寫的全局變量的任務數量for _, datap := range activeModules() {nDataRoots := nBlocks(datap.edata - datap.data)if nDataRoots > work.nDataRoots {work.nDataRoots = nDataRoots}}// 計算掃描只讀的全局變量的任務數量for _, datap := range activeModules() {nBSSRoots := nBlocks(datap.ebss - datap.bss)if nBSSRoots > work.nBSSRoots {work.nBSSRoots = nBSSRoots}}}// span中的finalizer和各個G的棧每一輪GC只掃描一次// 同上if !work.markrootDone {// 計算掃描span中的finalizer的任務數量// On the first markroot, we need to scan span roots.// In concurrent GC, this happens during concurrent// mark and we depend on addfinalizer to ensure the// above invariants for objects that get finalizers// after concurrent mark. In STW GC, this will happen// during mark termination.//// We're only interested in scanning the in-use spans,// which will all be swept at this point. More spans// may be added to this list during concurrent GC, but// we only care about spans that were allocated before// this mark phase.work.nSpanRoots = mheap_.sweepSpans[mheap_.sweepgen/2%2].numBlocks()// 計算掃描各個G的棧的任務數量// On the first markroot, we need to scan all Gs. Gs// may be created after this point, but it's okay that// we ignore them because they begin life without any// roots, so there's nothing to scan, and any roots// they create during the concurrent phase will be// scanned during mark termination. During mark// termination, allglen isn't changing, so we'll scan// all Gs.work.nStackRoots = int(atomic.Loaduintptr(&allglen))} else {// We've already scanned span roots and kept the scan// up-to-date during concurrent mark.work.nSpanRoots = 0// The hybrid barrier ensures that stacks can't// contain pointers to unmarked objects, so on the// second markroot, there's no need to scan stacks.work.nStackRoots = 0if debug.gcrescanstacks > 0 {// Scan stacks anyway for debugging.work.nStackRoots = int(atomic.Loaduintptr(&allglen))}}// 計算總任務數量// 后臺標記任務會對markrootNext進行原子遞增, 來決定做哪個任務// 這種用數值來實現鎖自由隊列的辦法挺聰明的, 盡管google工程師覺得不好(看后面markroot函數的分析)work.markrootNext = 0work.markrootJobs = uint32(fixedRootCount + work.nFlushCacheRoots + work.nDataRoots + work.nBSSRoots + work.nSpanRoots + work.nStackRoots) }

    gcMarkTinyAllocs函數會標記所有tiny alloc等待合并的對象:

    // gcMarkTinyAllocs greys all active tiny alloc blocks. // // The world must be stopped. func gcMarkTinyAllocs() {for _, p := range &allp {if p == nil || p.status == _Pdead {break}c := p.mcacheif c == nil || c.tiny == 0 {continue}// 標記各個P中的mcache中的tiny// 在上面的mallocgc函數中可以看到tiny是當前等待合并的對象_, hbits, span, objIndex := heapBitsForObject(c.tiny, 0, 0)gcw := &p.gcw// 標記一個對象存活, 并把它加到標記隊列(該對象變為灰色)greyobject(c.tiny, 0, 0, hbits, span, gcw, objIndex)// gcBlackenPromptly變量表示當前是否禁止本地隊列, 如果已禁止則把標記任務flush到全局隊列if gcBlackenPromptly {gcw.dispose()}} }

    startTheWorldWithSema函數會重新啟動世界:

    func startTheWorldWithSema() {_g_ := getg()// 禁止G被搶占_g_.m.locks++ // disable preemption because it can be holding p in a local var// 判斷收到的網絡事件(fd可讀可寫或錯誤)并添加對應的G到待運行隊列gp := netpoll(false) // non-blockinginjectglist(gp)// 判斷是否要啟動gc helperadd := needaddgcproc()lock(&sched.lock)// 如果要求改變gomaxprocs則調整P的數量// procresize會返回有可運行任務的P的鏈表procs := gomaxprocsif newprocs != 0 {procs = newprocsnewprocs = 0}p1 := procresize(procs)// 取消GC等待標記sched.gcwaiting = 0// 如果sysmon在等待則喚醒它if sched.sysmonwait != 0 {sched.sysmonwait = 0notewakeup(&sched.sysmonnote)}unlock(&sched.lock)// 喚醒有可運行任務的Pfor p1 != nil {p := p1p1 = p1.link.ptr()if p.m != 0 {mp := p.m.ptr()p.m = 0if mp.nextp != 0 {throw("startTheWorld: inconsistent mp->nextp")}mp.nextp.set(p)notewakeup(&mp.park)} else {// Start M to run P. Do not start another M below.newm(nil, p)add = false}}// 如果有空閑的P,并且沒有自旋中的M則喚醒或者創建一個M// Wakeup an additional proc in case we have excessive runnable goroutines// in local queues or in the global queue. If we don't, the proc will park itself.// If we have lots of excessive work, resetspinning will unpark additional procs as necessary.if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 {wakep()}// 啟動gc helperif add {// If GC could have used another helper proc, start one now,// in the hope that it will be available next time.// It would have been even better to start it before the collection,// but doing so requires allocating memory, so it's tricky to// coordinate. This lazy approach works out in practice:// we don't mind if the first couple gc rounds don't have quite// the maximum number of procs.newm(mhelpgc, nil)}// 允許G被搶占_g_.m.locks--// 如果當前G要求被搶占則重新嘗試if _g_.m.locks == 0 && _g_.preempt { // restore the preemption request in case we've cleared it in newstack_g_.stackguard0 = stackPreempt} }

    重啟世界后各個M會重新開始調度, 調度時會優先使用上面提到的findRunnableGCWorker函數查找任務, 之后就有大約25%的P運行后臺標記任務.
    后臺標記任務的函數是gcBgMarkWorker:

    func gcBgMarkWorker(_p_ *p) {gp := getg()// 用于休眠后重新獲取P的構造體type parkInfo struct {m muintptr // Release this m on park.attach puintptr // If non-nil, attach to this p on park.}// We pass park to a gopark unlock function, so it can't be on// the stack (see gopark). Prevent deadlock from recursively// starting GC by disabling preemption.gp.m.preemptoff = "GC worker init"park := new(parkInfo)gp.m.preemptoff = ""// 設置當前的M并禁止搶占park.m.set(acquirem())// 設置當前的P(需要關聯到的P)park.attach.set(_p_)// 通知gcBgMarkStartWorkers可以繼續處理// Inform gcBgMarkStartWorkers that this worker is ready.// After this point, the background mark worker is scheduled// cooperatively by gcController.findRunnable. Hence, it must// never be preempted, as this would put it into _Grunnable// and put it on a run queue. Instead, when the preempt flag// is set, this puts itself into _Gwaiting to be woken up by// gcController.findRunnable at the appropriate time.notewakeup(&work.bgMarkReady)for {// 讓當前G進入休眠// Go to sleep until woken by gcController.findRunnable.// We can't releasem yet since even the call to gopark// may be preempted.gopark(func(g *g, parkp unsafe.Pointer) bool {park := (*parkInfo)(parkp)// 重新允許搶占// The worker G is no longer running, so it's// now safe to allow preemption.releasem(park.m.ptr())// 設置關聯的P// 把當前的G設到P的gcBgMarkWorker成員, 下次findRunnableGCWorker會使用// 設置失敗時不休眠// If the worker isn't attached to its P,// attach now. During initialization and after// a phase change, the worker may have been// running on a different P. As soon as we// attach, the owner P may schedule the// worker, so this must be done after the G is// stopped.if park.attach != 0 {p := park.attach.ptr()park.attach.set(nil)// cas the worker because we may be// racing with a new worker starting// on this P.if !p.gcBgMarkWorker.cas(0, guintptr(unsafe.Pointer(g))) {// The P got a new worker.// Exit this worker.return false}}return true}, unsafe.Pointer(park), "GC worker (idle)", traceEvGoBlock, 0)// 檢查P的gcBgMarkWorker是否和當前的G一致, 不一致時結束當前的任務// Loop until the P dies and disassociates this// worker (the P may later be reused, in which case// it will get a new worker) or we failed to associate.if _p_.gcBgMarkWorker.ptr() != gp {break}// 禁止G被搶占// Disable preemption so we can use the gcw. If the// scheduler wants to preempt us, we'll stop draining,// dispose the gcw, and then preempt.park.m.set(acquirem())if gcBlackenEnabled == 0 {throw("gcBgMarkWorker: blackening not enabled")}// 記錄開始時間startTime := nanotime()decnwait := atomic.Xadd(&work.nwait, -1)if decnwait == work.nproc {println("runtime: work.nwait=", decnwait, "work.nproc=", work.nproc)throw("work.nwait was > work.nproc")}// 切換到g0運行systemstack(func() {// 設置G的狀態為等待中這樣它的棧可以被掃描(兩個后臺標記任務可以互相掃描對方的棧)// Mark our goroutine preemptible so its stack// can be scanned. This lets two mark workers// scan each other (otherwise, they would// deadlock). We must not modify anything on// the G stack. However, stack shrinking is// disabled for mark workers, so it is safe to// read from the G stack.casgstatus(gp, _Grunning, _Gwaiting)// 判斷后臺標記任務的模式switch _p_.gcMarkWorkerMode {default:throw("gcBgMarkWorker: unexpected gcMarkWorkerMode")case gcMarkWorkerDedicatedMode:// 這個模式下P應該專心執行標記// 執行標記, 直到被搶占, 并且需要計算后臺的掃描量來減少輔助GC和喚醒等待中的GgcDrain(&_p_.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit)// 被搶占時把本地運行隊列中的所有G都踢到全局運行隊列if gp.preempt {// We were preempted. This is// a useful signal to kick// everything out of the run// queue so it can run// somewhere else.lock(&sched.lock)for {gp, _ := runqget(_p_)if gp == nil {break}globrunqput(gp)}unlock(&sched.lock)}// 繼續執行標記, 直到無更多任務, 并且需要計算后臺的掃描量來減少輔助GC和喚醒等待中的G// Go back to draining, this time// without preemption.gcDrain(&_p_.gcw, gcDrainNoBlock|gcDrainFlushBgCredit)case gcMarkWorkerFractionalMode:// 這個模式下P應該適當執行標記// 執行標記, 直到被搶占, 并且需要計算后臺的掃描量來減少輔助GC和喚醒等待中的GgcDrain(&_p_.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit)case gcMarkWorkerIdleMode:// 這個模式下P只在空閑時執行標記// 執行標記, 直到被搶占或者達到一定的量, 并且需要計算后臺的掃描量來減少輔助GC和喚醒等待中的GgcDrain(&_p_.gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit)}// 恢復G的狀態到運行中casgstatus(gp, _Gwaiting, _Grunning)})// 如果標記了禁止本地標記隊列則flush到全局標記隊列// If we are nearing the end of mark, dispose// of the cache promptly. We must do this// before signaling that we're no longer// working so that other workers can't observe// no workers and no work while we have this// cached, and before we compute done.if gcBlackenPromptly {_p_.gcw.dispose()}// 累加所用時間// Account for time.duration := nanotime() - startTimeswitch _p_.gcMarkWorkerMode {case gcMarkWorkerDedicatedMode:atomic.Xaddint64(&gcController.dedicatedMarkTime, duration)atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, 1)case gcMarkWorkerFractionalMode:atomic.Xaddint64(&gcController.fractionalMarkTime, duration)atomic.Xaddint64(&gcController.fractionalMarkWorkersNeeded, 1)case gcMarkWorkerIdleMode:atomic.Xaddint64(&gcController.idleMarkTime, duration)}// Was this the last worker and did we run out// of work?incnwait := atomic.Xadd(&work.nwait, +1)if incnwait > work.nproc {println("runtime: p.gcMarkWorkerMode=", _p_.gcMarkWorkerMode,"work.nwait=", incnwait, "work.nproc=", work.nproc)throw("work.nwait > work.nproc")}// 判斷是否所有后臺標記任務都完成, 并且沒有更多的任務// If this worker reached a background mark completion// point, signal the main GC goroutine.if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {// 取消和P的關聯// Make this G preemptible and disassociate it// as the worker for this P so// findRunnableGCWorker doesn't try to// schedule it._p_.gcBgMarkWorker.set(nil)// 允許G被搶占releasem(park.m.ptr())// 準備進入完成標記階段gcMarkDone()// 休眠之前會重新關聯P// 因為上面允許被搶占, 到這里的時候可能就會變成其他P// 如果重新關聯P失敗則這個任務會結束// Disable preemption and prepare to reattach// to the P.//// We may be running on a different P at this// point, so we can't reattach until this G is// parked.park.m.set(acquirem())park.attach.set(_p_)}} }

    gcDrain函數用于執行標記:

    // gcDrain scans roots and objects in work buffers, blackening grey // objects until all roots and work buffers have been drained. // // If flags&gcDrainUntilPreempt != 0, gcDrain returns when g.preempt // is set. This implies gcDrainNoBlock. // // If flags&gcDrainIdle != 0, gcDrain returns when there is other work // to do. This implies gcDrainNoBlock. // // If flags&gcDrainNoBlock != 0, gcDrain returns as soon as it is // unable to get more work. Otherwise, it will block until all // blocking calls are blocked in gcDrain. // // If flags&gcDrainFlushBgCredit != 0, gcDrain flushes scan work // credit to gcController.bgScanCredit every gcCreditSlack units of // scan work. // //go:nowritebarrier func gcDrain(gcw *gcWork, flags gcDrainFlags) {if !writeBarrier.needed {throw("gcDrain phase incorrect")}gp := getg().m.curg// 看到搶占標志時是否要返回preemptible := flags&gcDrainUntilPreempt != 0// 沒有任務時是否要等待任務blocking := flags&(gcDrainUntilPreempt|gcDrainIdle|gcDrainNoBlock) == 0// 是否計算后臺的掃描量來減少輔助GC和喚醒等待中的GflushBgCredit := flags&gcDrainFlushBgCredit != 0// 是否只執行一定量的工作idle := flags&gcDrainIdle != 0// 記錄初始的已掃描數量initScanWork := gcw.scanWork// 掃描idleCheckThreshold(100000)個對象以后檢查是否要返回// idleCheck is the scan work at which to perform the next// idle check with the scheduler.idleCheck := initScanWork + idleCheckThreshold// 如果根對象未掃描完, 則先掃描根對象// Drain root marking jobs.if work.markrootNext < work.markrootJobs {// 如果標記了preemptible, 循環直到被搶占for !(preemptible && gp.preempt) {// 從根對象掃描隊列取出一個值(原子遞增)job := atomic.Xadd(&work.markrootNext, +1) - 1if job >= work.markrootJobs {break}// 執行根對象掃描工作markroot(gcw, job)// 如果是idle模式并且有其他工作, 則返回if idle && pollWork() {goto done}}}// 根對象已經在標記隊列中, 消費標記隊列// 如果標記了preemptible, 循環直到被搶占// Drain heap marking jobs.for !(preemptible && gp.preempt) {// 如果全局標記隊列為空, 把本地標記隊列的一部分工作分過去// (如果wbuf2不為空則移動wbuf2過去, 否則移動wbuf1的一半過去)// Try to keep work available on the global queue. We used to// check if there were waiting workers, but it's better to// just keep work available than to make workers wait. In the// worst case, we'll do O(log(_WorkbufSize)) unnecessary// balances.if work.full == 0 {gcw.balance()}// 從本地標記隊列中獲取對象, 獲取不到則從全局標記隊列獲取var b uintptrif blocking {// 阻塞獲取b = gcw.get()} else {// 非阻塞獲取b = gcw.tryGetFast()if b == 0 {b = gcw.tryGet()}}// 獲取不到對象, 標記隊列已為空, 跳出循環if b == 0 {// work barrier reached or tryGet failed.break}// 掃描獲取到的對象scanobject(b, gcw)// 如果已經掃描了一定數量的對象(gcCreditSlack的值是2000)// Flush background scan work credit to the global// account if we've accumulated enough locally so// mutator assists can draw on it.if gcw.scanWork >= gcCreditSlack {// 把掃描的對象數量添加到全局atomic.Xaddint64(&gcController.scanWork, gcw.scanWork)// 減少輔助GC的工作量和喚醒等待中的Gif flushBgCredit {gcFlushBgCredit(gcw.scanWork - initScanWork)initScanWork = 0}idleCheck -= gcw.scanWorkgcw.scanWork = 0// 如果是idle模式且達到了檢查的掃描量, 則檢查是否有其他任務(G), 如果有則跳出循環if idle && idleCheck <= 0 {idleCheck += idleCheckThresholdif pollWork() {break}}}}// In blocking mode, write barriers are not allowed after this// point because we must preserve the condition that the work// buffers are empty.done:// 把掃描的對象數量添加到全局// Flush remaining scan work credit.if gcw.scanWork > 0 {atomic.Xaddint64(&gcController.scanWork, gcw.scanWork)// 減少輔助GC的工作量和喚醒等待中的Gif flushBgCredit {gcFlushBgCredit(gcw.scanWork - initScanWork)}gcw.scanWork = 0} }

    markroot函數用于執行根對象掃描工作:

    // markroot scans the i'th root. // // Preemption must be disabled (because this uses a gcWork). // // nowritebarrier is only advisory here. // //go:nowritebarrier func markroot(gcw *gcWork, i uint32) {// 判斷取出的數值對應哪種任務// (google的工程師覺得這種辦法可笑)// TODO(austin): This is a bit ridiculous. Compute and store// the bases in gcMarkRootPrepare instead of the counts.baseFlushCache := uint32(fixedRootCount)baseData := baseFlushCache + uint32(work.nFlushCacheRoots)baseBSS := baseData + uint32(work.nDataRoots)baseSpans := baseBSS + uint32(work.nBSSRoots)baseStacks := baseSpans + uint32(work.nSpanRoots)end := baseStacks + uint32(work.nStackRoots)// Note: if you add a case here, please also update heapdump.go:dumproots.switch {// 釋放mcache中的所有span, 要求STWcase baseFlushCache <= i && i < baseData:flushmcache(int(i - baseFlushCache))// 掃描可讀寫的全局變量// 這里只會掃描i對應的block, 掃描時傳入包含哪里有指針的bitmap數據case baseData <= i && i < baseBSS:for _, datap := range activeModules() {markrootBlock(datap.data, datap.edata-datap.data, datap.gcdatamask.bytedata, gcw, int(i-baseData))}// 掃描只讀的全局變量// 這里只會掃描i對應的block, 掃描時傳入包含哪里有指針的bitmap數據case baseBSS <= i && i < baseSpans:for _, datap := range activeModules() {markrootBlock(datap.bss, datap.ebss-datap.bss, datap.gcbssmask.bytedata, gcw, int(i-baseBSS))}// 掃描析構器隊列case i == fixedRootFinalizers:// Only do this once per GC cycle since we don't call// queuefinalizer during marking.if work.markrootDone {break}for fb := allfin; fb != nil; fb = fb.alllink {cnt := uintptr(atomic.Load(&fb.cnt))scanblock(uintptr(unsafe.Pointer(&fb.fin[0])), cnt*unsafe.Sizeof(fb.fin[0]), &finptrmask[0], gcw)}// 釋放已中止的G的棧case i == fixedRootFreeGStacks:// Only do this once per GC cycle; preferably// concurrently.if !work.markrootDone {// Switch to the system stack so we can call// stackfree.systemstack(markrootFreeGStacks)}// 掃描各個span中特殊對象(析構器列表)case baseSpans <= i && i < baseStacks:// mark MSpan.specialsmarkrootSpans(gcw, int(i-baseSpans))// 掃描各個G的棧default:// 獲取需要掃描的G// the rest is scanning goroutine stacksvar gp *gif baseStacks <= i && i < end {gp = allgs[i-baseStacks]} else {throw("markroot: bad index")}// 記錄等待開始的時間// remember when we've first observed the G blocked// needed only to output in tracebackstatus := readgstatus(gp) // We are not in a scan stateif (status == _Gwaiting || status == _Gsyscall) && gp.waitsince == 0 {gp.waitsince = work.tstart}// 切換到g0運行(有可能會掃到自己的棧)// scang must be done on the system stack in case// we're trying to scan our own stack.systemstack(func() {// 判斷掃描的棧是否自己的// If this is a self-scan, put the user G in// _Gwaiting to prevent self-deadlock. It may// already be in _Gwaiting if this is a mark// worker or we're in mark termination.userG := getg().m.curgselfScan := gp == userG && readgstatus(userG) == _Grunning// 如果正在掃描自己的棧則切換狀態到等待中防止死鎖if selfScan {casgstatus(userG, _Grunning, _Gwaiting)userG.waitreason = "garbage collection scan"}// 掃描G的棧// TODO: scang blocks until gp's stack has// been scanned, which may take a while for// running goroutines. Consider doing this in// two phases where the first is non-blocking:// we scan the stacks we can and ask running// goroutines to scan themselves; and the// second blocks.scang(gp, gcw)// 如果正在掃描自己的棧則把狀態切換回運行中if selfScan {casgstatus(userG, _Gwaiting, _Grunning)}})} }

    scang函數負責掃描G的棧:

    // scang blocks until gp's stack has been scanned. // It might be scanned by scang or it might be scanned by the goroutine itself. // Either way, the stack scan has completed when scang returns. func scang(gp *g, gcw *gcWork) {// Invariant; we (the caller, markroot for a specific goroutine) own gp.gcscandone.// Nothing is racing with us now, but gcscandone might be set to true left over// from an earlier round of stack scanning (we scan twice per GC).// We use gcscandone to record whether the scan has been done during this round.// 標記掃描未完成gp.gcscandone = false// See http://golang.org/cl/21503 for justification of the yield delay.const yieldDelay = 10 * 1000var nextYield int64// 循環直到掃描完成// Endeavor to get gcscandone set to true,// either by doing the stack scan ourselves or by coercing gp to scan itself.// gp.gcscandone can transition from false to true when we're not looking// (if we asked for preemption), so any time we lock the status using// castogscanstatus we have to double-check that the scan is still not done. loop:for i := 0; !gp.gcscandone; i++ {// 判斷G的當前狀態switch s := readgstatus(gp); s {default:dumpgstatus(gp)throw("stopg: invalid status")// G已中止, 不需要掃描它case _Gdead:// No stack.gp.gcscandone = truebreak loop// G的棧正在擴展, 下一輪重試case _Gcopystack:// Stack being switched. Go around again.// G不是運行中, 首先需要防止它運行case _Grunnable, _Gsyscall, _Gwaiting:// Claim goroutine by setting scan bit.// Racing with execution or readying of gp.// The scan bit keeps them from running// the goroutine until we're done.if castogscanstatus(gp, s, s|_Gscan) {// 原子切換狀態成功時掃描它的棧if !gp.gcscandone {scanstack(gp, gcw)gp.gcscandone = true}// 恢復G的狀態, 并跳出循環restartg(gp)break loop}// G正在掃描它自己, 等待掃描完畢case _Gscanwaiting:// newstack is doing a scan for us right now. Wait.// G正在運行case _Grunning:// Goroutine running. Try to preempt execution so it can scan itself.// The preemption handler (in newstack) does the actual scan.// 如果已經有搶占請求, 則搶占成功時會幫我們處理// Optimization: if there is already a pending preemption request// (from the previous loop iteration), don't bother with the atomics.if gp.preemptscan && gp.preempt && gp.stackguard0 == stackPreempt {break}// 搶占G, 搶占成功時G會掃描它自己// Ask for preemption and self scan.if castogscanstatus(gp, _Grunning, _Gscanrunning) {if !gp.gcscandone {gp.preemptscan = truegp.preempt = truegp.stackguard0 = stackPreempt}casfrom_Gscanstatus(gp, _Gscanrunning, _Grunning)}}// 第一輪休眠10毫秒, 第二輪休眠5毫秒if i == 0 {nextYield = nanotime() + yieldDelay}if nanotime() < nextYield {procyield(10)} else {osyield()nextYield = nanotime() + yieldDelay/2}}// 掃描完成, 取消搶占掃描的請求gp.preemptscan = false // cancel scan request if no longer needed }

    設置preemptscan后, 在搶占G成功時會調用scanstack掃描它自己的棧, 具體代碼在這里.
    掃描棧用的函數是scanstack:

    // scanstack scans gp's stack, greying all pointers found on the stack. // // scanstack is marked go:systemstack because it must not be preempted // while using a workbuf. // //go:nowritebarrier //go:systemstack func scanstack(gp *g, gcw *gcWork) {if gp.gcscanvalid {return}if readgstatus(gp)&_Gscan == 0 {print("runtime:scanstack: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", hex(readgstatus(gp)), "\n")throw("scanstack - bad status")}switch readgstatus(gp) &^ _Gscan {default:print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")throw("mark - bad status")case _Gdead:returncase _Grunning:print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")throw("scanstack: goroutine not stopped")case _Grunnable, _Gsyscall, _Gwaiting:// ok}if gp == getg() {throw("can't scan our own stack")}mp := gp.mif mp != nil && mp.helpgc != 0 {throw("can't scan gchelper stack")}// Shrink the stack if not much of it is being used. During// concurrent GC, we can do this during concurrent mark.if !work.markrootDone {shrinkstack(gp)}// Scan the stack.var cache pcvalueCachescanframe := func(frame *stkframe, unused unsafe.Pointer) bool {// scanframeworker會根據代碼地址(pc)獲取函數信息// 然后找到函數信息中的stackmap.bytedata, 它保存了函數的棧上哪些地方有指針// 再調用scanblock來掃描函數的棧空間, 同時函數的參數也會這樣掃描scanframeworker(frame, &cache, gcw)return true}// 枚舉所有調用幀, 分別調用scanframe函數gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, scanframe, nil, 0)// 枚舉所有defer的調用幀, 分別調用scanframe函數tracebackdefers(gp, scanframe, nil)gp.gcscanvalid = true }

    scanblock函數是一個通用的掃描函數, 掃描全局變量和棧空間都會用它, 和scanobject不同的是bitmap需要手動傳入:

    // scanblock scans b as scanobject would, but using an explicit // pointer bitmap instead of the heap bitmap. // // This is used to scan non-heap roots, so it does not update // gcw.bytesMarked or gcw.scanWork. // //go:nowritebarrier func scanblock(b0, n0 uintptr, ptrmask *uint8, gcw *gcWork) {// Use local copies of original parameters, so that a stack trace// due to one of the throws below shows the original block// base and extent.b := b0n := n0arena_start := mheap_.arena_startarena_used := mheap_.arena_used// 枚舉掃描的地址for i := uintptr(0); i < n; {// 找到bitmap中對應的byte// Find bits for the next word.bits := uint32(*addb(ptrmask, i/(sys.PtrSize*8)))if bits == 0 {i += sys.PtrSize * 8continue}// 枚舉bytefor j := 0; j < 8 && i < n; j++ {// 如果該地址包含指針if bits&1 != 0 {// 標記在該地址的對象存活, 并把它加到標記隊列(該對象變為灰色)// Same work as in scanobject; see comments there.obj := *(*uintptr)(unsafe.Pointer(b + i))if obj != 0 && arena_start <= obj && obj < arena_used {// 找到該對象對應的span和bitmapif obj, hbits, span, objIndex := heapBitsForObject(obj, b, i); obj != 0 {// 標記一個對象存活, 并把它加到標記隊列(該對象變為灰色)greyobject(obj, b, i, hbits, span, gcw, objIndex)}}}// 處理下一個指針下一個bitbits >>= 1i += sys.PtrSize}} }

    greyobject用于標記一個對象存活, 并把它加到標記隊列(該對象變為灰色):

    // obj is the start of an object with mark mbits. // If it isn't already marked, mark it and enqueue into gcw. // base and off are for debugging only and could be removed. //go:nowritebarrierrec func greyobject(obj, base, off uintptr, hbits heapBits, span *mspan, gcw *gcWork, objIndex uintptr) {// obj should be start of allocation, and so must be at least pointer-aligned.if obj&(sys.PtrSize-1) != 0 {throw("greyobject: obj not pointer-aligned")}mbits := span.markBitsForIndex(objIndex)if useCheckmark {// checkmark是用于檢查是否所有可到達的對象都被正確標記的機制, 僅除錯使用if !mbits.isMarked() {printlock()print("runtime:greyobject: checkmarks finds unexpected unmarked object obj=", hex(obj), "\n")print("runtime: found obj at *(", hex(base), "+", hex(off), ")\n")// Dump the source (base) objectgcDumpObject("base", base, off)// Dump the objectgcDumpObject("obj", obj, ^uintptr(0))getg().m.traceback = 2throw("checkmark found unmarked object")}if hbits.isCheckmarked(span.elemsize) {return}hbits.setCheckmarked(span.elemsize)if !hbits.isCheckmarked(span.elemsize) {throw("setCheckmarked and isCheckmarked disagree")}} else {if debug.gccheckmark > 0 && span.isFree(objIndex) {print("runtime: marking free object ", hex(obj), " found at *(", hex(base), "+", hex(off), ")\n")gcDumpObject("base", base, off)gcDumpObject("obj", obj, ^uintptr(0))getg().m.traceback = 2throw("marking free object")}// 如果對象所在的span中的gcmarkBits對應的bit已經設置為1則可以跳過處理// If marked we have nothing to do.if mbits.isMarked() {return}// 設置對象所在的span中的gcmarkBits對應的bit為1// mbits.setMarked() // Avoid extra call overhead with manual inlining.atomic.Or8(mbits.bytep, mbits.mask)// 如果確定對象不包含指針(所在span的類型是noscan), 則不需要把對象放入標記隊列// If this is a noscan object, fast-track it to black// instead of greying it.if span.spanclass.noscan() {gcw.bytesMarked += uint64(span.elemsize)return}}// 把對象放入標記隊列// 先放入本地標記隊列, 失敗時把本地標記隊列中的部分工作轉移到全局標記隊列, 再放入本地標記隊列// Queue the obj for scanning. The PREFETCH(obj) logic has been removed but// seems like a nice optimization that can be added back in.// There needs to be time between the PREFETCH and the use.// Previously we put the obj in an 8 element buffer that is drained at a rate// to give the PREFETCH time to do its work.// Use of PREFETCHNTA might be more appropriate than PREFETCHif !gcw.putFast(obj) {gcw.put(obj)} }

    gcDrain函數掃描完根對象, 就會開始消費標記隊列, 對從標記隊列中取出的對象調用scanobject函數:

    // scanobject scans the object starting at b, adding pointers to gcw. // b must point to the beginning of a heap object or an oblet. // scanobject consults the GC bitmap for the pointer mask and the // spans for the size of the object. // //go:nowritebarrier func scanobject(b uintptr, gcw *gcWork) {// Note that arena_used may change concurrently during// scanobject and hence scanobject may encounter a pointer to// a newly allocated heap object that is *not* in// [start,used). It will not mark this object; however, we// know that it was just installed by a mutator, which means// that mutator will execute a write barrier and take care of// marking it. This is even more pronounced on relaxed memory// architectures since we access arena_used without barriers// or synchronization, but the same logic applies.arena_start := mheap_.arena_startarena_used := mheap_.arena_used// Find the bits for b and the size of the object at b.//// b is either the beginning of an object, in which case this// is the size of the object to scan, or it points to an// oblet, in which case we compute the size to scan below.// 獲取對象對應的bitmaphbits := heapBitsForAddr(b)// 獲取對象所在的spans := spanOfUnchecked(b)// 獲取對象的大小n := s.elemsizeif n == 0 {throw("scanobject n == 0")}// 對象大小過大時(maxObletBytes是128KB)需要分割掃描// 每次最多只掃描128KBif n > maxObletBytes {// Large object. Break into oblets for better// parallelism and lower latency.if b == s.base() {// It's possible this is a noscan object (not// from greyobject, but from other code// paths), in which case we must *not* enqueue// oblets since their bitmaps will be// uninitialized.if s.spanclass.noscan() {// Bypass the whole scan.gcw.bytesMarked += uint64(n)return}// Enqueue the other oblets to scan later.// Some oblets may be in b's scalar tail, but// these will be marked as "no more pointers",// so we'll drop out immediately when we go to// scan those.for oblet := b + maxObletBytes; oblet < s.base()+s.elemsize; oblet += maxObletBytes {if !gcw.putFast(oblet) {gcw.put(oblet)}}}// Compute the size of the oblet. Since this object// must be a large object, s.base() is the beginning// of the object.n = s.base() + s.elemsize - bif n > maxObletBytes {n = maxObletBytes}}// 掃描對象中的指針var i uintptrfor i = 0; i < n; i += sys.PtrSize {// 獲取對應的bit// Find bits for this word.if i != 0 {// Avoid needless hbits.next() on last iteration.hbits = hbits.next()}// Load bits once. See CL 22712 and issue 16973 for discussion.bits := hbits.bits()// 檢查scan bit判斷是否繼續掃描, 注意第二個scan bit是checkmark// During checkmarking, 1-word objects store the checkmark// in the type bit for the one word. The only one-word objects// are pointers, or else they'd be merged with other non-pointer// data into larger allocations.if i != 1*sys.PtrSize && bits&bitScan == 0 {break // no more pointers in this object}// 檢查pointer bit, 不是指針則繼續if bits&bitPointer == 0 {continue // not a pointer}// 取出指針的值// Work here is duplicated in scanblock and above.// If you make changes here, make changes there too.obj := *(*uintptr)(unsafe.Pointer(b + i))// 如果指針在arena區域中, 則調用greyobject標記對象并把對象放到標記隊列中// At this point we have extracted the next potential pointer.// Check if it points into heap and not back at the current object.if obj != 0 && arena_start <= obj && obj < arena_used && obj-b >= n {// Mark the object.if obj, hbits, span, objIndex := heapBitsForObject(obj, b, i); obj != 0 {greyobject(obj, b, i, hbits, span, gcw, objIndex)}}}// 統計掃描過的大小和對象數量gcw.bytesMarked += uint64(n)gcw.scanWork += int64(i) }

    在所有后臺標記任務都把標記隊列消費完畢時, 會執行gcMarkDone函數準備進入完成標記階段(mark termination):
    在并行GC中gcMarkDone會被執行兩次, 第一次會禁止本地標記隊列然后重新開始后臺標記任務, 第二次會進入完成標記階段(mark termination)。

    // gcMarkDone transitions the GC from mark 1 to mark 2 and from mark 2 // to mark termination. // // This should be called when all mark work has been drained. In mark // 1, this includes all root marking jobs, global work buffers, and // active work buffers in assists and background workers; however, // work may still be cached in per-P work buffers. In mark 2, per-P // caches are disabled. // // The calling context must be preemptible. // // Note that it is explicitly okay to have write barriers in this // function because completion of concurrent mark is best-effort // anyway. Any work created by write barriers here will be cleaned up // by mark termination. func gcMarkDone() { top:semacquire(&work.markDoneSema)// Re-check transition condition under transition lock.if !(gcphase == _GCmark && work.nwait == work.nproc && !gcMarkWorkAvailable(nil)) {semrelease(&work.markDoneSema)return}// 暫時禁止啟動新的后臺標記任務// Disallow starting new workers so that any remaining workers// in the current mark phase will drain out.//// TODO(austin): Should dedicated workers keep an eye on this// and exit gcDrain promptly?atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, -0xffffffff)atomic.Xaddint64(&gcController.fractionalMarkWorkersNeeded, -0xffffffff)// 判斷本地標記隊列是否已禁用if !gcBlackenPromptly {// 本地標記隊列是否未禁用, 禁用然后重新開始后臺標記任務// Transition from mark 1 to mark 2.//// The global work list is empty, but there can still be work// sitting in the per-P work caches.// Flush and disable work caches.// 禁用本地標記隊列// Disallow caching workbufs and indicate that we're in mark 2.gcBlackenPromptly = true// Prevent completion of mark 2 until we've flushed// cached workbufs.atomic.Xadd(&work.nwait, -1)// GC is set up for mark 2. Let Gs blocked on the// transition lock go while we flush caches.semrelease(&work.markDoneSema)// 把所有本地標記隊列中的對象都推到全局標記隊列systemstack(func() {// Flush all currently cached workbufs and// ensure all Ps see gcBlackenPromptly. This// also blocks until any remaining mark 1// workers have exited their loop so we can// start new mark 2 workers.forEachP(func(_p_ *p) {_p_.gcw.dispose()})})// 除錯用// Check that roots are marked. We should be able to// do this before the forEachP, but based on issue// #16083 there may be a (harmless) race where we can// enter mark 2 while some workers are still scanning// stacks. The forEachP ensures these scans are done.//// TODO(austin): Figure out the race and fix this// properly.gcMarkRootCheck()// 允許啟動新的后臺標記任務// Now we can start up mark 2 workers.atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, 0xffffffff)atomic.Xaddint64(&gcController.fractionalMarkWorkersNeeded, 0xffffffff)// 如果確定沒有更多的任務則可以直接跳到函數頂部// 這樣就當作是第二次調用了incnwait := atomic.Xadd(&work.nwait, +1)if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {// This loop will make progress because// gcBlackenPromptly is now true, so it won't// take this same "if" branch.goto top}} else {// 記錄完成標記階段開始的時間和STW開始的時間// Transition to mark termination.now := nanotime()work.tMarkTerm = nowwork.pauseStart = now// 禁止G被搶占getg().m.preemptoff = "gcing"// 停止所有運行中的G, 并禁止它們運行systemstack(stopTheWorldWithSema)// !!!!!!!!!!!!!!!!// 世界已停止(STW)...// !!!!!!!!!!!!!!!!// The gcphase is _GCmark, it will transition to _GCmarktermination// below. The important thing is that the wb remains active until// all marking is complete. This includes writes made by the GC.// 標記對根對象的掃描已完成, 會影響gcMarkRootPrepare中的處理// Record that one root marking pass has completed.work.markrootDone = true// 禁止輔助GC和后臺標記任務的運行// Disable assists and background workers. We must do// this before waking blocked assists.atomic.Store(&gcBlackenEnabled, 0)// 喚醒所有因為輔助GC而休眠的G// Wake all blocked assists. These will run when we// start the world again.gcWakeAllAssists()// Likewise, release the transition lock. Blocked// workers and assists will run when we start the// world again.semrelease(&work.markDoneSema)// 計算下一次觸發gc需要的heap大小// endCycle depends on all gcWork cache stats being// flushed. This is ensured by mark 2.nextTriggerRatio := gcController.endCycle()// 進入完成標記階段, 會重新啟動世界// Perform mark termination. This will restart the world.gcMarkTermination(nextTriggerRatio)} }

    gcMarkTermination函數會進入完成標記階段:

    func gcMarkTermination(nextTriggerRatio float64) {// World is stopped.// Start marktermination which includes enabling the write barrier.// 禁止輔助GC和后臺標記任務的運行atomic.Store(&gcBlackenEnabled, 0)// 重新允許本地標記隊列(下次GC使用)gcBlackenPromptly = false// 設置當前GC階段到完成標記階段, 并啟用寫屏障setGCPhase(_GCmarktermination)// 記錄開始時間work.heap1 = memstats.heap_livestartTime := nanotime()// 禁止G被搶占mp := acquirem()mp.preemptoff = "gcing"_g_ := getg()_g_.m.traceback = 2// 設置G的狀態為等待中這樣它的棧可以被掃描gp := _g_.m.curgcasgstatus(gp, _Grunning, _Gwaiting)gp.waitreason = "garbage collection"// 切換到g0運行// Run gc on the g0 stack. We do this so that the g stack// we're currently running on will no longer change. Cuts// the root set down a bit (g0 stacks are not scanned, and// we don't need to scan gc's internal state). We also// need to switch to g0 so we can shrink the stack.systemstack(func() {// 開始STW中的標記gcMark(startTime)// 必須立刻返回, 因為外面的G的棧有可能被移動, 不能在這之后訪問外面的變量// Must return immediately.// The outer function's stack may have moved// during gcMark (it shrinks stacks, including the// outer function's stack), so we must not refer// to any of its variables. Return back to the// non-system stack to pick up the new addresses// before continuing.})// 重新切換到g0運行systemstack(func() {work.heap2 = work.bytesMarked// 如果啟用了checkmark則執行檢查, 檢查是否所有可到達的對象都有標記if debug.gccheckmark > 0 {// Run a full stop-the-world mark using checkmark bits,// to check that we didn't forget to mark anything during// the concurrent mark process.gcResetMarkState()initCheckmarks()gcMark(startTime)clearCheckmarks()}// 設置當前GC階段到關閉, 并禁用寫屏障// marking is complete so we can turn the write barrier offsetGCPhase(_GCoff)// 喚醒后臺清掃任務, 將在STW結束后開始運行gcSweep(work.mode)// 除錯用if debug.gctrace > 1 {startTime = nanotime()// The g stacks have been scanned so// they have gcscanvalid==true and gcworkdone==true.// Reset these so that all stacks will be rescanned.gcResetMarkState()finishsweep_m()// Still in STW but gcphase is _GCoff, reset to _GCmarktermination// At this point all objects will be found during the gcMark which// does a complete STW mark and object scan.setGCPhase(_GCmarktermination)gcMark(startTime)setGCPhase(_GCoff) // marking is done, turn off wb.gcSweep(work.mode)}})// 設置G的狀態為運行中_g_.m.traceback = 0casgstatus(gp, _Gwaiting, _Grunning)// 跟蹤處理if trace.enabled {traceGCDone()}// all donemp.preemptoff = ""if gcphase != _GCoff {throw("gc done but gcphase != _GCoff")}// 更新下一次觸發gc需要的heap大小(gc_trigger)// Update GC trigger and pacing for the next cycle.gcSetTriggerRatio(nextTriggerRatio)// 更新用時記錄// Update timing memstatsnow := nanotime()sec, nsec, _ := time_now()unixNow := sec*1e9 + int64(nsec)work.pauseNS += now - work.pauseStartwork.tEnd = nowatomic.Store64(&memstats.last_gc_unix, uint64(unixNow)) // must be Unix time to make sense to useratomic.Store64(&memstats.last_gc_nanotime, uint64(now)) // monotonic time for usmemstats.pause_ns[memstats.numgc%uint32(len(memstats.pause_ns))] = uint64(work.pauseNS)memstats.pause_end[memstats.numgc%uint32(len(memstats.pause_end))] = uint64(unixNow)memstats.pause_total_ns += uint64(work.pauseNS)// 更新所用cpu記錄// Update work.totaltime.sweepTermCpu := int64(work.stwprocs) * (work.tMark - work.tSweepTerm)// We report idle marking time below, but omit it from the// overall utilization here since it's "free".markCpu := gcController.assistTime + gcController.dedicatedMarkTime + gcController.fractionalMarkTimemarkTermCpu := int64(work.stwprocs) * (work.tEnd - work.tMarkTerm)cycleCpu := sweepTermCpu + markCpu + markTermCpuwork.totaltime += cycleCpu// Compute overall GC CPU utilization.totalCpu := sched.totaltime + (now-sched.procresizetime)*int64(gomaxprocs)memstats.gc_cpu_fraction = float64(work.totaltime) / float64(totalCpu)// 重置清掃狀態// Reset sweep state.sweep.nbgsweep = 0sweep.npausesweep = 0// 統計強制開始GC的次數if work.userForced {memstats.numforcedgc++}// 統計執行GC的次數然后喚醒等待清掃的G// Bump GC cycle count and wake goroutines waiting on sweep.lock(&work.sweepWaiters.lock)memstats.numgc++injectglist(work.sweepWaiters.head.ptr())work.sweepWaiters.head = 0unlock(&work.sweepWaiters.lock)// 性能統計用// Finish the current heap profiling cycle and start a new// heap profiling cycle. We do this before starting the world// so events don't leak into the wrong cycle.mProf_NextCycle()// 重新啟動世界systemstack(startTheWorldWithSema)// !!!!!!!!!!!!!!!// 世界已重新啟動...// !!!!!!!!!!!!!!!// 性能統計用// Flush the heap profile so we can start a new cycle next GC.// This is relatively expensive, so we don't do it with the// world stopped.mProf_Flush()// 移動標記隊列使用的緩沖區到自由列表, 使得它們可以被回收// Prepare workbufs for freeing by the sweeper. We do this// asynchronously because it can take non-trivial time.prepareFreeWorkbufs()// 釋放未使用的棧// Free stack spans. This must be done between GC cycles.systemstack(freeStackSpans)// 除錯用// Print gctrace before dropping worldsema. As soon as we drop// worldsema another cycle could start and smash the stats// we're trying to print.if debug.gctrace > 0 {util := int(memstats.gc_cpu_fraction * 100)var sbuf [24]byteprintlock()print("gc ", memstats.numgc," @", string(itoaDiv(sbuf[:], uint64(work.tSweepTerm-runtimeInitTime)/1e6, 3)), "s ",util, "%: ")prev := work.tSweepTermfor i, ns := range []int64{work.tMark, work.tMarkTerm, work.tEnd} {if i != 0 {print("+")}print(string(fmtNSAsMS(sbuf[:], uint64(ns-prev))))prev = ns}print(" ms clock, ")for i, ns := range []int64{sweepTermCpu, gcController.assistTime, gcController.dedicatedMarkTime + gcController.fractionalMarkTime, gcController.idleMarkTime, markTermCpu} {if i == 2 || i == 3 {// Separate mark time components with /.print("/")} else if i != 0 {print("+")}print(string(fmtNSAsMS(sbuf[:], uint64(ns))))}print(" ms cpu, ",work.heap0>>20, "->", work.heap1>>20, "->", work.heap2>>20, " MB, ",work.heapGoal>>20, " MB goal, ",work.maxprocs, " P")if work.userForced {print(" (forced)")}print("\n")printunlock()}semrelease(&worldsema)// Careful: another GC cycle may start now.// 重新允許當前的G被搶占releasem(mp)mp = nil// 如果是并行GC, 讓當前M繼續運行(會回到gcBgMarkWorker然后休眠)// 如果不是并行GC, 則讓當前M開始調度// now that gc is done, kick off finalizer thread if neededif !concurrentSweep {// give the queued finalizers, if any, a chance to runGosched()} }

    gcSweep函數會喚醒后臺清掃任務:

    后臺清掃任務會在程序啟動時調用的gcenable函數中啟動.

    func gcSweep(mode gcMode) {if gcphase != _GCoff {throw("gcSweep being done but phase is not GCoff")}// 增加sweepgen, 這樣sweepSpans中兩個隊列角色會交換, 所有span都會變為"待清掃"的spanlock(&mheap_.lock)mheap_.sweepgen += 2mheap_.sweepdone = 0if mheap_.sweepSpans[mheap_.sweepgen/2%2].index != 0 {// We should have drained this list during the last// sweep phase. We certainly need to start this phase// with an empty swept list.throw("non-empty swept list")}mheap_.pagesSwept = 0unlock(&mheap_.lock)// 如果非并行GC則在這里完成所有工作(STW中)if !_ConcurrentSweep || mode == gcForceBlockMode {// Special case synchronous sweep.// Record that no proportional sweeping has to happen.lock(&mheap_.lock)mheap_.sweepPagesPerByte = 0unlock(&mheap_.lock)// Sweep all spans eagerly.for sweepone() != ^uintptr(0) {sweep.npausesweep++}// Free workbufs eagerly.prepareFreeWorkbufs()for freeSomeWbufs(false) {}// All "free" events for this mark/sweep cycle have// now happened, so we can make this profile cycle// available immediately.mProf_NextCycle()mProf_Flush()return}// 喚醒后臺清掃任務// Background sweep.lock(&sweep.lock)if sweep.parked {sweep.parked = falseready(sweep.g, 0, true)}unlock(&sweep.lock) }

    后臺清掃任務的函數是bgsweep:

    func bgsweep(c chan int) {sweep.g = getg()// 等待喚醒lock(&sweep.lock)sweep.parked = truec <- 1goparkunlock(&sweep.lock, "GC sweep wait", traceEvGoBlock, 1)// 循環清掃for {// 清掃一個span, 然后進入調度(一次只做少量工作)for gosweepone() != ^uintptr(0) {sweep.nbgsweep++Gosched()}// 釋放一些未使用的標記隊列緩沖區到heapfor freeSomeWbufs(true) {Gosched()}// 如果清掃未完成則繼續循環lock(&sweep.lock)if !gosweepdone() {// This can happen if a GC runs between// gosweepone returning ^0 above// and the lock being acquired.unlock(&sweep.lock)continue}// 否則讓后臺清掃任務進入休眠, 當前M繼續調度sweep.parked = truegoparkunlock(&sweep.lock, "GC sweep wait", traceEvGoBlock, 1)} }

    gosweepone函數會從sweepSpans中取出單個span清掃:

    //go:nowritebarrier func gosweepone() uintptr {var ret uintptr// 切換到g0運行systemstack(func() {ret = sweepone()})return ret }

    sweepone函數如下:

    // sweeps one span // returns number of pages returned to heap, or ^uintptr(0) if there is nothing to sweep //go:nowritebarrier func sweepone() uintptr {_g_ := getg()sweepRatio := mheap_.sweepPagesPerByte // For debugging// 禁止G被搶占// increment locks to ensure that the goroutine is not preempted// in the middle of sweep thus leaving the span in an inconsistent state for next GC_g_.m.locks++// 檢查是否已完成清掃if atomic.Load(&mheap_.sweepdone) != 0 {_g_.m.locks--return ^uintptr(0)}// 更新同時執行sweep的任務數量atomic.Xadd(&mheap_.sweepers, +1)npages := ^uintptr(0)sg := mheap_.sweepgenfor {// 從sweepSpans中取出一個spans := mheap_.sweepSpans[1-sg/2%2].pop()// 全部清掃完畢時跳出循環if s == nil {atomic.Store(&mheap_.sweepdone, 1)break}// 其他M已經在清掃這個span時跳過if s.state != mSpanInUse {// This can happen if direct sweeping already// swept this span, but in that case the sweep// generation should always be up-to-date.if s.sweepgen != sg {print("runtime: bad span s.state=", s.state, " s.sweepgen=", s.sweepgen, " sweepgen=", sg, "\n")throw("non in-use span in unswept list")}continue}// 原子增加span的sweepgen, 失敗表示其他M已經開始清掃這個span, 跳過if s.sweepgen != sg-2 || !atomic.Cas(&s.sweepgen, sg-2, sg-1) {continue}// 清掃這個span, 然后跳出循環npages = s.npagesif !s.sweep(false) {// Span is still in-use, so this returned no// pages to the heap and the span needs to// move to the swept in-use list.npages = 0}break}// 更新同時執行sweep的任務數量// Decrement the number of active sweepers and if this is the// last one print trace information.if atomic.Xadd(&mheap_.sweepers, -1) == 0 && atomic.Load(&mheap_.sweepdone) != 0 {if debug.gcpacertrace > 0 {print("pacer: sweep done at heap size ", memstats.heap_live>>20, "MB; allocated ", (memstats.heap_live-mheap_.sweepHeapLiveBasis)>>20, "MB during sweep; swept ", mheap_.pagesSwept, " pages at ", sweepRatio, " pages/byte\n")}}// 允許G被搶占_g_.m.locks--// 返回清掃的頁數return npages }

    span的sweep函數用于清掃單個span:

    // Sweep frees or collects finalizers for blocks not marked in the mark phase. // It clears the mark bits in preparation for the next GC round. // Returns true if the span was returned to heap. // If preserve=true, don't return it to heap nor relink in MCentral lists; // caller takes care of it. //TODO go:nowritebarrier func (s *mspan) sweep(preserve bool) bool {// It's critical that we enter this function with preemption disabled,// GC must not start while we are in the middle of this function._g_ := getg()if _g_.m.locks == 0 && _g_.m.mallocing == 0 && _g_ != _g_.m.g0 {throw("MSpan_Sweep: m is not locked")}sweepgen := mheap_.sweepgenif s.state != mSpanInUse || s.sweepgen != sweepgen-1 {print("MSpan_Sweep: state=", s.state, " sweepgen=", s.sweepgen, " mheap.sweepgen=", sweepgen, "\n")throw("MSpan_Sweep: bad span state")}if trace.enabled {traceGCSweepSpan(s.npages * _PageSize)}// 統計已清理的頁數atomic.Xadd64(&mheap_.pagesSwept, int64(s.npages))spc := s.spanclasssize := s.elemsizeres := falsec := _g_.m.mcachefreeToHeap := false// The allocBits indicate which unmarked objects don't need to be// processed since they were free at the end of the last GC cycle// and were not allocated since then.// If the allocBits index is >= s.freeindex and the bit// is not marked then the object remains unallocated// since the last GC.// This situation is analogous to being on a freelist.// 判斷在special中的析構器, 如果對應的對象已經不再存活則標記對象存活防止回收, 然后把析構器移到運行隊列// Unlink & free special records for any objects we're about to free.// Two complications here:// 1. An object can have both finalizer and profile special records.// In such case we need to queue finalizer for execution,// mark the object as live and preserve the profile special.// 2. A tiny object can have several finalizers setup for different offsets.// If such object is not marked, we need to queue all finalizers at once.// Both 1 and 2 are possible at the same time.specialp := &s.specialsspecial := *specialpfor special != nil {// A finalizer can be set for an inner byte of an object, find object beginning.objIndex := uintptr(special.offset) / sizep := s.base() + objIndex*sizembits := s.markBitsForIndex(objIndex)if !mbits.isMarked() {// This object is not marked and has at least one special record.// Pass 1: see if it has at least one finalizer.hasFin := falseendOffset := p - s.base() + sizefor tmp := special; tmp != nil && uintptr(tmp.offset) < endOffset; tmp = tmp.next {if tmp.kind == _KindSpecialFinalizer {// Stop freeing of object if it has a finalizer.mbits.setMarkedNonAtomic()hasFin = truebreak}}// Pass 2: queue all finalizers _or_ handle profile record.for special != nil && uintptr(special.offset) < endOffset {// Find the exact byte for which the special was setup// (as opposed to object beginning).p := s.base() + uintptr(special.offset)if special.kind == _KindSpecialFinalizer || !hasFin {// Splice out special record.y := specialspecial = special.next*specialp = specialfreespecial(y, unsafe.Pointer(p), size)} else {// This is profile record, but the object has finalizers (so kept alive).// Keep special record.specialp = &special.nextspecial = *specialp}}} else {// object is still live: keep special recordspecialp = &special.nextspecial = *specialp}}// 除錯用if debug.allocfreetrace != 0 || raceenabled || msanenabled {// Find all newly freed objects. This doesn't have to// efficient; allocfreetrace has massive overhead.mbits := s.markBitsForBase()abits := s.allocBitsForIndex(0)for i := uintptr(0); i < s.nelems; i++ {if !mbits.isMarked() && (abits.index < s.freeindex || abits.isMarked()) {x := s.base() + i*s.elemsizeif debug.allocfreetrace != 0 {tracefree(unsafe.Pointer(x), size)}if raceenabled {racefree(unsafe.Pointer(x), size)}if msanenabled {msanfree(unsafe.Pointer(x), size)}}mbits.advance()abits.advance()}}// 計算釋放的對象數量// Count the number of free objects in this span.nalloc := uint16(s.countAlloc())if spc.sizeclass() == 0 && nalloc == 0 {// 如果span的類型是0(大對象)并且其中的對象已經不存活則釋放到heaps.needzero = 1freeToHeap = true}nfreed := s.allocCount - nallocif nalloc > s.allocCount {print("runtime: nelems=", s.nelems, " nalloc=", nalloc, " previous allocCount=", s.allocCount, " nfreed=", nfreed, "\n")throw("sweep increased allocation count")}// 設置新的allocCounts.allocCount = nalloc// 判斷span是否無未分配的對象wasempty := s.nextFreeIndex() == s.nelems// 重置freeindex, 下次分配從0開始搜索s.freeindex = 0 // reset allocation index to start of span.if trace.enabled {getg().m.p.ptr().traceReclaimed += uintptr(nfreed) * s.elemsize}// gcmarkBits變為新的allocBits// 然后重新分配一塊全部為0的gcmarkBits// 下次分配對象時可以根據allocBits得知哪些元素是未分配的// gcmarkBits becomes the allocBits.// get a fresh cleared gcmarkBits in preparation for next GCs.allocBits = s.gcmarkBitss.gcmarkBits = newMarkBits(s.nelems)// 更新freeindex開始的allocCache// Initialize alloc bits cache.s.refillAllocCache(0)// 如果span中已經無存活的對象則更新sweepgen到最新// 下面會把span加到mcentral或者mheap// We need to set s.sweepgen = h.sweepgen only when all blocks are swept,// because of the potential for a concurrent free/SetFinalizer.// But we need to set it before we make the span available for allocation// (return it to heap or mcentral), because allocation code assumes that a// span is already swept if available for allocation.if freeToHeap || nfreed == 0 {// The span must be in our exclusive ownership until we update sweepgen,// check for potential races.if s.state != mSpanInUse || s.sweepgen != sweepgen-1 {print("MSpan_Sweep: state=", s.state, " sweepgen=", s.sweepgen, " mheap.sweepgen=", sweepgen, "\n")throw("MSpan_Sweep: bad span state after sweep")}// Serialization point.// At this point the mark bits are cleared and allocation ready// to go so release the span.atomic.Store(&s.sweepgen, sweepgen)}if nfreed > 0 && spc.sizeclass() != 0 {// 把span加到mcentral, res等于是否添加成功c.local_nsmallfree[spc.sizeclass()] += uintptr(nfreed)res = mheap_.central[spc].mcentral.freeSpan(s, preserve, wasempty)// freeSpan會更新sweepgen// MCentral_FreeSpan updates sweepgen} else if freeToHeap {// 把span釋放到mheap// Free large span to heap// NOTE(rsc,dvyukov): The original implementation of efence// in CL 22060046 used SysFree instead of SysFault, so that// the operating system would eventually give the memory// back to us again, so that an efence program could run// longer without running out of memory. Unfortunately,// calling SysFree here without any kind of adjustment of the// heap data structures means that when the memory does// come back to us, we have the wrong metadata for it, either in// the MSpan structures or in the garbage collection bitmap.// Using SysFault here means that the program will run out of// memory fairly quickly in efence mode, but at least it won't// have mysterious crashes due to confused memory reuse.// It should be possible to switch back to SysFree if we also// implement and then call some kind of MHeap_DeleteSpan.if debug.efence > 0 {s.limit = 0 // prevent mlookup from finding this spansysFault(unsafe.Pointer(s.base()), size)} else {mheap_.freeSpan(s, 1)}c.local_nlargefree++c.local_largefree += sizeres = true}// 如果span未加到mcentral或者未釋放到mheap, 則表示span仍在使用if !res {// 把仍在使用的span加到sweepSpans的"已清掃"隊列中// The span has been swept and is still in-use, so put// it on the swept in-use list.mheap_.sweepSpans[sweepgen/2%2].push(s)}return res }

    從bgsweep和前面的分配器可以看出掃描階段的工作是十分懶惰(lazy)的,
    實際可能會出現前一階段的掃描還未完成, 就需要開始新一輪的GC的情況,
    所以每一輪GC開始之前都需要完成前一輪GC的掃描工作(Sweep Termination階段).

    GC的整個流程都分析完畢了, 最后貼上寫屏障函數writebarrierptr的實現:

    // NOTE: Really dst *unsafe.Pointer, src unsafe.Pointer, // but if we do that, Go inserts a write barrier on *dst = src. //go:nosplit func writebarrierptr(dst *uintptr, src uintptr) {if writeBarrier.cgo {cgoCheckWriteBarrier(dst, src)}if !writeBarrier.needed {*dst = srcreturn}if src != 0 && src < minPhysPageSize {systemstack(func() {print("runtime: writebarrierptr *", dst, " = ", hex(src), "\n")throw("bad pointer in write barrier")})}// 標記指針writebarrierptr_prewrite1(dst, src)// 設置指針到目標*dst = src }

    writebarrierptr_prewrite1函數如下:

    // writebarrierptr_prewrite1 invokes a write barrier for *dst = src // prior to the write happening. // // Write barrier calls must not happen during critical GC and scheduler // related operations. In particular there are times when the GC assumes // that the world is stopped but scheduler related code is still being // executed, dealing with syscalls, dealing with putting gs on runnable // queues and so forth. This code cannot execute write barriers because // the GC might drop them on the floor. Stopping the world involves removing // the p associated with an m. We use the fact that m.p == nil to indicate // that we are in one these critical section and throw if the write is of // a pointer to a heap object. //go:nosplit func writebarrierptr_prewrite1(dst *uintptr, src uintptr) {mp := acquirem()if mp.inwb || mp.dying > 0 {releasem(mp)return}systemstack(func() {if mp.p == 0 && memstats.enablegc && !mp.inwb && inheap(src) {throw("writebarrierptr_prewrite1 called with mp.p == nil")}mp.inwb = truegcmarkwb_m(dst, src)})mp.inwb = falsereleasem(mp) }

    gcmarkwb_m函數如下:

    func gcmarkwb_m(slot *uintptr, ptr uintptr) {if writeBarrier.needed {// Note: This turns bad pointer writes into bad// pointer reads, which could be confusing. We avoid// reading from obviously bad pointers, which should// take care of the vast majority of these. We could// patch this up in the signal handler, or use XCHG to// combine the read and the write. Checking inheap is// insufficient since we need to track changes to// roots outside the heap.//// Note: profbuf.go omits a barrier during signal handler// profile logging; that's safe only because this deletion barrier exists.// If we remove the deletion barrier, we'll have to work out// a new way to handle the profile logging.if slot1 := uintptr(unsafe.Pointer(slot)); slot1 >= minPhysPageSize {if optr := *slot; optr != 0 {// 標記舊指針shade(optr)}}// TODO: Make this conditional on the caller's stack color.if ptr != 0 && inheap(ptr) {// 標記新指針shade(ptr)}} }

    shade函數如下:

    // Shade the object if it isn't already. // The object is not nil and known to be in the heap. // Preemption must be disabled. //go:nowritebarrier func shade(b uintptr) {if obj, hbits, span, objIndex := heapBitsForObject(b, 0, 0); obj != 0 {gcw := &getg().m.p.ptr().gcw// 標記一個對象存活, 并把它加到標記隊列(該對象變為灰色)greyobject(obj, 0, 0, hbits, span, gcw, objIndex)// 如果標記了禁止本地標記隊列則flush到全局標記隊列if gcphase == _GCmarktermination || gcBlackenPromptly {// Ps aren't allowed to cache work during mark// termination.gcw.dispose()}} }

    ?

    總結

    以上是生活随笔為你收集整理的深入理解GO语言:GC原理及源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。

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

    国产午夜手机精彩视频 | 久久久久免费精品国产 | 国产乱码精品一品二品 | 精品国偷自产在线 | 黑人玩弄人妻中文在线 | 天堂久久天堂av色综合 | www国产亚洲精品久久网站 | www国产亚洲精品久久网站 | 乱人伦人妻中文字幕无码久久网 | 日本一卡二卡不卡视频查询 | 亚洲综合伊人久久大杳蕉 | 国产精品资源一区二区 | 无码精品人妻一区二区三区av | 2020最新国产自产精品 | 日本丰满熟妇videos | 荫蒂被男人添的好舒服爽免费视频 | 蜜桃视频插满18在线观看 | 精品国产乱码久久久久乱码 | 久久精品国产一区二区三区肥胖 | 欧美高清在线精品一区 | 久久精品国产日本波多野结衣 | 色欲av亚洲一区无码少妇 | 欧美zoozzooz性欧美 | 日本精品高清一区二区 | 自拍偷自拍亚洲精品10p | 亚洲欧美国产精品专区久久 | 亚洲熟悉妇女xxx妇女av | 在线天堂新版最新版在线8 | 免费人成网站视频在线观看 | 给我免费的视频在线观看 | 无码福利日韩神码福利片 | 亚洲色大成网站www | 激情爆乳一区二区三区 | 婷婷丁香五月天综合东京热 | 国产精品国产三级国产专播 | 女人被男人爽到呻吟的视频 | 性啪啪chinese东北女人 | 亚洲一区二区三区 | 中文字幕无码人妻少妇免费 | 精品国产av色一区二区深夜久久 | 欧美熟妇另类久久久久久不卡 | 国产亚洲精品久久久久久国模美 | 亚洲日韩一区二区三区 | 波多野结衣乳巨码无在线观看 | 日韩亚洲欧美精品综合 | 熟女俱乐部五十路六十路av | 伊人久久大香线蕉av一区二区 | 欧美一区二区三区 | 国产熟妇另类久久久久 | 特级做a爰片毛片免费69 | 久久综合激激的五月天 | 国产成人亚洲综合无码 | 国产一区二区三区影院 | 我要看www免费看插插视频 | 人人妻人人澡人人爽欧美一区 | 久久精品国产一区二区三区 | 东京热一精品无码av | 精品无码一区二区三区的天堂 | 久久精品人人做人人综合试看 | 亚洲精品国产第一综合99久久 | 97资源共享在线视频 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 国产精品对白交换视频 | 女人被男人躁得好爽免费视频 | 97夜夜澡人人双人人人喊 | 波多野结衣一区二区三区av免费 | 精品国产国产综合精品 | 成熟人妻av无码专区 | аⅴ资源天堂资源库在线 | 亚洲乱码中文字幕在线 | 曰韩少妇内射免费播放 | 伊人久久大香线蕉av一区二区 | 久久亚洲中文字幕精品一区 | 精品午夜福利在线观看 | 国产莉萝无码av在线播放 | 粗大的内捧猛烈进出视频 | 亚洲另类伦春色综合小说 | 日日橹狠狠爱欧美视频 | 国产精品久久久久7777 | 人妻尝试又大又粗久久 | av香港经典三级级 在线 | а天堂中文在线官网 | 无码乱肉视频免费大全合集 | 色五月五月丁香亚洲综合网 | 无码成人精品区在线观看 | aa片在线观看视频在线播放 | 国产在线一区二区三区四区五区 | 激情国产av做激情国产爱 | 少妇太爽了在线观看 | 疯狂三人交性欧美 | 亚洲精品久久久久avwww潮水 | 亚洲一区二区三区在线观看网站 | 国产精品久久久久无码av色戒 | 久久久久亚洲精品中文字幕 | 亚洲综合色区中文字幕 | 夫妻免费无码v看片 | 久久亚洲精品成人无码 | 99久久精品日本一区二区免费 | 国内综合精品午夜久久资源 | 久久 国产 尿 小便 嘘嘘 | 国产成人精品无码播放 | 强伦人妻一区二区三区视频18 | 久久午夜无码鲁丝片秋霞 | 99国产欧美久久久精品 | 日韩精品久久久肉伦网站 | 国产xxx69麻豆国语对白 | 老熟妇乱子伦牲交视频 | 奇米影视7777久久精品人人爽 | 色欲人妻aaaaaaa无码 | 国内精品人妻无码久久久影院蜜桃 | 久久久国产一区二区三区 | 亚洲成av人综合在线观看 | 77777熟女视频在线观看 а天堂中文在线官网 | 久久国内精品自在自线 | 亚洲欧美综合区丁香五月小说 | 精品 日韩 国产 欧美 视频 | av无码电影一区二区三区 | 久久aⅴ免费观看 | 欧美亚洲日韩国产人成在线播放 | 亚洲精品一区二区三区在线观看 | 国产亲子乱弄免费视频 | 国产精品久久久 | 亚洲精品久久久久中文第一幕 | 学生妹亚洲一区二区 | 亚洲性无码av中文字幕 | 久久五月精品中文字幕 | 精品久久8x国产免费观看 | 国产精品办公室沙发 | 兔费看少妇性l交大片免费 | 无码人妻出轨黑人中文字幕 | 国产人妻精品一区二区三区 | 免费中文字幕日韩欧美 | 东京一本一道一二三区 | 午夜不卡av免费 一本久久a久久精品vr综合 | 水蜜桃色314在线观看 | 亚洲 激情 小说 另类 欧美 | 狠狠色欧美亚洲狠狠色www | 日本精品人妻无码77777 天堂一区人妻无码 | 色综合天天综合狠狠爱 | 熟女体下毛毛黑森林 | 99国产精品白浆在线观看免费 | 内射白嫩少妇超碰 | 中文字幕人成乱码熟女app | 日日噜噜噜噜夜夜爽亚洲精品 | 久久久久久av无码免费看大片 | 98国产精品综合一区二区三区 | 高清无码午夜福利视频 | 熟妇人妻中文av无码 | 欧美日韩在线亚洲综合国产人 | 一个人看的www免费视频在线观看 | 2019nv天堂香蕉在线观看 | 亚拍精品一区二区三区探花 | 水蜜桃亚洲一二三四在线 | 一个人看的www免费视频在线观看 | 初尝人妻少妇中文字幕 | 骚片av蜜桃精品一区 | 综合激情五月综合激情五月激情1 | 激情内射亚州一区二区三区爱妻 | 国产亚洲人成a在线v网站 | 无套内射视频囯产 | 亚洲国产欧美日韩精品一区二区三区 | 日日夜夜撸啊撸 | 国产精品久久久久久无码 | 国产日产欧产精品精品app | 国产精品永久免费视频 | 国产乱人伦av在线无码 | 国产av人人夜夜澡人人爽麻豆 | 欧美日本精品一区二区三区 | 青青草原综合久久大伊人精品 | 中文字幕无码乱人伦 | 精品午夜福利在线观看 | 天堂亚洲2017在线观看 | 鲁鲁鲁爽爽爽在线视频观看 | 久久无码人妻影院 | 无遮挡国产高潮视频免费观看 | 麻豆精产国品 | 又大又硬又黄的免费视频 | 东京热男人av天堂 | 99久久精品无码一区二区毛片 | 少妇人妻偷人精品无码视频 | 性史性农村dvd毛片 | 亚洲男人av天堂午夜在 | 蜜桃臀无码内射一区二区三区 | 亚洲精品综合一区二区三区在线 | 99久久精品国产一区二区蜜芽 | 免费观看又污又黄的网站 | 精品成在人线av无码免费看 | 国产精品久久久久久亚洲毛片 | 熟妇激情内射com | 55夜色66夜色国产精品视频 | 久久亚洲日韩精品一区二区三区 | 97色伦图片97综合影院 | 野狼第一精品社区 | 日韩av无码一区二区三区 | 亚洲色大成网站www国产 | 国产成人一区二区三区在线观看 | 国产无遮挡吃胸膜奶免费看 | 天天摸天天碰天天添 | 无码av中文字幕免费放 | 无码午夜成人1000部免费视频 | 亚洲乱码日产精品bd | 内射白嫩少妇超碰 | 蜜桃av抽搐高潮一区二区 | 男人的天堂2018无码 | 六月丁香婷婷色狠狠久久 | 亚洲国产精品久久久天堂 | 国精产品一品二品国精品69xx | 精品人妻人人做人人爽 | 日日摸天天摸爽爽狠狠97 | 精品一区二区不卡无码av | 在线观看国产午夜福利片 | 激情综合激情五月俺也去 | 国产亚洲人成在线播放 | 国产午夜亚洲精品不卡下载 | 午夜无码区在线观看 | 精品国产一区二区三区四区 | 国产超碰人人爽人人做人人添 | 亚洲国产精品无码久久久久高潮 | 色诱久久久久综合网ywww | 永久免费观看国产裸体美女 | 麻豆av传媒蜜桃天美传媒 | 装睡被陌生人摸出水好爽 | 国内精品人妻无码久久久影院蜜桃 | 呦交小u女精品视频 | 蜜桃视频韩日免费播放 | 久久久久se色偷偷亚洲精品av | 熟妇人妻无码xxx视频 | 国内丰满熟女出轨videos | 国产精品-区区久久久狼 | 黑森林福利视频导航 | 国产国产精品人在线视 | 午夜福利电影 | 我要看www免费看插插视频 | 人妻少妇精品久久 | 5858s亚洲色大成网站www | 日本高清一区免费中文视频 | 国产激情综合五月久久 | 四虎永久在线精品免费网址 | 国产成人人人97超碰超爽8 | 狠狠亚洲超碰狼人久久 | 东京无码熟妇人妻av在线网址 | 中文字幕乱码亚洲无线三区 | 国产精品-区区久久久狼 | 成在人线av无码免费 | 国产办公室秘书无码精品99 | 狠狠色噜噜狠狠狠狠7777米奇 | 国产精品久久久一区二区三区 | 亚洲aⅴ无码成人网站国产app | 欧美性生交活xxxxxdddd | 亚洲高清偷拍一区二区三区 | 啦啦啦www在线观看免费视频 | 狂野欧美激情性xxxx | 亚洲精品成a人在线观看 | 一个人看的www免费视频在线观看 | 欧美激情一区二区三区成人 | 无码人妻出轨黑人中文字幕 | 国产综合久久久久鬼色 | 人妻互换免费中文字幕 | 欧美第一黄网免费网站 | 99久久99久久免费精品蜜桃 | √天堂资源地址中文在线 | 精品久久久中文字幕人妻 | 免费人成在线观看网站 | 欧美野外疯狂做受xxxx高潮 | 天天爽夜夜爽夜夜爽 | 网友自拍区视频精品 | 激情综合激情五月俺也去 | 嫩b人妻精品一区二区三区 | 亚洲第一网站男人都懂 | 久久久精品国产sm最大网站 | 久久国语露脸国产精品电影 | 亚洲欧洲日本无在线码 | 一本精品99久久精品77 | 亚洲国产精品久久人人爱 | 亚洲男女内射在线播放 | 国产又爽又黄又刺激的视频 | 少妇性俱乐部纵欲狂欢电影 | 人人妻人人澡人人爽欧美一区 | 亚洲日本一区二区三区在线 | 国产黑色丝袜在线播放 | 国产激情艳情在线看视频 | 丝袜美腿亚洲一区二区 | 色综合久久久无码网中文 | 少妇人妻偷人精品无码视频 | 国产suv精品一区二区五 | 欧美放荡的少妇 | 无码人妻精品一区二区三区不卡 | 伊人久久大香线蕉av一区二区 | 人人超人人超碰超国产 | 亚洲日本一区二区三区在线 | 亚洲国产av美女网站 | 综合激情五月综合激情五月激情1 | 久久无码人妻影院 | 亚洲熟妇色xxxxx亚洲 | 亚洲精品欧美二区三区中文字幕 | 亚洲精品国产精品乱码视色 | aⅴ在线视频男人的天堂 | 日日碰狠狠丁香久燥 | 亚洲aⅴ无码成人网站国产app | 亚洲精品国产精品乱码视色 | 亚洲日韩一区二区 | 亚洲精品午夜无码电影网 | 久青草影院在线观看国产 | 双乳奶水饱满少妇呻吟 | 国产麻豆精品精东影业av网站 | 青青草原综合久久大伊人精品 | 亚洲熟熟妇xxxx | 综合网日日天干夜夜久久 | 在线精品国产一区二区三区 | 蜜桃无码一区二区三区 | 国产人妖乱国产精品人妖 | 亚洲精品一区二区三区在线 | 熟妇人妻无乱码中文字幕 | 国内揄拍国内精品少妇国语 | 婷婷六月久久综合丁香 | 18禁黄网站男男禁片免费观看 | 欧美日韩精品 | 久久久久久久人妻无码中文字幕爆 | 装睡被陌生人摸出水好爽 | 老熟妇乱子伦牲交视频 | 色综合视频一区二区三区 | 亚洲色欲色欲欲www在线 | 久久精品视频在线看15 | 人人爽人人澡人人高潮 | 日本精品少妇一区二区三区 | 国产精品久久久久久久影院 | 三级4级全黄60分钟 | 国产精品毛多多水多 | 久久zyz资源站无码中文动漫 | 精品aⅴ一区二区三区 | 最近免费中文字幕中文高清百度 | 俺去俺来也在线www色官网 | 国产又粗又硬又大爽黄老大爷视 | 国产人妻精品一区二区三区不卡 | 97精品人妻一区二区三区香蕉 | 国产热a欧美热a在线视频 | 无码人妻少妇伦在线电影 | 免费观看黄网站 | 成 人影片 免费观看 | 日日鲁鲁鲁夜夜爽爽狠狠 | 久久99精品国产.久久久久 | 欧美 日韩 人妻 高清 中文 | 风流少妇按摩来高潮 | 少女韩国电视剧在线观看完整 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 丰满岳乱妇在线观看中字无码 | 国产热a欧美热a在线视频 | 精品无人区无码乱码毛片国产 | 日韩精品无码一本二本三本色 | 亚洲一区二区三区播放 | 亚洲精品久久久久中文第一幕 | 国产免费观看黄av片 | 国产无遮挡又黄又爽又色 | 色综合久久中文娱乐网 | 国产成人一区二区三区在线观看 | 午夜福利试看120秒体验区 | 中文字幕乱码中文乱码51精品 | 人妻天天爽夜夜爽一区二区 | 欧美日韩精品 | 东京热无码av男人的天堂 | 无套内谢的新婚少妇国语播放 | 无码播放一区二区三区 | 丰满人妻被黑人猛烈进入 | 日本熟妇人妻xxxxx人hd | 天天燥日日燥 | 国产精品久久久久无码av色戒 | 天堂а√在线地址中文在线 | 免费国产成人高清在线观看网站 | 中文字幕乱码人妻无码久久 | 亚洲成av人片在线观看无码不卡 | 中文亚洲成a人片在线观看 | 国产三级精品三级男人的天堂 | 四十如虎的丰满熟妇啪啪 | 一二三四在线观看免费视频 | 日本一区二区更新不卡 | 欧美国产亚洲日韩在线二区 | 亚洲日韩av一区二区三区中文 | 18禁止看的免费污网站 | 又湿又紧又大又爽a视频国产 | 精品久久久久香蕉网 | 国模大胆一区二区三区 | 免费国产黄网站在线观看 | 国产区女主播在线观看 | 人妻少妇精品无码专区动漫 | 国产极品美女高潮无套在线观看 | 久久久久成人片免费观看蜜芽 | 久久zyz资源站无码中文动漫 | 亚欧洲精品在线视频免费观看 | 精品久久久久久人妻无码中文字幕 | 人妻天天爽夜夜爽一区二区 | 高潮喷水的毛片 | 蜜桃视频插满18在线观看 | 亚洲中文字幕成人无码 | 国产精品人人爽人人做我的可爱 | 377p欧洲日本亚洲大胆 | 成人精品视频一区二区 | 免费人成在线视频无码 | 精品偷拍一区二区三区在线看 | 亚洲欧美国产精品久久 | 欧美xxxx黑人又粗又长 | 成在人线av无码免观看麻豆 | 狠狠色噜噜狠狠狠狠7777米奇 | 欧美放荡的少妇 | 国产av无码专区亚洲awww | 国产人妻精品一区二区三区不卡 | 久久久久免费精品国产 | 日本大乳高潮视频在线观看 | 少妇一晚三次一区二区三区 | 国产黄在线观看免费观看不卡 | yw尤物av无码国产在线观看 | 四虎国产精品一区二区 | 中文字幕av伊人av无码av | 丁香啪啪综合成人亚洲 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 色噜噜亚洲男人的天堂 | 性生交大片免费看l | 欧美人与动性行为视频 | 高潮喷水的毛片 | 秋霞特色aa大片 | 国内揄拍国内精品少妇国语 | aa片在线观看视频在线播放 | 免费乱码人妻系列无码专区 | 一二三四在线观看免费视频 | 久久精品无码一区二区三区 | 性欧美疯狂xxxxbbbb | 中文字幕av无码一区二区三区电影 | 荫蒂添的好舒服视频囗交 | 欧美日韩视频无码一区二区三 | 午夜福利一区二区三区在线观看 | 国产成人无码专区 | 蜜桃无码一区二区三区 | 午夜无码区在线观看 | 国产精品18久久久久久麻辣 | 亚洲国产精品久久久天堂 | 三级4级全黄60分钟 | 亚洲 欧美 激情 小说 另类 | 六十路熟妇乱子伦 | 亚洲精品久久久久avwww潮水 | 亚洲熟妇色xxxxx欧美老妇y | 大乳丰满人妻中文字幕日本 | 麻豆国产97在线 | 欧洲 | 精品国产乱码久久久久乱码 | 18无码粉嫩小泬无套在线观看 | 国产熟女一区二区三区四区五区 | 亚洲а∨天堂久久精品2021 | 无码任你躁久久久久久久 | 久青草影院在线观看国产 | 未满小14洗澡无码视频网站 | 性生交大片免费看女人按摩摩 | 国产精品无码一区二区三区不卡 | 国产舌乚八伦偷品w中 | 波多野结衣av在线观看 | 77777熟女视频在线观看 а天堂中文在线官网 | 国产精品久久久久久亚洲毛片 | 东京一本一道一二三区 | 国产精品99爱免费视频 | 久久久久亚洲精品男人的天堂 | 色婷婷香蕉在线一区二区 | 内射巨臀欧美在线视频 | 国产精品毛多多水多 | 国产免费无码一区二区视频 | 久久精品中文闷骚内射 | 国产绳艺sm调教室论坛 | 国产性生交xxxxx无码 | 亚洲狠狠婷婷综合久久 | 性生交大片免费看女人按摩摩 | 国产精品久久久午夜夜伦鲁鲁 | 日产国产精品亚洲系列 | 成人无码影片精品久久久 | 男女爱爱好爽视频免费看 | 两性色午夜免费视频 | 黑人大群体交免费视频 | 色综合视频一区二区三区 | 国产亚洲人成a在线v网站 | 国产精品a成v人在线播放 | 国产人妻久久精品二区三区老狼 | 97人妻精品一区二区三区 | 精品久久8x国产免费观看 | 国产精品无码成人午夜电影 | 午夜男女很黄的视频 | 伊人久久婷婷五月综合97色 | 亚洲の无码国产の无码影院 | 老子影院午夜精品无码 | 又大又黄又粗又爽的免费视频 | 人人妻人人澡人人爽人人精品浪潮 | 丰满少妇熟乱xxxxx视频 | 日日碰狠狠丁香久燥 | 久久人人爽人人爽人人片ⅴ | 极品尤物被啪到呻吟喷水 | 97精品国产97久久久久久免费 | 欧美自拍另类欧美综合图片区 | 性欧美疯狂xxxxbbbb | 十八禁真人啪啪免费网站 | 亚洲精品一区二区三区在线观看 | 亚洲日韩一区二区三区 | 丝袜人妻一区二区三区 | 扒开双腿疯狂进出爽爽爽视频 | 自拍偷自拍亚洲精品被多人伦好爽 | 国产精品无码mv在线观看 | 欧美熟妇另类久久久久久不卡 | 国内精品久久毛片一区二区 | 亚洲一区av无码专区在线观看 | 无码av岛国片在线播放 | 丰满少妇弄高潮了www | 国产成人精品无码播放 | 午夜理论片yy44880影院 | 亚洲精品成人av在线 | 国产在线精品一区二区三区直播 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 亚洲s色大片在线观看 | 精品国产一区二区三区四区 | 大乳丰满人妻中文字幕日本 | 国产乱人偷精品人妻a片 | 国产成人久久精品流白浆 | √8天堂资源地址中文在线 | 麻豆成人精品国产免费 | 亚洲国产综合无码一区 | 国产人妻精品一区二区三区不卡 | 国产激情综合五月久久 | 少妇人妻偷人精品无码视频 | 国产凸凹视频一区二区 | 国产做国产爱免费视频 | 色综合久久网 | 男人和女人高潮免费网站 | 精品aⅴ一区二区三区 | 天堂久久天堂av色综合 | 国产精品鲁鲁鲁 | 成年美女黄网站色大免费全看 | 成人亚洲精品久久久久软件 | 亚洲码国产精品高潮在线 | 天天爽夜夜爽夜夜爽 | 精品无码一区二区三区爱欲 | 人人爽人人澡人人人妻 | 亚洲成在人网站无码天堂 | 国内精品久久毛片一区二区 | 狂野欧美性猛xxxx乱大交 | 99国产精品白浆在线观看免费 | 国精品人妻无码一区二区三区蜜柚 | 妺妺窝人体色www在线小说 | 国产又爽又黄又刺激的视频 | 国产成人无码专区 | 国产福利视频一区二区 | 色一情一乱一伦 | 国产亚洲视频中文字幕97精品 | 久久精品人人做人人综合 | 日本爽爽爽爽爽爽在线观看免 | 九九久久精品国产免费看小说 | 国产极品美女高潮无套在线观看 | 成人精品天堂一区二区三区 | 日日碰狠狠丁香久燥 | 国产性生交xxxxx无码 | 亚洲国产精品无码一区二区三区 | 成人亚洲精品久久久久软件 | 国产精品香蕉在线观看 | 无码午夜成人1000部免费视频 | 两性色午夜免费视频 | 人妻体内射精一区二区三四 | 欧美三级不卡在线观看 | 国产午夜视频在线观看 | 亚洲精品国产第一综合99久久 | 婷婷五月综合激情中文字幕 | 久久久久久av无码免费看大片 | 亚洲中文字幕在线无码一区二区 | 人人超人人超碰超国产 | 无码帝国www无码专区色综合 | 国产精品爱久久久久久久 | 色综合久久中文娱乐网 | 欧美人与物videos另类 | 特黄特色大片免费播放器图片 | 国产一区二区三区四区五区加勒比 | 国产精品久久久久7777 | 午夜福利一区二区三区在线观看 | 色综合视频一区二区三区 | 成人精品天堂一区二区三区 | 国产色精品久久人妻 | 97久久精品无码一区二区 | 欧美成人午夜精品久久久 | 久久人人爽人人人人片 | 扒开双腿疯狂进出爽爽爽视频 | 亚洲经典千人经典日产 | 蜜臀av在线播放 久久综合激激的五月天 | 无码任你躁久久久久久久 | 300部国产真实乱 | 国产无av码在线观看 | 日本一卡2卡3卡四卡精品网站 | 久久久久久av无码免费看大片 | 人妻少妇被猛烈进入中文字幕 | 久久国产精品_国产精品 | 色综合天天综合狠狠爱 | 粉嫩少妇内射浓精videos | 亚洲爆乳精品无码一区二区三区 | 无码吃奶揉捏奶头高潮视频 | 色一情一乱一伦 | 免费看少妇作爱视频 | 婷婷五月综合激情中文字幕 | 日韩亚洲欧美中文高清在线 | 国产明星裸体无码xxxx视频 | 日韩 欧美 动漫 国产 制服 | 正在播放老肥熟妇露脸 | 免费观看激色视频网站 | 国产精品鲁鲁鲁 | 国产成人精品无码播放 | 又大又硬又爽免费视频 | 又湿又紧又大又爽a视频国产 | 亚洲国产精品久久久天堂 | 亚洲一区二区三区无码久久 | 天下第一社区视频www日本 | 熟女少妇人妻中文字幕 | 国产亚洲精品久久久久久久久动漫 | 99er热精品视频 | 亚洲一区二区三区国产精华液 | аⅴ资源天堂资源库在线 | 亚洲一区av无码专区在线观看 | 国产午夜无码精品免费看 | 任你躁在线精品免费 | 真人与拘做受免费视频一 | 亚洲国产精品一区二区美利坚 | 亚洲综合伊人久久大杳蕉 | 精品国偷自产在线视频 | 无码av最新清无码专区吞精 | 亚洲男人av天堂午夜在 | 亚洲一区二区三区在线观看网站 | 亚洲国产午夜精品理论片 | 国产在线精品一区二区高清不卡 | 中文字幕乱妇无码av在线 | 四虎4hu永久免费 | 欧美日韩在线亚洲综合国产人 | 玩弄中年熟妇正在播放 | 一个人看的www免费视频在线观看 | 精品无码国产一区二区三区av | 欧美 日韩 亚洲 在线 | 精品国偷自产在线视频 | 久久视频在线观看精品 | 亚洲va中文字幕无码久久不卡 | 4hu四虎永久在线观看 | 久久亚洲国产成人精品性色 | 久久久久久亚洲精品a片成人 | 免费人成在线视频无码 | 波多野结衣aⅴ在线 | 欧美zoozzooz性欧美 | 精品国产aⅴ无码一区二区 | 人妻夜夜爽天天爽三区 | 荫蒂被男人添的好舒服爽免费视频 | 欧美熟妇另类久久久久久不卡 | 国产亚av手机在线观看 | 中文字幕无码日韩欧毛 | 无码纯肉视频在线观看 | 99久久人妻精品免费一区 | 国产农村乱对白刺激视频 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 999久久久国产精品消防器材 | 最近的中文字幕在线看视频 | 狂野欧美激情性xxxx | 黑人粗大猛烈进出高潮视频 | 国产办公室秘书无码精品99 | 久久国产精品二国产精品 | 日日麻批免费40分钟无码 | 国产另类ts人妖一区二区 | 内射老妇bbwx0c0ck | 丰满人妻精品国产99aⅴ | 丝袜足控一区二区三区 | 亚洲国产成人a精品不卡在线 | 老熟妇乱子伦牲交视频 | 国产偷抇久久精品a片69 | 久久99精品久久久久久 | 日日鲁鲁鲁夜夜爽爽狠狠 | 在线亚洲高清揄拍自拍一品区 | 久久久www成人免费毛片 | 青青久在线视频免费观看 | 亚洲精品成人av在线 | 亚洲精品www久久久 | 久久午夜无码鲁丝片午夜精品 | 国产免费久久精品国产传媒 | 午夜成人1000部免费视频 | 成人一在线视频日韩国产 | 亚洲日韩中文字幕在线播放 | 日本精品人妻无码免费大全 | 免费无码一区二区三区蜜桃大 | 麻豆av传媒蜜桃天美传媒 | 少妇被粗大的猛进出69影院 | 亚洲热妇无码av在线播放 | 狠狠色丁香久久婷婷综合五月 | 成人免费视频视频在线观看 免费 | 久久99精品国产麻豆蜜芽 | 亚洲精品久久久久久一区二区 | 国产av一区二区三区最新精品 | 熟女少妇在线视频播放 | 国产亚洲视频中文字幕97精品 | 国产真实夫妇视频 | 又大又硬又黄的免费视频 | 亚洲熟熟妇xxxx | 色一情一乱一伦一区二区三欧美 | 1000部啪啪未满十八勿入下载 | 无码国产色欲xxxxx视频 | av无码电影一区二区三区 | 真人与拘做受免费视频 | 亚无码乱人伦一区二区 | 国产精品va在线观看无码 | 色情久久久av熟女人妻网站 | 免费国产成人高清在线观看网站 | 久久久婷婷五月亚洲97号色 | 婷婷五月综合激情中文字幕 | 免费乱码人妻系列无码专区 | 天天做天天爱天天爽综合网 | 久久婷婷五月综合色国产香蕉 | 国产成人精品优优av | 鲁鲁鲁爽爽爽在线视频观看 | 免费看男女做好爽好硬视频 | 欧美性黑人极品hd | 亚洲日韩乱码中文无码蜜桃臀网站 | 99国产欧美久久久精品 | 亚洲日韩av一区二区三区中文 | 精品成在人线av无码免费看 | 亚洲欧美国产精品专区久久 | 欧美刺激性大交 | 免费看少妇作爱视频 | 国产精品成人av在线观看 | 精品人妻av区 | 国产精品无码久久av | 夜夜躁日日躁狠狠久久av | 帮老师解开蕾丝奶罩吸乳网站 | 日韩精品无码一本二本三本色 | 福利一区二区三区视频在线观看 | 欧美黑人性暴力猛交喷水 | 天堂亚洲2017在线观看 | 成人aaa片一区国产精品 | a片在线免费观看 | 天下第一社区视频www日本 | 亚洲码国产精品高潮在线 | 色五月五月丁香亚洲综合网 | 麻豆国产丝袜白领秘书在线观看 | 色一情一乱一伦一区二区三欧美 | 亚洲综合无码一区二区三区 | 成人试看120秒体验区 | 日韩av无码一区二区三区 | 无码人妻出轨黑人中文字幕 | 国内丰满熟女出轨videos | 国产成人精品优优av | 久久久av男人的天堂 | 爱做久久久久久 | 国产精品永久免费视频 | 亚洲区欧美区综合区自拍区 | 中文字幕色婷婷在线视频 | 成人无码视频在线观看网站 | 好男人社区资源 | 少妇被粗大的猛进出69影院 | 天天综合网天天综合色 | 国产电影无码午夜在线播放 | 国产精品va在线播放 | av在线亚洲欧洲日产一区二区 | 国产成人综合色在线观看网站 | 久热国产vs视频在线观看 | 亚洲国产成人av在线观看 | 国产精品人人妻人人爽 | 国产成人无码av片在线观看不卡 | 真人与拘做受免费视频一 | 亚洲国产一区二区三区在线观看 | 最近的中文字幕在线看视频 | 亚洲第一网站男人都懂 | 亚洲精品久久久久久久久久久 | а√天堂www在线天堂小说 | 国产精品无码mv在线观看 | 天堂а√在线地址中文在线 | 日韩成人一区二区三区在线观看 | 精品一区二区三区无码免费视频 | 波多野结衣av一区二区全免费观看 | 性开放的女人aaa片 | 久久人人97超碰a片精品 | 国产亚洲欧美日韩亚洲中文色 | 真人与拘做受免费视频 | 久久久久成人片免费观看蜜芽 | 精品国精品国产自在久国产87 | 国产一区二区三区影院 | 骚片av蜜桃精品一区 | 丝袜 中出 制服 人妻 美腿 | 久久精品中文字幕大胸 | 天天做天天爱天天爽综合网 | 亚洲国产精品成人久久蜜臀 | 午夜免费福利小电影 | 亚洲精品综合一区二区三区在线 | 亚洲精品国产精品乱码不卡 | www一区二区www免费 | 2019nv天堂香蕉在线观看 | 亚洲欧美日韩国产精品一区二区 | 波多野结衣aⅴ在线 | 日本一区二区三区免费高清 | 亲嘴扒胸摸屁股激烈网站 | 天天做天天爱天天爽综合网 | 国精品人妻无码一区二区三区蜜柚 | 亚洲精品综合一区二区三区在线 | 亚洲人成影院在线无码按摩店 | 亚洲精品鲁一鲁一区二区三区 | 久久久成人毛片无码 | 日本一区二区三区免费高清 | 动漫av一区二区在线观看 | 国产亚洲欧美在线专区 | 久久人人97超碰a片精品 | 欧美日本免费一区二区三区 | 亚洲欧洲日本综合aⅴ在线 | 午夜性刺激在线视频免费 | 国模大胆一区二区三区 | 九九综合va免费看 | 免费人成网站视频在线观看 | 精品无码国产一区二区三区av | 无码福利日韩神码福利片 | 日本肉体xxxx裸交 | 兔费看少妇性l交大片免费 | 最近免费中文字幕中文高清百度 | 亚洲精品国产第一综合99久久 | 激情内射亚州一区二区三区爱妻 | 日本www一道久久久免费榴莲 | 国产精品理论片在线观看 | 亚洲国产精品无码一区二区三区 | 久久综合狠狠综合久久综合88 | 啦啦啦www在线观看免费视频 | 日本在线高清不卡免费播放 | 欧美日韩一区二区免费视频 | 免费乱码人妻系列无码专区 | 久久视频在线观看精品 | 欧洲熟妇精品视频 | 麻豆av传媒蜜桃天美传媒 | 99久久精品午夜一区二区 | 捆绑白丝粉色jk震动捧喷白浆 | 少妇高潮喷潮久久久影院 | 国产精品无码永久免费888 | 久久天天躁狠狠躁夜夜免费观看 | 国产精品美女久久久网av | 久久久精品456亚洲影院 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 高潮毛片无遮挡高清免费视频 | 国产精品久久久久9999小说 | www国产精品内射老师 | 欧美 日韩 人妻 高清 中文 | 久久久久成人精品免费播放动漫 | 亚洲综合精品香蕉久久网 | 国产特级毛片aaaaaa高潮流水 | 无人区乱码一区二区三区 | 一个人看的视频www在线 | 久久久久99精品国产片 | √天堂资源地址中文在线 | 三上悠亚人妻中文字幕在线 | 国产猛烈高潮尖叫视频免费 | 国产成人无码av片在线观看不卡 | 免费无码av一区二区 | 在线播放免费人成毛片乱码 | 日本一卡二卡不卡视频查询 | 日本xxxx色视频在线观看免费 | 亚洲精品美女久久久久久久 | 中文字幕无码av激情不卡 | 精品久久久无码中文字幕 | 天干天干啦夜天干天2017 | 精品成在人线av无码免费看 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 亚洲中文字幕无码一久久区 | 老头边吃奶边弄进去呻吟 | 亚洲欧美国产精品久久 | 国产精品久久久久久久9999 | 亚洲天堂2017无码 | 男女下面进入的视频免费午夜 | 免费看少妇作爱视频 | 国产精品99久久精品爆乳 | 国产高清不卡无码视频 | 荫蒂被男人添的好舒服爽免费视频 | 国产亚洲精品久久久久久久 | 欧美三级不卡在线观看 | 久久亚洲a片com人成 | 免费男性肉肉影院 | 欧美xxxxx精品 | 亚洲精品成人福利网站 | 十八禁真人啪啪免费网站 | 日产精品99久久久久久 | 国产免费久久久久久无码 | 国产97色在线 | 免 | 免费无码的av片在线观看 | 偷窥日本少妇撒尿chinese | 亚洲一区二区三区国产精华液 | a国产一区二区免费入口 | 欧美第一黄网免费网站 | 99久久人妻精品免费一区 | av无码电影一区二区三区 | 无码帝国www无码专区色综合 | 亚洲爆乳大丰满无码专区 | 黑人巨大精品欧美一区二区 | a在线亚洲男人的天堂 | 精品国产青草久久久久福利 | 欧美第一黄网免费网站 | 无码精品人妻一区二区三区av | 日韩欧美群交p片內射中文 | 少妇高潮一区二区三区99 | 久久国产精品萌白酱免费 | 噜噜噜亚洲色成人网站 | ass日本丰满熟妇pics | 蜜臀av无码人妻精品 | 日日噜噜噜噜夜夜爽亚洲精品 | 精品乱子伦一区二区三区 | 亚洲s码欧洲m码国产av | 亚洲国产综合无码一区 | 国产农村妇女高潮大叫 | 天天综合网天天综合色 | 99久久亚洲精品无码毛片 | 精品亚洲韩国一区二区三区 | 欧美野外疯狂做受xxxx高潮 | 亚洲娇小与黑人巨大交 | 成人性做爰aaa片免费看不忠 | 亚洲爆乳无码专区 | 人人澡人人透人人爽 | 亚洲国产精品久久久久久 | 日本一区二区更新不卡 | 国产午夜亚洲精品不卡 | www国产精品内射老师 | 久久午夜无码鲁丝片 | 精品国产aⅴ无码一区二区 | 日本又色又爽又黄的a片18禁 | 色妞www精品免费视频 | 久久精品99久久香蕉国产色戒 | 一本无码人妻在中文字幕免费 | 国产后入清纯学生妹 | 欧美一区二区三区 | 无码一区二区三区在线观看 | 亚洲区欧美区综合区自拍区 | 日韩人妻无码中文字幕视频 | 国产av一区二区三区最新精品 | 日本一区二区三区免费播放 | 亚洲 a v无 码免 费 成 人 a v | 老头边吃奶边弄进去呻吟 | 久久久精品欧美一区二区免费 | 国产97色在线 | 免 | 成人精品天堂一区二区三区 | 99精品国产综合久久久久五月天 | 国产精品无码一区二区桃花视频 | 免费无码一区二区三区蜜桃大 | 亚洲成色www久久网站 | 国产精品办公室沙发 | 无码免费一区二区三区 | 97无码免费人妻超级碰碰夜夜 | 亚洲乱码国产乱码精品精 | 日本在线高清不卡免费播放 | 国产成人精品无码播放 | 亚洲无人区午夜福利码高清完整版 | 国产一区二区不卡老阿姨 | 国产艳妇av在线观看果冻传媒 | 日韩成人一区二区三区在线观看 | a国产一区二区免费入口 | 黑人大群体交免费视频 | 福利一区二区三区视频在线观看 | 色综合久久久久综合一本到桃花网 | 九九综合va免费看 | 天天躁夜夜躁狠狠是什么心态 | 色综合久久久无码网中文 | 成人无码精品一区二区三区 | 国产精品99爱免费视频 | 亚洲日韩一区二区三区 | 丁香花在线影院观看在线播放 | 国产综合在线观看 | 久久久精品国产sm最大网站 | 丝袜足控一区二区三区 | 中文字幕无码日韩欧毛 | 青青久在线视频免费观看 | 红桃av一区二区三区在线无码av | 午夜精品久久久内射近拍高清 | 午夜成人1000部免费视频 | 亚洲春色在线视频 | 大屁股大乳丰满人妻 | 国产网红无码精品视频 | 欧美黑人乱大交 | 精品少妇爆乳无码av无码专区 | 久久精品丝袜高跟鞋 | 日本精品久久久久中文字幕 | 亚洲性无码av中文字幕 | 99久久人妻精品免费二区 | 4hu四虎永久在线观看 | 99久久精品无码一区二区毛片 | 桃花色综合影院 | 无码午夜成人1000部免费视频 | 国产色精品久久人妻 | 亚洲gv猛男gv无码男同 | 亚洲精品国偷拍自产在线观看蜜桃 | 樱花草在线社区www | 精品国产av色一区二区深夜久久 | 奇米影视7777久久精品人人爽 | 国产亚洲tv在线观看 | 无码一区二区三区在线观看 | 国产无遮挡又黄又爽免费视频 | 兔费看少妇性l交大片免费 | 国产精品免费大片 | 国产另类ts人妖一区二区 | av人摸人人人澡人人超碰下载 | 67194成是人免费无码 | 性色欲情网站iwww九文堂 | 无码人妻丰满熟妇区五十路百度 | 国产亚洲视频中文字幕97精品 | 中文字幕乱码亚洲无线三区 | 久在线观看福利视频 | 成熟人妻av无码专区 | 国产一精品一av一免费 | 真人与拘做受免费视频一 | 国产综合久久久久鬼色 | 97se亚洲精品一区 | 亚洲综合精品香蕉久久网 | 国产亚洲日韩欧美另类第八页 | 三级4级全黄60分钟 | 久9re热视频这里只有精品 | 精品欧洲av无码一区二区三区 | 久久亚洲精品中文字幕无男同 | 99国产精品白浆在线观看免费 | 黑森林福利视频导航 | 综合激情五月综合激情五月激情1 | 最新国产乱人伦偷精品免费网站 | 日本又色又爽又黄的a片18禁 | 亚洲欧美国产精品久久 | 少妇被黑人到高潮喷出白浆 | 真人与拘做受免费视频 | 亚洲中文无码av永久不收费 | 在线观看国产一区二区三区 | 又湿又紧又大又爽a视频国产 | 国内精品一区二区三区不卡 | 永久黄网站色视频免费直播 | 亚洲 欧美 激情 小说 另类 | 国产精品资源一区二区 | 国产午夜亚洲精品不卡下载 | 日本xxxx色视频在线观看免费 | 日韩欧美成人免费观看 | 亚洲综合无码一区二区三区 | 成年美女黄网站色大免费全看 | 精品国产成人一区二区三区 | 乱人伦人妻中文字幕无码久久网 | 色婷婷综合激情综在线播放 | 亚洲欧洲中文日韩av乱码 | 国产区女主播在线观看 | 丰满人妻精品国产99aⅴ | 日本大乳高潮视频在线观看 | 久久久国产精品无码免费专区 | 成人试看120秒体验区 | 国产精品自产拍在线观看 | 亚洲经典千人经典日产 | 波多野42部无码喷潮在线 | 国产亚洲视频中文字幕97精品 | 国产人妖乱国产精品人妖 | 国模大胆一区二区三区 | 在线视频网站www色 | 99久久精品国产一区二区蜜芽 | 极品尤物被啪到呻吟喷水 | 国产 浪潮av性色四虎 | 又紧又大又爽精品一区二区 | 亚洲精品一区二区三区四区五区 | 色五月五月丁香亚洲综合网 | 疯狂三人交性欧美 | 国产莉萝无码av在线播放 | 任你躁国产自任一区二区三区 | 中文字幕人妻丝袜二区 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 欧美性生交活xxxxxdddd | 乌克兰少妇性做爰 | 国产欧美亚洲精品a | 女人色极品影院 | 中文毛片无遮挡高清免费 | 少女韩国电视剧在线观看完整 | 无套内射视频囯产 | 少妇性俱乐部纵欲狂欢电影 | 色婷婷综合中文久久一本 | 帮老师解开蕾丝奶罩吸乳网站 | 99riav国产精品视频 | 性欧美videos高清精品 | 婷婷五月综合激情中文字幕 | 爽爽影院免费观看 | 日本免费一区二区三区最新 | 国内少妇偷人精品视频免费 | 国产精品亚洲lv粉色 | 波多野结衣一区二区三区av免费 | 图片区 小说区 区 亚洲五月 | 少妇无套内谢久久久久 | 精品熟女少妇av免费观看 | 亚洲一区av无码专区在线观看 | 久久精品国产大片免费观看 | 少妇高潮一区二区三区99 | 永久免费观看国产裸体美女 | 国产色精品久久人妻 | 曰本女人与公拘交酡免费视频 | 天堂一区人妻无码 | 精品国产青草久久久久福利 | 在线a亚洲视频播放在线观看 | 曰韩少妇内射免费播放 | 婷婷综合久久中文字幕蜜桃三电影 | 国产亚洲人成a在线v网站 | 在线亚洲高清揄拍自拍一品区 | 一二三四社区在线中文视频 | a片免费视频在线观看 | 中文字幕av伊人av无码av | 欧美黑人性暴力猛交喷水 | 久久国产劲爆∧v内射 | 天堂一区人妻无码 | 97久久精品无码一区二区 | 中文字幕 亚洲精品 第1页 | 亚洲性无码av中文字幕 | 久久精品国产一区二区三区肥胖 | 午夜精品久久久久久久 | 亚洲aⅴ无码成人网站国产app | 国产xxx69麻豆国语对白 | 国产电影无码午夜在线播放 | 中文无码精品a∨在线观看不卡 | 欧美xxxx黑人又粗又长 | 无码国产色欲xxxxx视频 | 久在线观看福利视频 | 人妻尝试又大又粗久久 | 色综合久久久无码网中文 | 99久久精品无码一区二区毛片 | 久久精品女人天堂av免费观看 | 精品厕所偷拍各类美女tp嘘嘘 | 2020久久超碰国产精品最新 | 国产精品无码一区二区桃花视频 | 国精产品一品二品国精品69xx | 亚洲无人区午夜福利码高清完整版 | 成人免费视频视频在线观看 免费 | 国产免费久久久久久无码 | 国产午夜福利100集发布 | 中文字幕乱妇无码av在线 | 久久99久久99精品中文字幕 | 国产sm调教视频在线观看 | 人妻与老人中文字幕 | 日本又色又爽又黄的a片18禁 | 精品国产一区av天美传媒 | 亚洲国产午夜精品理论片 | 久久五月精品中文字幕 | 骚片av蜜桃精品一区 | 精品水蜜桃久久久久久久 | 色偷偷人人澡人人爽人人模 | 欧美兽交xxxx×视频 | 国产两女互慰高潮视频在线观看 | 亚洲色成人中文字幕网站 | 强伦人妻一区二区三区视频18 | 久久99精品国产.久久久久 | 狠狠色丁香久久婷婷综合五月 | 十八禁视频网站在线观看 | 国产内射老熟女aaaa | 免费男性肉肉影院 | 久久成人a毛片免费观看网站 | 麻豆精产国品 | 7777奇米四色成人眼影 | 久久 国产 尿 小便 嘘嘘 | www国产亚洲精品久久网站 | 色婷婷香蕉在线一区二区 | 久久综合激激的五月天 | 国产午夜福利100集发布 | 欧洲欧美人成视频在线 | 国产精品无码一区二区三区不卡 | 国产99久久精品一区二区 | 无码国产激情在线观看 | 久久久久久九九精品久 | 亚洲国产午夜精品理论片 | 亚洲欧美日韩综合久久久 | 中文字幕久久久久人妻 | 国产人妖乱国产精品人妖 | 无码av岛国片在线播放 | 色情久久久av熟女人妻网站 | 国产免费无码一区二区视频 | 午夜熟女插插xx免费视频 | 红桃av一区二区三区在线无码av | 亚洲综合无码一区二区三区 | 国产精品亚洲lv粉色 | 国产农村乱对白刺激视频 | 日韩精品无码一本二本三本色 | 亚洲国产av美女网站 | 丰满诱人的人妻3 | 欧美日韩视频无码一区二区三 | 亚洲国产精品久久久久久 | 波多野结衣av一区二区全免费观看 | 欧洲精品码一区二区三区免费看 | 国产av久久久久精东av | 女人被爽到呻吟gif动态图视看 | 蜜桃av抽搐高潮一区二区 | 九一九色国产 | 人人妻人人澡人人爽人人精品浪潮 | 男人扒开女人内裤强吻桶进去 | 天天躁日日躁狠狠躁免费麻豆 | 国产欧美熟妇另类久久久 | 成 人 免费观看网站 | а√天堂www在线天堂小说 | 国产舌乚八伦偷品w中 | 又大又黄又粗又爽的免费视频 | 日本丰满护士爆乳xxxx | 国产亚洲视频中文字幕97精品 | 台湾无码一区二区 | 99久久精品日本一区二区免费 | 久久综合香蕉国产蜜臀av | 无码国产色欲xxxxx视频 | 丰满少妇弄高潮了www | 沈阳熟女露脸对白视频 | 香港三级日本三级妇三级 | 久久99精品久久久久婷婷 | 2019nv天堂香蕉在线观看 | 人妻少妇精品视频专区 | 精品人妻人人做人人爽 | 午夜精品一区二区三区在线观看 | 奇米影视888欧美在线观看 | а天堂中文在线官网 | 亚洲 欧美 激情 小说 另类 | 麻豆国产人妻欲求不满谁演的 | 久久www免费人成人片 | 人妻aⅴ无码一区二区三区 | 国产电影无码午夜在线播放 | 国产人妻精品一区二区三区 | 成人一区二区免费视频 | 亚洲高清偷拍一区二区三区 | 国产亚洲精品久久久久久大师 | 日本www一道久久久免费榴莲 | 无套内谢老熟女 | 大肉大捧一进一出好爽视频 | 色老头在线一区二区三区 | 国产真实伦对白全集 | 精品久久久久久人妻无码中文字幕 | 一二三四社区在线中文视频 | 久久婷婷五月综合色国产香蕉 | 无码人妻出轨黑人中文字幕 | 老子影院午夜伦不卡 | 亚洲乱码日产精品bd | 国产片av国语在线观看 | 呦交小u女精品视频 | 亚洲日韩乱码中文无码蜜桃臀网站 | 亚洲综合在线一区二区三区 | 国产精品美女久久久网av | 国产精品18久久久久久麻辣 | 亚洲 欧美 激情 小说 另类 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 中文久久乱码一区二区 | 午夜成人1000部免费视频 | 大地资源中文第3页 | 精品国偷自产在线视频 | 欧美精品在线观看 | 麻豆果冻传媒2021精品传媒一区下载 | 麻豆md0077饥渴少妇 | 少妇无码av无码专区在线观看 | 人人爽人人爽人人片av亚洲 | 亚洲gv猛男gv无码男同 | 亚洲中文字幕久久无码 | 人人澡人摸人人添 | 99久久精品午夜一区二区 | 性欧美熟妇videofreesex | 日日橹狠狠爱欧美视频 | 无码人妻丰满熟妇区毛片18 | 国产一区二区三区四区五区加勒比 | 亚洲精品久久久久久一区二区 | 老头边吃奶边弄进去呻吟 | 色婷婷欧美在线播放内射 | 色欲av亚洲一区无码少妇 | 强伦人妻一区二区三区视频18 | 麻豆精品国产精华精华液好用吗 | 午夜精品久久久久久久 | 性欧美大战久久久久久久 | 在线观看免费人成视频 | 野狼第一精品社区 | 亚洲欧美色中文字幕在线 | 日本大香伊一区二区三区 | 蜜臀aⅴ国产精品久久久国产老师 | 精品无码成人片一区二区98 | 亚洲精品久久久久avwww潮水 | 色综合久久久无码中文字幕 | 精品国产精品久久一区免费式 | 女高中生第一次破苞av | 人妻少妇被猛烈进入中文字幕 | 日韩欧美群交p片內射中文 | 人妻插b视频一区二区三区 | 成在人线av无码免观看麻豆 | 久久综合激激的五月天 | 77777熟女视频在线观看 а天堂中文在线官网 | 亚洲中文字幕无码一久久区 | 精品国产aⅴ无码一区二区 | 老司机亚洲精品影院无码 | 色婷婷综合中文久久一本 | 日本熟妇人妻xxxxx人hd | 色综合视频一区二区三区 | 久久精品国产日本波多野结衣 | 久久久久久久久888 | 婷婷丁香六月激情综合啪 | 乱中年女人伦av三区 | 国产精品无码mv在线观看 | 少妇愉情理伦片bd | 国精品人妻无码一区二区三区蜜柚 | 国产精品久久精品三级 | 国产一区二区三区精品视频 | 亚洲一区二区三区四区 | 东京热无码av男人的天堂 | 日韩av无码中文无码电影 | 亚洲中文字幕在线无码一区二区 | 亚洲国产精品久久久久久 | 久久综合九色综合欧美狠狠 | 麻豆精品国产精华精华液好用吗 | 精品国产青草久久久久福利 | 亚洲欧洲日本无在线码 | 国产人妻精品午夜福利免费 | 成年女人永久免费看片 | 亚洲s色大片在线观看 | 亚洲中文字幕无码一久久区 | 又黄又爽又色的视频 | aa片在线观看视频在线播放 | 少女韩国电视剧在线观看完整 | 久激情内射婷内射蜜桃人妖 | 日本va欧美va欧美va精品 | 亚洲 日韩 欧美 成人 在线观看 | 亚洲毛片av日韩av无码 | 成 人 免费观看网站 | 中文精品无码中文字幕无码专区 | 国产精品亚洲а∨无码播放麻豆 | 国语自产偷拍精品视频偷 | 亚洲第一无码av无码专区 | 欧洲精品码一区二区三区免费看 | 在线视频网站www色 | 欧美性色19p | 国产亚洲精品久久久久久大师 | 无码国内精品人妻少妇 | 精品无码成人片一区二区98 | 国产亚洲精品久久久ai换 | 日韩人妻无码中文字幕视频 | 少妇一晚三次一区二区三区 | 国产精品无码mv在线观看 | 国产电影无码午夜在线播放 | 少妇人妻av毛片在线看 | 妺妺窝人体色www在线小说 | 老熟女重囗味hdxx69 | 国产乱人偷精品人妻a片 | 国产成人精品视频ⅴa片软件竹菊 | 又湿又紧又大又爽a视频国产 | 成年美女黄网站色大免费全看 | 国产精品无码一区二区三区不卡 | 欧美兽交xxxx×视频 | 国产人妻精品一区二区三区不卡 | 国产在线无码精品电影网 | 国产av无码专区亚洲a∨毛片 | 亚洲精品国偷拍自产在线麻豆 | 亚洲精品一区二区三区大桥未久 | 日日躁夜夜躁狠狠躁 | 国产莉萝无码av在线播放 | 国産精品久久久久久久 | 欧美日韩一区二区三区自拍 | 国产成人无码一二三区视频 | 国产av无码专区亚洲awww | 亚洲va中文字幕无码久久不卡 | 国产成人精品优优av | 国产精品久久久久无码av色戒 | 99精品无人区乱码1区2区3区 | ass日本丰满熟妇pics | 国产97色在线 | 免 | 国产sm调教视频在线观看 | 精品水蜜桃久久久久久久 | 牛和人交xxxx欧美 | 国产在线一区二区三区四区五区 | 国产人妻精品一区二区三区不卡 | 日本又色又爽又黄的a片18禁 | 无套内谢的新婚少妇国语播放 | 丰满肥臀大屁股熟妇激情视频 | 国产sm调教视频在线观看 | 久久久无码中文字幕久... | 国产一区二区不卡老阿姨 | 少妇性荡欲午夜性开放视频剧场 | 在线播放亚洲第一字幕 | 亚洲人成网站免费播放 | 国产网红无码精品视频 | 精品国产青草久久久久福利 | 国产精品久久久一区二区三区 | 日本一卡二卡不卡视频查询 | 成人无码视频在线观看网站 | 荡女精品导航 | 国产成人一区二区三区别 | 国产国产精品人在线视 | 久久99精品久久久久久 | 亚洲精品国产a久久久久久 | 丁香啪啪综合成人亚洲 | 伊人久久大香线焦av综合影院 | 天天拍夜夜添久久精品大 | 久久无码人妻影院 | 牲交欧美兽交欧美 | 亚洲性无码av中文字幕 | 国产精品成人av在线观看 | 国产激情无码一区二区app | 日韩少妇内射免费播放 | 日本丰满熟妇videos | 亚洲一区二区三区国产精华液 | 全黄性性激高免费视频 | 熟女少妇在线视频播放 | 日本免费一区二区三区最新 | 亚洲精品成a人在线观看 | 亚洲の无码国产の无码影院 | 国产精品第一区揄拍无码 | 帮老师解开蕾丝奶罩吸乳网站 | 国产又爽又黄又刺激的视频 | 人人妻人人澡人人爽精品欧美 | 99麻豆久久久国产精品免费 | 色一情一乱一伦一视频免费看 | 久久精品中文闷骚内射 | 久久综合九色综合欧美狠狠 | 亚洲爆乳无码专区 | 2019nv天堂香蕉在线观看 | 国产高清av在线播放 | 奇米影视7777久久精品 | 任你躁在线精品免费 | 美女黄网站人色视频免费国产 | 亚洲中文无码av永久不收费 | 天堂亚洲2017在线观看 | 日韩视频 中文字幕 视频一区 | 亚洲综合另类小说色区 | 色窝窝无码一区二区三区色欲 | 婷婷色婷婷开心五月四房播播 | 丁香花在线影院观看在线播放 | 精品国产一区二区三区四区在线看 | 女人色极品影院 | 欧美精品无码一区二区三区 | 亚洲一区二区三区含羞草 | 日韩人妻无码中文字幕视频 | 欧美国产亚洲日韩在线二区 | 色妞www精品免费视频 | 国产亚洲视频中文字幕97精品 | 国产精品久久福利网站 | 无遮挡啪啪摇乳动态图 | 色综合天天综合狠狠爱 | 亚洲国产午夜精品理论片 | 97夜夜澡人人双人人人喊 | 久久精品国产日本波多野结衣 | 精品久久久久久亚洲精品 | 国产精品-区区久久久狼 | 激情国产av做激情国产爱 | 18无码粉嫩小泬无套在线观看 | 日本一区二区更新不卡 | 97精品国产97久久久久久免费 | 2020久久香蕉国产线看观看 | 极品尤物被啪到呻吟喷水 | 一本加勒比波多野结衣 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 久久久久久亚洲精品a片成人 | 天天做天天爱天天爽综合网 | 99视频精品全部免费免费观看 | 欧美老熟妇乱xxxxx | 精品国精品国产自在久国产87 | 女人被男人爽到呻吟的视频 | 性色av无码免费一区二区三区 | 国产无遮挡又黄又爽又色 | 亚洲一区二区三区无码久久 | 免费观看又污又黄的网站 | 国产免费久久久久久无码 | 免费无码午夜福利片69 | 久久亚洲中文字幕无码 | 极品嫩模高潮叫床 | 久久精品国产99精品亚洲 | 国产精品美女久久久网av | 国产精品久久久久9999小说 | 国产婷婷色一区二区三区在线 | 久久综合久久自在自线精品自 | 午夜肉伦伦影院 | 97色伦图片97综合影院 | 亚洲日本一区二区三区在线 | 丰满人妻精品国产99aⅴ | √天堂中文官网8在线 | 欧美日韩综合一区二区三区 | 奇米影视7777久久精品 | 娇妻被黑人粗大高潮白浆 | 久久国产精品偷任你爽任你 | 丁香啪啪综合成人亚洲 | 亚洲欧美精品aaaaaa片 | 精品国产青草久久久久福利 | 久久久精品456亚洲影院 | 久久精品国产大片免费观看 | 内射欧美老妇wbb | 国产精品自产拍在线观看 | 又粗又大又硬毛片免费看 | 国产精品办公室沙发 | 色综合视频一区二区三区 | 亚洲一区二区三区四区 | 99精品无人区乱码1区2区3区 | 久久精品国产一区二区三区肥胖 | 国产成人亚洲综合无码 | 亚洲国产午夜精品理论片 | 日韩精品乱码av一区二区 | 狠狠色丁香久久婷婷综合五月 | 又大又黄又粗又爽的免费视频 | 丰满肥臀大屁股熟妇激情视频 | 成人aaa片一区国产精品 | 欧洲熟妇精品视频 | 特大黑人娇小亚洲女 | 色婷婷av一区二区三区之红樱桃 | 国产精品久久久久7777 | 澳门永久av免费网站 | 丰满人妻一区二区三区免费视频 | 人妻夜夜爽天天爽三区 | 精品久久久无码中文字幕 | 中文字幕久久久久人妻 | 久久久中文久久久无码 | 国产亚洲欧美日韩亚洲中文色 | 国产亚洲美女精品久久久2020 | 欧美猛少妇色xxxxx | 成 人 网 站国产免费观看 | 久久国产精品偷任你爽任你 | 精品乱子伦一区二区三区 | 青青青手机频在线观看 | 亚洲无人区一区二区三区 | 自拍偷自拍亚洲精品10p | 日本护士xxxxhd少妇 | 熟妇人妻激情偷爽文 | 精品午夜福利在线观看 | 真人与拘做受免费视频一 | av无码电影一区二区三区 | 欧美日韩视频无码一区二区三 | 亚洲精品鲁一鲁一区二区三区 | 特级做a爰片毛片免费69 | 亚洲成a人片在线观看无码3d | www国产精品内射老师 | 日本乱偷人妻中文字幕 | 人人妻人人藻人人爽欧美一区 | 兔费看少妇性l交大片免费 | 国产精品亚洲综合色区韩国 | 久久精品人妻少妇一区二区三区 | 搡女人真爽免费视频大全 | 国产极品美女高潮无套在线观看 | 全黄性性激高免费视频 | 丰满人妻翻云覆雨呻吟视频 | 国产精品手机免费 | 久久99热只有频精品8 | 内射欧美老妇wbb | 红桃av一区二区三区在线无码av | 亚洲春色在线视频 | 欧美老妇交乱视频在线观看 | 少妇一晚三次一区二区三区 | 无码人妻精品一区二区三区下载 | 欧美日韩色另类综合 | 日本丰满护士爆乳xxxx | 十八禁真人啪啪免费网站 | 国产莉萝无码av在线播放 | 强开小婷嫩苞又嫩又紧视频 | 久久久久免费看成人影片 | 亚洲精品一区二区三区在线观看 | 一本色道久久综合狠狠躁 | 久久精品一区二区三区四区 | 亚洲の无码国产の无码步美 | 午夜免费福利小电影 | 三级4级全黄60分钟 | 日日碰狠狠躁久久躁蜜桃 | 性史性农村dvd毛片 | 内射老妇bbwx0c0ck | 美女毛片一区二区三区四区 | 精品人妻中文字幕有码在线 | 无遮挡啪啪摇乳动态图 | 亚洲一区二区三区香蕉 | 好爽又高潮了毛片免费下载 | 亚洲国产午夜精品理论片 | 国产亲子乱弄免费视频 | 亚洲综合在线一区二区三区 | 久久久www成人免费毛片 | 少妇人妻大乳在线视频 | 装睡被陌生人摸出水好爽 | 高中生自慰www网站 | 免费观看又污又黄的网站 | 亚洲午夜福利在线观看 | 精品一区二区三区波多野结衣 | 国产精品高潮呻吟av久久4虎 | 美女黄网站人色视频免费国产 | 国产69精品久久久久app下载 | 久久久无码中文字幕久... | 国产精品久久久久久亚洲毛片 | 国语自产偷拍精品视频偷 | 日韩在线不卡免费视频一区 | 无码中文字幕色专区 | 帮老师解开蕾丝奶罩吸乳网站 | 久精品国产欧美亚洲色aⅴ大片 | 性啪啪chinese东北女人 | 欧美激情综合亚洲一二区 | 中文字幕乱码人妻二区三区 | 无码成人精品区在线观看 | 国产麻豆精品一区二区三区v视界 | 国产舌乚八伦偷品w中 | 美女扒开屁股让男人桶 | 最新版天堂资源中文官网 | 精品国偷自产在线 | 国产偷国产偷精品高清尤物 | 国产精品a成v人在线播放 | 亚洲国产精品一区二区第一页 | 婷婷色婷婷开心五月四房播播 | 玩弄少妇高潮ⅹxxxyw | 亚洲第一无码av无码专区 | 3d动漫精品啪啪一区二区中 | 国产精品久久久一区二区三区 | 国产精品.xx视频.xxtv | 成人精品视频一区二区 | 亚洲人成网站免费播放 | 久久国产劲爆∧v内射 | 中文字幕人成乱码熟女app | 激情亚洲一区国产精品 | 成人三级无码视频在线观看 | 亚洲熟熟妇xxxx | 牲欲强的熟妇农村老妇女视频 | 国产高清av在线播放 | a国产一区二区免费入口 | 国产乱码精品一品二品 | 亚洲精品www久久久 | 精品无人区无码乱码毛片国产 | 日韩 欧美 动漫 国产 制服 | 亚洲熟妇自偷自拍另类 | 久久国产自偷自偷免费一区调 | av人摸人人人澡人人超碰下载 | 国产另类ts人妖一区二区 | 亚洲娇小与黑人巨大交 | 97无码免费人妻超级碰碰夜夜 | 131美女爱做视频 | 欧美日本免费一区二区三区 | 亚洲成av人片在线观看无码不卡 | 草草网站影院白丝内射 | 精品无码国产自产拍在线观看蜜 | 精品亚洲成av人在线观看 | 国产精品久久久久7777 | 亚洲日韩精品欧美一区二区 | 国产精品福利视频导航 | 熟妇人妻无乱码中文字幕 | 国产农村妇女高潮大叫 | 久久久精品国产sm最大网站 | 99久久久无码国产aaa精品 | 亚洲国产综合无码一区 | 欧美黑人性暴力猛交喷水 | 国产在线精品一区二区三区直播 | 午夜丰满少妇性开放视频 | 国产精品国产自线拍免费软件 | 少妇无码一区二区二三区 | 亚洲无人区午夜福利码高清完整版 | 国产亚洲精品久久久久久久 | 亚洲中文字幕无码一久久区 | av香港经典三级级 在线 | 亚洲爆乳大丰满无码专区 | 国产精品嫩草久久久久 | 久久精品成人欧美大片 | 99久久精品无码一区二区毛片 | 国产成人久久精品流白浆 | 欧美 丝袜 自拍 制服 另类 | 人妻插b视频一区二区三区 | 欧美激情内射喷水高潮 | 77777熟女视频在线观看 а天堂中文在线官网 | 日本熟妇人妻xxxxx人hd | 高中生自慰www网站 | 久久综合激激的五月天 | av无码不卡在线观看免费 | 成人无码精品1区2区3区免费看 | 成人影院yy111111在线观看 | 亚洲va中文字幕无码久久不卡 | 樱花草在线社区www | 小鲜肉自慰网站xnxx | 露脸叫床粗话东北少妇 | 日韩欧美成人免费观看 | 少妇被粗大的猛进出69影院 | 2019nv天堂香蕉在线观看 | 中文字幕色婷婷在线视频 | 中文字幕乱码人妻二区三区 | 久久精品国产精品国产精品污 | 97资源共享在线视频 | 亚洲一区二区三区香蕉 | 国产人成高清在线视频99最全资源 | 六十路熟妇乱子伦 | 综合网日日天干夜夜久久 | 日本一卡二卡不卡视频查询 | 欧美野外疯狂做受xxxx高潮 | 中文字幕人妻丝袜二区 | 国产在线精品一区二区三区直播 | 四虎永久在线精品免费网址 | 亚洲の无码国产の无码步美 | а√资源新版在线天堂 | 精品久久久中文字幕人妻 | 国内丰满熟女出轨videos | 国产精品久久久 | 国产9 9在线 | 中文 | 色老头在线一区二区三区 | 无码人妻丰满熟妇区五十路百度 | 欧美日韩人成综合在线播放 | 捆绑白丝粉色jk震动捧喷白浆 | 久久精品女人的天堂av | 少妇被黑人到高潮喷出白浆 | 日本一区二区三区免费高清 | 粉嫩少妇内射浓精videos | 人人澡人摸人人添 | 性欧美疯狂xxxxbbbb | 国产成人一区二区三区别 | 久久zyz资源站无码中文动漫 | 色欲人妻aaaaaaa无码 | www国产亚洲精品久久久日本 |