3atv精品不卡视频,97人人超碰国产精品最新,中文字幕av一区二区三区人妻少妇,久久久精品波多野结衣,日韩一区二区三区精品

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

.NET内存性能分析指南

發(fā)布時間:2023/12/4 asp.net 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 .NET内存性能分析指南 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

.NET Memory Performance Analysis

知道什么時候該擔(dān)心,以及在需要擔(dān)心的時候該怎么做

譯者注

作者信息:Maoni Stephens?- 微軟架構(gòu)師,負責(zé).NET Runtime GC設(shè)計與實現(xiàn)?博客鏈接?Github

譯者:Bing Translator、INCerry 博客鏈接:https://incerry.cnblogs.com?聯(lián)系郵箱:incerry@foxmail.com

本文已獲得Maoni大佬授權(quán),另外感謝@曉青、@賈佬、@黑洞、@曉晨、@一線碼農(nóng)?在百忙之中抽出時間校對和提出修改建議。

本文Github倉庫:https://github.com/InCerryGit/mem-doc/blob/master/doc/.NETMemoryPerformanceAnalysis.zh-CN.md

原文鏈接:https://github.com/Maoni0/mem-doc/blob/master/doc/.NETMemoryPerformanceAnalysis.md

本文90%通過機器翻譯,另外10%譯者按照自己的理解進行翻譯,和原文相比有所刪減,與原文并不是一一對應(yīng),但是意思基本一致。另外文章較長,還沒有足夠的時間完全校對好,后續(xù)還會對一些語句不通順、模糊和錯漏的地方進行補充,請關(guān)注文檔版本號

文檔版本號修訂記錄修訂人修訂日期
0.0.1翻譯文檔創(chuàng)建-2021-12-05
0.0.2人工校對,修復(fù)超鏈接錯誤-2021-12-16
0.0.3校對信息,修復(fù)樣式問題,修復(fù)描述問題 thx @曉青 @Maoni-2021-12-17

譯者水平有限,如果錯漏歡迎批評指正

本文檔的目的

本文旨在幫助.NET開發(fā)者,如何思考內(nèi)存性能分析,并在需要時找到正確的方法來進行這種分析。在本文檔中.NET的包括.NET Framework和.NET Core。為了在垃圾收集器和框架的其他部分獲得最新的內(nèi)存改進,我強烈建議你使用.NET Core,如果你還沒有的話,因為那是應(yīng)該盡快去升級的地方。

本文檔的狀態(tài)

這是一份正在完善的文檔。現(xiàn)在,這份文檔主要是在Windows上。添加相應(yīng)的Linux材料肯定會使它更有用。我正計劃在未來這樣做,但也非常歡迎其他朋友(尤其是對Linux部分)對該文件的貢獻。

如何閱讀本文檔

這是一份很長的文檔,但你不需要讀完它;你也不需要按順序閱讀各部分。根據(jù)你在做性能分析方面的經(jīng)驗,有些章節(jié)可以完全跳過。

🔹 如果你對性能分析工作完全陌生,我建議從頭開始。

🔹 對于那些已經(jīng)能自如地進行一般的性能分析工作,但希望增加他們在管理內(nèi)存相關(guān)主題方面的知識的人,他們可以跳過開頭,直接進入基礎(chǔ)知識部分。

🔹 如果你不是很有經(jīng)驗,并且在做一次性能分析,你可以跳到知道什么時候該擔(dān)心部分開始閱讀,如果需要,再參考基礎(chǔ)知識部分的具體內(nèi)容。

🔹 如果你是一名性能工程師,其工作包括將托管內(nèi)存分析作為一項常規(guī)任務(wù),但又是.NET的新手,我強烈建議你真正閱讀并內(nèi)化GC基礎(chǔ)部分,因為它能幫助你更快地關(guān)注正確的事情。然而,如果你手頭有一個緊急問題,你可以去看看我在本文檔中將要使用的工具,熟悉它,然后看看你是否能在GC暫停問題或堆大小問題部分找到相關(guān)癥狀。

🔹 如果你已經(jīng)有做托管內(nèi)存性能分析工作的經(jīng)驗,并且有具體的問題,你可以在GC停頓時間長或GC堆太大部分找到它。

注意!

當我在寫這篇文檔時,我打算根據(jù)分析的需要來介紹一些概念,如并發(fā)的GC或釘住。所以在你閱讀的過程中,你會逐漸接觸到它們。如果你已經(jīng)知道它們是什么,并且正在尋找關(guān)于特定概念的解釋,這里有它們的鏈接-

如何看待性能分析工作?

那些在做性能分析方面有經(jīng)驗的人知道,這可能就像偵探工作一樣--沒有 "如果你按照這10個步驟去做,你就會改善性能或從根本上解決性能問題"的方法。這是為什么呢?因為在運行的東西不僅僅是你的代碼 - 還有你使用操作系統(tǒng)、運行時、庫(至少是BCL,但通常是許多其他的庫)。而運行你的代碼的線程需要與同一進程中的其他線程和/或其他進程共享機器/VM/容器。

然而,這并不意味著你需要對我剛才提到的一切有一個徹底的了解。否則我們都不會有任何成就 - 你根本沒有時間。但你不需要這樣做。你只需要了解足夠的基礎(chǔ)知識,掌握足夠的性能分析技能,這樣你就可以專注于自己代碼的性能分析。在本文中,我們將討論這兩點。我還會解釋事情為什么會這樣,這樣才有意義,而不是讓你背誦那些很容易被翻出來的東西。

這篇文檔談到了你自己可以做什么,以及什么時候是把分析工作交給GC團隊的好時機,因為這將是需要在運行時進行的改進。很明顯,我們在GC中仍然在做改進的工作(否則我就不會還在這個團隊中)。正如我們將看到的,GC的行為是由你的應(yīng)用行為驅(qū)動的,所以你肯定可以改變你的應(yīng)用行為來影響GC。在你作為性能工程師需要做多少工作和GC自動處理多少工作之間存在著一個平衡。.NET GC的理念是,我們盡量自動處理;當我們需要你的參與時(通過配置),我們會通過有意義的方式要求你的協(xié)助,這種方式是從應(yīng)用程序的角度,并不要求你了解GC的全部細節(jié)。當然,GC團隊一直在努力讓.NET GC處理越來越多的性能場景,這樣用戶就不需要擔(dān)心了。但如果你遇到了GC目前不能很好處理的情況,我將指出你可以做什么來解決它。

我對性能分析的目標是使客戶需要做的大部分分析自動化。我們在這方面已經(jīng)走了很長的路,但我們還沒有達到所有分析都自動化的程度。在這份文件中,我將告訴你目前做分析的方法,在文件的最后,我將給你一個展望,說明我們正在為實現(xiàn)這個目標做了什么樣的改進。

挑選正確的方法來做性能分析

我們都有有限的資源,如何將這些資源花在能夠產(chǎn)生最大回報的事情上是關(guān)鍵。這意味著你應(yīng)該找到哪個部分是最值得優(yōu)化的,以及如何以最有效的方式優(yōu)化它們。當你斷定你需要優(yōu)化某些東西,或者你要如何優(yōu)化某些東西時,應(yīng)該有一個合理的理由來說明你為什么這樣做。

知道你的目標是什么

當人們第一次來找我時,我總是問他們這樣一個問題 - 你的性能目標是什么?不同的產(chǎn)品有非常不同的性能要求。在你想出一個數(shù)字之前(例如,將某些東西提高X%),你需要知道你要優(yōu)化的是什么。在最高層次的角度來看,這些是需要優(yōu)化的方面 -

?? 優(yōu)化內(nèi)存占用,例如,需要在同一臺機器上盡可能多地運行實例。

?? 優(yōu)化吞吐量,例如,需要在一定的時間內(nèi)處理盡可能多的請求。

??針對尾部延遲進行優(yōu)化,例如,需要滿足一定的延遲SLA。

當然,你可以有多個這樣的要求,例如,你可能需要滿足一個SLA,但仍然需要在一個時間段內(nèi)至少處理一定數(shù)量的請求。在這種情況下,你需要弄清楚什么是優(yōu)先考慮的,這將決定你應(yīng)該把大部分精力放在什么地方。

你要明白GC只是框架的一個部分

GC行為的改變可能是由于GC本身的變化或框架其他部分的變化,當你使用一個新版本時,框架中通常會有很多改動。當你在升級后看到內(nèi)存行為的變化時,可能是由于GC的變化或框架中的其他東西開始分配更多的內(nèi)存,并以不同的方式保留內(nèi)存。此外,如果你升級你的操作系統(tǒng)版本或在不同的虛擬化環(huán)境中運行你的產(chǎn)品,你也可以得到不同的行為,因為它們可能導(dǎo)致你的應(yīng)用程序出現(xiàn)不同的行為。

不要猜測,去測量

測量是你在開始一個產(chǎn)品時絕對應(yīng)該計劃做的事情,而不是在事情發(fā)生后才想到的,特別是當你知道你的產(chǎn)品需要在相當高負載的情況下運行時。如果你正在閱讀這份文件,那么你很有可能正在從事一些對性能有要求的工作。

對于我所接觸的大多數(shù)工程師來說,測量并不是一個陌生的概念。然而,如何測量和測量什么是我見過的許多人需要幫助的事情。

?? 這意味著你需要有一些方法來真實地測量你的性能。在復(fù)雜的服務(wù)器應(yīng)用程序上的一個常見問題是,你很難在你的測試環(huán)境中模擬你在生產(chǎn)中實際看到的情況。

?? 測量并不僅僅意味著 "我可以測量我的應(yīng)用程序每秒可以處理多少個請求,因為這是我所關(guān)心的",它意味著你也應(yīng)該有一些東西,當你的測量結(jié)果告訴你某些東西沒有達到理想的水平時,你可以做有意義的性能分析。能夠發(fā)現(xiàn)問題是一方面。如果你沒有任何東西可以幫助你找出這些問題的原因,那就沒有什么幫助了。當然,這需要你知道如何收集數(shù)據(jù),我們將在下面談及。

?? 能夠衡量來自修復(fù)/解決方法的效果。

足夠的測量,讓你知道應(yīng)該把精力集中在哪個領(lǐng)域

我一次又一次地聽到人們會測量某一個點,并選擇只優(yōu)化這個點,因為他們從朋友或同事那里聽說了這個點。這就是了解基本原理真正有幫助的地方,這樣你就不會一直關(guān)注你聽說過的這個點,而這個點可能是也可能不是正確的。

測量那些可能影響你的性能指標的因素

在您知道哪些因素可能對您關(guān)心的事情(即您的性能指標)影響最大之后,你應(yīng)該測量它們的影響,這樣你就可以觀察它們在你開發(fā)產(chǎn)品的過程中貢獻是大還是小。一個完美的例子是,服務(wù)器應(yīng)用程序如何改善其P95請求延遲(即第95百分位的請求延遲)。這是一個幾乎每個網(wǎng)絡(luò)服務(wù)器都會看的性能指標。當然,很多因素都可以影響這個延遲,但你知道那些可能影響最大的因素。

網(wǎng)絡(luò)IO只是另一個可能導(dǎo)致你的請求延遲的因素的例子。這里的方框?qū)挾葍H僅是為了說明問題。

你每天(或你記錄P95的任何時間單位)的P95延遲可能會波動,但你知道大致的數(shù)字。比方說,你的平均請求延遲是<3ms,而你的P95大約是70ms。你必須有一些方法來測量每個請求總共需要多長時間(否則你就不會知道你的延遲百分數(shù))。你可以記錄你看到GC暫停或網(wǎng)絡(luò)IO的時間(兩者都可以通過事件來測量)。對于那些在P95延遲附近的請求,你可以計算出 "P95的GC影響",即

這些請求觀察到的GC暫停總時間/請求總延時

如果這是10%,你應(yīng)該有其他因素沒有計算在內(nèi)。

通常人們會猜測GC停頓是影響他們P95延遲的原因。當然這是有可能的,但這絕不是唯一可能的因素,也不是對你的P95影響最大的因素。這就是為什么了解影響很重要,它告訴你應(yīng)該把大部分精力花在什么地方。

而影響你的P95的因素可能與影響你的P99或P99.99的因素非常不同;同樣的原則也適用于其他百分位數(shù)。

優(yōu)化框架代碼與優(yōu)化用戶代碼

雖然這個文檔是為每一個關(guān)心內(nèi)存分析的人準備的,但根據(jù)你所工作的層次,應(yīng)該有不同的考慮。

作為一個從事終端產(chǎn)品的人,你有很大的自由空間去優(yōu)化,因為你可以預(yù)測你的產(chǎn)品在什么樣的環(huán)境下運行,例如,一般來說,你知道你傾向于哪種資源的飽和,CPU,內(nèi)存或其他東西。你可以控制你的產(chǎn)品在什么樣的機器/虛擬機上運行,你使用什么樣的庫,你如何使用它們。你可以做一些估計,比如 "我們的機器上有128GB的內(nèi)存,計劃在我們最大的進程中拿出20GB的內(nèi)存緩存"。

從事平臺技術(shù)或庫工作的人無法預(yù)測他們的代碼將在什么樣的環(huán)境中運行。這意味著:1)如果希望用戶能夠在性能關(guān)鍵路徑上使用代碼,則需要節(jié)約內(nèi)存使用;2)你可能要提供不同的API,在性能和可用性之間做出權(quán)衡,并指導(dǎo)你的用戶如何做。

內(nèi)存基礎(chǔ)知識

正如我在上面提到的,讓一個人對整個技術(shù)棧有透徹的了解是完全不現(xiàn)實的。本節(jié)列出了任何需要從事內(nèi)存性能分析工作的人都必須知道的基本知識。

虛擬內(nèi)存基礎(chǔ)知識

我們通過VMM(虛擬內(nèi)存管理器)使用內(nèi)存,它為每個進程提供了自己的虛擬地址空間,盡管同一臺機器上的所有進程都共享物理內(nèi)存(如果你有頁面文件的話)。如果你在一個虛擬機中運行,虛擬機就有一種在真實機器上運行的錯覺。對于應(yīng)用程序來說,實際上,你很少會直接使用虛擬內(nèi)存工作。如果你寫的是本地代碼,通常你會通過一些本地分配器來使用虛擬地址空間,比如CRT堆,或者C++的new/delete關(guān)鍵字 - 這些分配器會代表你分配和釋放虛擬內(nèi)存;如果你寫的是托管代碼,GC是代表你分配/釋放虛擬內(nèi)存的人。

每個VA(虛擬地址)范圍(指虛擬地址的連續(xù)范圍)可以處于不同的狀態(tài) - 空閑 (free)、已保留(reserved) 和已提交(committed)。"空閑"很容易理解,就是空閑的內(nèi)存。"已保留"和"已提交"之間的區(qū)別有時讓人困惑。"已保留"是說 "我想讓這個區(qū)域的內(nèi)存供我自己使用"。當你"保留"了一個虛擬地址的范圍后,這個范圍就不能用來滿足其他的"保留"請求。在這一點上,你還不能在這個地址范圍內(nèi)存儲你的任何數(shù)據(jù) - 你必須"提交"它才可以,這意味著系統(tǒng)將不得不用一些物理存儲來支持它,以便你可以在其中存儲東西。當你通過性能工具查看內(nèi)存時,要確保你看的是正確的東西。如果你的預(yù)留空間用完了,或者"提交"空間用完了,你就會出現(xiàn)內(nèi)存不足的情況(在本文檔中,我主要關(guān)注Windows VMM--在Linux中,當你實際接觸到內(nèi)存時,你會出現(xiàn)OOM(Out Of Memory))。

虛擬內(nèi)存可以是私有或共享的。私有意味著它只被當前進程使用,而共享意味著它可以被其他進程共享。所有與GC相關(guān)的內(nèi)存使用都是私有的。

虛擬地址空間可能被分割--換句話說,地址空間中可能有 "缺口"(空閑塊)。當你請求保留一大塊虛擬內(nèi)存時,虛擬機管理器需要在虛擬地址范圍內(nèi)找到一個足夠大的空閑塊來滿足該請求--如果你只有幾個空閑塊,其總和足夠大,那就無法工作。這意味著即使你有2GB,你也不一定能看到所有的2GB被使用。當大多數(shù)應(yīng)用程序作為32位進程運行時,這是一個嚴重的問題。今天,我們在64位有一個充足的虛擬地址范圍,所以物理內(nèi)存是主要的關(guān)注點。當你提交內(nèi)存時,VMM確保你有足夠的物理存儲空間,如果你真的想使用該內(nèi)存。當你實際寫入數(shù)據(jù)時,VMM將在物理內(nèi)存中找到一個頁面(4KB)來存儲這些數(shù)據(jù)。這個頁面現(xiàn)在是你進程工作集的一部分。當你啟動你的進程時,這是一個非常正常的操作。

當機器上的進程使用的內(nèi)存總量超過機器所擁有的內(nèi)存時,一些頁面將需要被寫入頁面文件(如果有的話,大多數(shù)情況下是這樣的)。這是一個非常緩慢的操作,所以通常的做法是盡量避免進入分頁。我在簡化這個問題--實際的細節(jié)與這個討論沒有關(guān)系。當進程處于穩(wěn)定狀態(tài)時,通常你希望看到你正在使用的頁面被保留在你的工作集中,這樣我們就不需要支付任何成本來把它們帶回來。在下一節(jié)中,我們將討論GC是如何避免分頁的。

我故意把這一節(jié)寫得很短,因為GC才是需要代表你與虛擬內(nèi)存互動的人,但了解一點基本情況有助于解釋性能工具的結(jié)果。

GC基礎(chǔ)

垃圾收集器提供了內(nèi)存安全的巨大好處,使開發(fā)人員不必手動釋放內(nèi)存,并節(jié)省了可能是幾個月或幾年的調(diào)試堆損壞的時間。如果你不得不調(diào)試堆損壞,你就會知道這有多難。但是它也給內(nèi)存性能分析帶來了挑戰(zhàn),因為GC不會在每個對象死亡后運行(這將是令人難以置信的低效),而且GC越復(fù)雜,如果你需要做內(nèi)存分析,你就必須考慮得越多(你可能會,也可能不會,我們將在下一節(jié)討論這個問題)。本節(jié)是為了建立一些基本概念,幫助你對.NET GC有足夠的了解,以便在面對內(nèi)存調(diào)查時知道什么是正確的方法。

了解GC堆內(nèi)存的使用與進程/機器內(nèi)存的使用情況

  • GC堆只是你進程中的一種內(nèi)存使用情況

    在每個進程中,每個使用內(nèi)存的組件都是相互共存的。在任何一個.NET進程中,總有一些非GC堆的內(nèi)存使用,例如,在你的進程中總是有一些模塊被加載,需要消耗內(nèi)存。但可以說,對于大多數(shù)的.NET應(yīng)用程序來說,這意味著GC堆占用大部分的內(nèi)存。

    如果一個進程的私有提交字節(jié)總數(shù)(如上所述,GC堆總是在私有內(nèi)存中)與你的GC堆的提交字節(jié)數(shù)相當接近,你就知道大部分是由于GC堆本身造成的,所以這就是你應(yīng)該關(guān)注的地方。如果你觀察到一個明顯的差異,這時你應(yīng)該開始擔(dān)心查看進程中的其他內(nèi)存使用情況。

  • GC是按進程進行的,但它知道機器上的物理內(nèi)存負載

    GC是一個以進程為維度的組件(自從CLR誕生以來一直如此)。大多數(shù)GC的啟發(fā)式方法都是基于每個進程的測量,但GC也知道機器上的全局物理內(nèi)存負載。我們這樣做是因為我們想避免陷入分頁的情況。GC將一定的內(nèi)存負載百分比識別為 "高內(nèi)存負載情況"。當內(nèi)存負載百分比超過這個百分比時,GC就會進入一個更積極的模式,也就是說,如果它認為有成效的話,它會選擇做更多的完全阻塞的GC,因為它想減少堆的大小。

    目前,在較小的機器上(即內(nèi)存小于80GiB),默認情況下GC將90%視為高內(nèi)存負荷。在有更多內(nèi)存的機器上,這是在90%到97%之間。這個閾值可以通過COMPlus_GCHighMemPercent環(huán)境變量(或者從.NET 5開始在runtimeconfig.json中配置System.GC.HighMemoryPercent)來調(diào)整。你想調(diào)整這個的主要原因是為了控制堆的大小。例如,在一臺有64GB內(nèi)存的機器上,對于主要的主導(dǎo)進程,當有10%的內(nèi)存可用時,GC開始反應(yīng)是合理的。但是對于較小的進程(例如,如果一個進程只消耗1GB的內(nèi)存),GC可以在<10%的可用內(nèi)存下舒適地運行,所以你可能想對這些進程設(shè)置得更高。另一方面,如果你想讓較大的進程擁有較小的堆大小(即使機器上有大量可用的物理內(nèi)存),把這個值調(diào)低將是一個有效的方法,讓GC更快地做出反應(yīng),壓縮堆的大小。

    對于在容器中運行的進程,GC會根據(jù)容器的限制來考慮物理內(nèi)存。

    本節(jié)描述了如何找出每個GC觀察到的內(nèi)存負載。

了解GC是如何被觸發(fā)的

到目前為止,我們用GC來指代組件。下面我將用GC來指代組件,或者指代一個或多個在堆上進行內(nèi)存回收的集合行為,即GC或GCs。

  • 觸發(fā)GC的主要原因是分配

    由于GC是用來管理內(nèi)存分配的,自然觸發(fā)GC的最主要因素是由于分配。隨著進程的運行和分配的發(fā)生,GC將不斷被觸發(fā)。我們有一個 "分配預(yù)算 "的概念,它是決定何時觸發(fā)GC的主導(dǎo)因素。我們將在下面非常詳細地討論分配預(yù)算

  • 觸發(fā)GC的其他因素

    GC也可以由于機器運行到高物理內(nèi)存壓力而被觸發(fā),或者如果用戶通過調(diào)用GC.Collect而自己誘發(fā)GC。

了解分配內(nèi)存的成本

由于大多數(shù)GC是由于分配而觸發(fā)的,所以值得了解分配的成本。首先,當分配沒有觸發(fā)GC時,它是否有成本?答案是絕對的。有一些代碼需要運行來提供分配--只要你必須運行代碼來做一些事情,就會有成本。這只是一個多少的問題。

分配中開銷最大的部分(沒有觸發(fā)GC)是內(nèi)存清除。GC有一個契約,即它所有分配的內(nèi)存會用零填充。我們這樣做是為了安全、保障和可靠性的原因。

我們經(jīng)常聽到人們談?wù)摐y量GC成本,但卻不怎么談?wù)摐y量分配成本。一個明顯的原因是由于GC干擾了你的線程。還有一種情況是,監(jiān)測GC發(fā)生的時間是非常輕量的 - 我們提供了輕量級的工具,可以告訴你這個。但是分配一直在發(fā)生,而且很難在每次分配發(fā)生時都進行監(jiān)控 - 會占用很多性能資源,很可能使你的進程不再以有意義的狀態(tài)運行。我們可以通過以下適當?shù)姆绞絹頊y量分配成本,在工具部分,我們將看到如何用各種工具技術(shù)來做這些事情--

監(jiān)控內(nèi)存分配的3種方法

1)我們還可以測量GC的發(fā)生頻率,這告訴我們發(fā)生了多少分配。畢竟,大多數(shù)GC是由于分配而被觸發(fā)的。

2)對非常頻繁發(fā)生的事情進行分析的方法之一是抽樣。

3)當你有了CPU使用信息,你可以在GC方法名稱查看內(nèi)存清除的成本。實際上,通過GC方法名稱來查找東西顯然是非常內(nèi)部且專業(yè)的,并受制于實現(xiàn)的變化。但由于本文檔的目標是大眾,包括有經(jīng)驗的性能工程師,我將提到幾個具體的方法(其名稱往往不會有太大的變化),作為進行性能測量的一種方式。

如何正確看待GC堆的大小

這聽起來是一個簡單的問題。通過測量,對嗎?是的,但是當你測量GC堆的時候,就很重要了。

  • 看一下GC堆的大小與GC發(fā)生的時間關(guān)系

    這到底是什么意思?假設(shè)我們不考慮GC發(fā)生的時間,只是每秒鐘測量一次堆的大小。請看下面這個(編造的)例子

    表格 1

    動作這一秒過后的堆大小
    1分配 1 GB1 GB
    2分配 2 GB3 GB
    3分配 0 GB3 GB
    4GC發(fā)生(500M存活),然后分配1GB1.5 GB
    5分配 3 GB4.5 GB

    我們可以說,是的,有一個GC發(fā)生在第4秒,因為堆的大小比第3秒小。但我們再看看另一種可能性-

    表格2

    秒動作這一秒過后的堆大小
    1分配 1 GB1 GB
    2分配 2 GB3 GB
    3GC發(fā)生(1GB存活),然后分配2GB3 GB
    4分配 1 GB4 GB

    如果我們只有堆的大小數(shù)據(jù),我們就不能說GC是否已經(jīng)發(fā)生。

    這就是為什么測量GC發(fā)生時的堆大小是很重要的。自然,GC本身提供的部分性能測量數(shù)據(jù)正是如此 - 每次GC前后的堆大小,也就是說,每次GC的開始和結(jié)束(以及其他大量的數(shù)據(jù),我們將在本文的后面看到)。不幸的是,許多內(nèi)存工具,或者我經(jīng)常看到人們采取的診斷方法,都沒有考慮到這一點。他們做內(nèi)存診斷的方式是 "讓我給你看看在你碰巧問起的時候堆是什么樣子的"。這通常是沒有幫助的,有時甚至是完全誤導(dǎo)的。這并不是說像這樣的工具完全沒有幫助 - 當問題很簡單的時候,它們可能會有幫助。如果你有一個已經(jīng)持續(xù)了一段時間的非常大的內(nèi)存泄漏,并且你使用了一個工具來顯示你在那個時候的堆(要么通過采取進程轉(zhuǎn)儲和使用SoS,要么通過另一個工具來轉(zhuǎn)儲堆),那找到什么東西在泄露內(nèi)存就真的很容了。這是性能分析中的一個常見模式 - 問題越嚴重,就越容易找出問題。但是,當你遇到的性能問題不是這種顯而易見的情況時,這些工具就顯得不足了。

  • 分配預(yù)算

    看完上一段,思考分配預(yù)算的一個簡單方法是上一次GC退出時的堆大小和這次GC進入時的堆大小之間的差異。因此,分配預(yù)算是指在觸發(fā)下一次GC之前,GC允許多少分配。在表1和表2中,分配預(yù)算是一樣的 - 3GB。

    然而,由于.NET GC支持釘住對象(防止GC移動被釘住的對象)以及釘住的復(fù)雜情況,分配預(yù)算往往不是2個堆大小之間的區(qū)別。然而,預(yù)算是 "在觸發(fā)下一次GC之前的分配量 "的想法仍然成立。我們將在本文檔的后面討論更多關(guān)于釘住的問題(?后面的內(nèi)容.)。

    當試圖提高內(nèi)存性能時,我看到人們經(jīng)常做的一件事(或只做一件事)是減少分配。如果你真的可以在性能關(guān)鍵路徑開始之前預(yù)先分配所有的東西,我說你更有更多的權(quán)利!但是,這有時是非常不實際的。例如,如果你使用的是庫,你并不能完全控制它們的分配(當然,你可以嘗試找到一種無分配的方式來調(diào)用API,但并不保證有這樣的方式,而且它們的實現(xiàn)可能會改變)。

    那么,減少分配是一件好事嗎?是的,只要它確實會對你的應(yīng)用程序的性能產(chǎn)生影響,并且不會使你的代碼中的邏輯變得非常笨拙或復(fù)雜,從而使它成為一個值得的優(yōu)化。減少分配實際上會降低性能嗎?這完全取決于你是如何減少分配的。你是在消除分配還是用其他東西來代替它們?因為用其他東西代替分配可能不會減少GC所要做的工作。

  • 分代GC的影響

    .NET的GC是分代的,有3代,IOW,GC堆中的對象被分為3代;gen0是最年輕的一代,gen2是老一代。gen1作為一個緩沖區(qū),通常是為了在觸發(fā)GC時仍在請求中的數(shù)據(jù)(所以我們希望在我們做gen1時,這些數(shù)據(jù)不會被你的代碼所引用)。

    根據(jù)設(shè)計,分代GC不會在每次觸發(fā)GC時收集整個堆。他們嘗試做年輕一代的GC,比老一代的GC更頻繁。老一代的GC通常成本更高,因為它們收集的堆更多。

    你很可能曾經(jīng)聽說過 "GC暫停 "這個術(shù)語。GC暫停是指GC以STW(Stop-The-World)的方式執(zhí)行其工作時。對于并發(fā)GC來說,它與用戶線程同時進行大部分的GC工作,GC暫停的時間并不長,但是GC仍然需要花費CPU周期來完成它的工作。年輕的gen GCs,即gen0和gen1 GC,被稱為短暫的GC,而老的gen GC,即gen2 GC,也被稱為full GC,因為它們收集整個堆。當genX GC發(fā)生時,它收集了genX和它所有的年輕世代。因此,gen1 GC同時收集了堆中的gen0和gen1部分。

    這也使得看堆變得更加復(fù)雜,因為如果你剛從一個老一代的GC中出來,特別是一個正在整理的GC,你的堆的大小顯然比你在該GC被觸發(fā)之前要小得多;但如果你看一下年輕一代的GC,它們可能正在被整理,但堆的大小差異沒有那么大,這就是設(shè)計。

    上面提到的分配預(yù)算概念實際上是每一代的,所以gen0、gen1和gen2都有自己的分配預(yù)算。用戶的分配將發(fā)生在gen0,并消耗gen0的分配預(yù)算。當分配消耗了gen0的所有預(yù)算時,GC將被觸發(fā),gen0的幸存者將消耗gen1的分配預(yù)算。同樣地,gen1的幸存者將消耗gen2的預(yù)算。

    圖1 - 經(jīng)過不同代GC的對象

一個對象 "死了 "和它被清理掉之間的區(qū)別可能會讓人困惑。我收到的一個常見問題是:"我不再保留我的對象了,而且我看到GC正在發(fā)生,為什么我的對象還在那里?"。請注意,一個對象不再被用戶代碼持有的事實(在本文中,用戶代碼包括框架/庫代碼,即不是GC代碼)需要被GC掃描到。要記住的一個重要規(guī)則是:"如果一個對象在genX中,這意味著它只可能在genX GC發(fā)生時被回收",因為這時GC會真正去檢查genX中的對象是否還活著。如果一個對象在gen2中,不管發(fā)生了多少次短暫的GC(即0代和1代GC),這個對象仍然會在那里,因為GC根本沒有收集gen2。另一種思考方式是,一個對象所處的代數(shù)越高,GC需要收集的工作就越多。

  • 大對象堆

    現(xiàn)在是談?wù)摯髮ο蟮暮脮r機,也就是LOH(大對象堆)。到目前為止,我們已經(jīng)提到了gen0、gen1和gen2,以及用戶代碼總是在gen0中分配對象。實際上,如果對象太大,這并不正確 - 它們會被分配到堆的另一個部分,即LOH。而gen0、gen1和gen2組成了SOH(小對象堆)。

    在某種程度上,你可以認為LOH是一種阻止用戶不小心分配大對象的方式,因為大對象比小對象更容易引入性能挑戰(zhàn)。例如,當運行時默認發(fā)放一個對象時,它保證內(nèi)存被清空。內(nèi)存清空是一個昂貴的操作,如果我們需要清空更多的內(nèi)存,它的成本會更高。也更難找到空間來容納一個更大的對象。

    LOH在內(nèi)部是作為gen3被跟蹤的,但在邏輯上它是gen2的一部分,這意味著LOH只在gen2的GC中被收集。這意味著,如果你代碼經(jīng)常會使用LOH,你就會經(jīng)常觸發(fā)gen2的GC,如果你的gen2也很大,這意味著GC將不得不做大量的工作來執(zhí)行g(shù)en2的GC。

    和其他gen一樣,LOH也有它的分配預(yù)算,當它用完時,與gen0不同,gen2 GC將被觸發(fā),因為LOH只在gen2 GC期間被清理。

    默認情況下,一個對象進入LOH的閾值是>=85000字節(jié)。這可以通過使用GCLOHThreshold配置來調(diào)整更高。LOH也默認不壓縮,除非它在有內(nèi)存限制的容器中運行(容器行為在.NET Core 3.0中引入)。

  • 碎片化(自由對象)是堆大小的一部分

    另一個常見問題是 "我看到gen2有很多自由空間,為什么GC沒有使用這些空間?"。

    答案是,GC正在使用這個空間。我們必須再次回到何時測量堆的大小,但現(xiàn)在我們需要增加另一個維度 - 整理GC vs 清掃GC。

    .NET GC可以執(zhí)行整理或清掃GC。整理是開銷更大的操作,因為GC會移動對象(會發(fā)生內(nèi)存復(fù)制),這意味著它必須更新堆上這些對象的所有引用,但整理可以大大減少堆的大小。清掃GC不進行壓縮,而是將相鄰的死對象凝聚成一個空閑對象,并將這些空閑對象穿到該代的空閑列表中。空閑列表占據(jù)的大小,我們稱之為碎片,也是gen的一部分,因此在我們報告gen和堆的大小時也包括在內(nèi)。雖然在這種情況下,堆的大小并沒有什么變化,但重要的是要明白這個空閑列表是用來容納年輕一代的幸存者的,所以我們要使用空閑空間。

    這里我們將介紹GC的另一個概念 -?并發(fā)的GC與阻塞的GC。

    并發(fā)GC/后臺GC

    我們知道,如果我們以停止托管線程的方式進行GC,可能需要很長的時間,也就是我們所說的完全阻塞式GC。我們不想讓用戶線程暫停那么久,所以大多數(shù)時候,一個完整的GC是并發(fā)進行的,這意味著GC線程與用戶線程同時運行,在GC的大部分時間里(一個并發(fā)的GC仍然需要暫停用戶線程,但只是短暫的暫停)。目前.NET中的并發(fā)GC風(fēng)格被稱為后臺GC,或簡稱BGC。BGC只進行清掃。也就是說,BGC的工作是建立一個第二代自由列表來容納第一代的幸存者。短暫的GC總是作為阻塞的GC來做,因為它們足夠短。

    現(xiàn)在我們再來思考一下 "何時測量 "的問題。當我們做一個BGC時,在該GC結(jié)束時,一個新的自由列表被建立起來。隨著第一代GC的運行,他們將使用這個自由列表的一部分來容納他們的幸存者,所以列表的大小將變得越來越小。因此,當你說 "我看到gen2有很多空閑空間 "時,如果那是在BGC剛剛發(fā)生的時候,或者剛剛發(fā)生不久的時候,那是正常的。如果到了我們做下一次BGC的時候,gen2中總是有很多空閑空間,這意味著我們做了那么多工作來建立一個空閑列表,但它并沒有被使用多少,這就是一個真正的性能問題。我已經(jīng)在一些場景中看到了這種情況,我們正在建立一個解決方案,使我們能夠進行最佳的BGC觸發(fā)。

    Pinning 再次增加了碎片的復(fù)雜性,我們將在釘住章節(jié)中談及。

  • GC堆的物理表示

    我們一直在討論如何正確地測量GC堆的大小,但是GC堆在內(nèi)存中到底是什么樣子的,也就是說,GC堆是如何物理組織的?

    GC像其他Win32程序一樣通過VirtualAlloc和VirtualFreeAPI來獲取和釋放虛擬內(nèi)存(在Linux上通過mmap/munmap完成)。GC對虛擬內(nèi)存進行的操作有以下幾點

    當GC堆被初始化時,它為SOH保留了一個初始段,為LOH保留了另一個初始段,并且只在每個段的開頭提交幾個頁面來存儲一些初始信息。

    當分配發(fā)生在這個段上時,內(nèi)存會根據(jù)需要被提交。對于SOH來說,由于只有一個段,gen0、gen1和gen2此時都在這個段上。要記住的一個不變因素是,兩個短暫的gen,即gen0和gen1,總是生活在同一個段上,這個段被稱為短暫段,這意味著合并的短暫gen永遠不會比一個段大。如果SOH的增長超過了一個段的容量,在GC期間將獲得一個新的段。gen0和gen1所在的段是新的短暫段,另一個段現(xiàn)在變成了gen2段。這是在GC期間完成的。LOH是不同的,因為用戶的分配會進入LOH,新的段是在分配時間內(nèi)獲得的。因此,GC堆可能看起來像這樣(在段的末尾可能有未使用的空間,用白色空間表示):

    圖. 2 - GC堆的段

    隨著GC的發(fā)生和內(nèi)存回收,當段上沒有發(fā)現(xiàn)活對象時,段就會被釋放;段空間的末端(即段上最后一個活對象的末端,直到段的末端)被取消提交,除了短暫的段。

對短暫段的特殊處理

對于短暫段,我們保留GC后提交的最后一個實時對象之后的空間,因為我們知道gen0分配將立即使用這個空間。因為我們要分配的內(nèi)存量是gen0的預(yù)算,所以提交的空間量就是gen0的預(yù)算。這回答了另一個常見問題 - "為什么GC提交的內(nèi)存比堆的大小多?"。這是因為提交的字節(jié)包括gen0預(yù)算部分,而如果你碰巧在GC發(fā)生后不久看一下堆,它還沒有消耗大部分的空間。特別是當你有服務(wù)器GC時,它可能有相當大的gen0預(yù)算;這意味著這個差異可能很大,例如,如果有32個堆,每個堆有50MB的gen0預(yù)算,你在GC后馬上看堆的大小,你看到的大小會比提交的字節(jié)少(32 * 50 = 1.6 GB)。

請注意,在.NET 5中,取消提交的行為發(fā)生了變化,我們可以留下更多的內(nèi)存,因為我們想把gen1也納入GC的考慮。另外,服務(wù)器GC的取消提交現(xiàn)在是在GC暫停之外完成的,所以GC結(jié)束時報告的部分內(nèi)容可能會被取消提交。這是一個實現(xiàn)細節(jié)--使用gen0的預(yù)算通常仍然是一個非常好的近似值,可以確定投入的部分是多少。

按照上面的例子,在gen2 GC之后,堆可能看起來是這樣的(注意這只是一個例子說明)。

圖3 - gen2 GC后的GC堆段

在gen0的GC之后,由于它只能收集gen0的空間,我們可能會看到這個:

圖4 - gen0 GC后的GC堆段

大多數(shù)時候,你不必關(guān)心GC堆被組織成段的事實,除了在32位上,因為虛擬地址空間很小(總共2-4GB),而且可能是碎片化的,甚至當你要求分配一個小對象時,你可能得到一個OOM,因為我們需要保留一個新的段。在64位平臺上,也就是我們大多數(shù)客戶現(xiàn)在使用的平臺上,有大量的虛擬地址空間,所以預(yù)留空間不是一個問題。而且在64位平臺上,段的大小要大得多。

  • GC自己的記賬

    很明顯,GC也需要做自己的記賬工作,這就需要消耗內(nèi)存 - 這大約是GC堆大小的1%。最大的消耗是由于啟用了并行GC,這是默認的。準確地說,并發(fā)的GC記賬與堆的儲備大小成正比,但其余的記賬實際上與堆的范圍成正比。由于這是1%,你需要關(guān)心它的可能性極低。

  • 什么時候GC會拋出一個OOM異常?

    幾乎所有人都聽說過或遇到過OOM異常。GC究竟什么時候會拋出一個OOM異常呢?在拋出OOM之前,GC確實非常努力。因為GC大多做短暫的GC,這意味著堆的大小往往不是最小的,這是設(shè)計上的。然而,GC通常會嘗試一個完全阻塞的GC,并在拋出OOM之前驗證它是否仍然不能滿足分配請求。但也有一個例外,那就是GC有一個調(diào)整啟發(fā)式,說它不會繼續(xù)嘗試完全阻塞的GC,如果它們不能有效地縮小堆的大小。它將嘗試一些gen1 GCs和完全阻塞的GCs混合在一起。所以你可能會看到一個OOM拋出,但拋出它的GC并不是一個完全阻塞的GC。

了解GC暫停,即何時觸發(fā)GC以及GC持續(xù)多長時間

當人們研究 GC 暫停問題時,我總是問他們是否關(guān)心總暫停和/或單個暫停。總暫停是由 "GC中的%暫停時間 "來表示的,每次GC被觸發(fā),暫停都會被加到總暫停中。通常情況下,你關(guān)心這個是出于吞吐量的原因,因為你不希望GC過多地暫停你的代碼,以至于把吞吐量降低到可接受的程度。單個暫停表示單個GC持續(xù)的時間。除了作為總暫停的一部分,你關(guān)心單個暫停的一個原因通常是為了請求的尾部延遲--你想減少長的GC以消除或減少它們對尾部延遲的影響。

  • 單個GC的持續(xù)時間

    .NET的GC是一個引用追蹤式GC,這意味著GC需要通過各種根(例如,堆棧定位,GC處理表)去追蹤,以找出哪些對象應(yīng)該是活的。因此,GC的工作量與有多少對象在內(nèi)存中存活成正比。一個GC持續(xù)的時間與GC的工作量大致成正比。我們將在本文檔的后面更多地討論根的問題。

    對于阻塞式GC來說,由于它們在整個GC期間暫停用戶線程,所以GC持續(xù)的時間與GC暫停的時間相同。對于BGC,它們可以持續(xù)相當長的時間,但暫停時間要小得多,因為GC主要是以并發(fā)的方式工作。

    注意,我說過GC的持續(xù)時間與GC的工作量大致成正比。為什么是大致?GC需要像其他東西一樣分享機器上的核心。對于阻塞式GC,當我們說 "GC暫停用戶線程 "時,我們實際上是指 "執(zhí)行托管代碼的線程"。執(zhí)行本地代碼的線程可以自由運行(盡管需要等待GC結(jié)束,如果它們需要在GC仍在進行的時候返回到托管代碼)。最后,不要忘了,在線程運行時,其他進程由于GC的原因暫停了你的進程。

    這就是我們引入的另一個概念,即GC的不同主要類型--工作站GC?vs?服務(wù)器GC(簡稱WKS GC vs SVR GC)。

    服務(wù)器GC

    顧名思義,它們分別用于工作站(即客戶端)和服務(wù)器的工作負載。工作站工作負載意味著你與許多其他進程共享機器,而服務(wù)器工作負載通常意味著它是機器上的主導(dǎo)進程,并傾向于有許多用戶線程在這個進程中工作。這兩種GC的主要區(qū)別在于,WKS GC只有一個堆,SVR GC有多少個堆取決于機器上有多少邏輯核心,也就有和邏輯核心相同數(shù)量的GC線程進行GC工作。到目前為止,我們介紹的所有概念都適用于每個堆,例如,分配預(yù)算現(xiàn)在是每代每堆,所以每個堆都有自己的gen0預(yù)算。當任何一個堆的gen0分配預(yù)算用完后,就會觸發(fā)GC。上圖中的GC堆段將在每個堆上重復(fù)出現(xiàn)(盡管它們可能包含不同數(shù)量的內(nèi)存)。

    由于2種工作負載的性質(zhì)不同,SVR GC有2個明顯不同的屬性,而WKS GC則沒有。

  • SVR GC線程的優(yōu)先級被設(shè)置為 "THREAD_PRIORITY_HIGHEST",這意味著如果其他線程的優(yōu)先級較低,它就會搶占這些線程,而大多數(shù)線程都是如此。相比之下,WKS GC在觸發(fā)GC的用戶線程上運行GC工作,所以它的優(yōu)先級是該線程運行的任何優(yōu)先級,通常是正常的優(yōu)先級。

  • SVR GC線程與邏輯核心硬性綁定。

    參見MSDN文檔中關(guān)于SVR GC的圖解。既然我們現(xiàn)在談到了服務(wù)器和并發(fā)/后臺GC,你可能會問服務(wù)器GC也有并發(fā)的嗎?答案是肯定的。我再次向你推薦MSDN doc,因為它對Background WKS GC與Background SVR GC有一個明確的說明。

    我們這樣做的原因是,當SVR GC發(fā)生時,我們希望它能夠盡可能快地完成它的工作。雖然這在大多數(shù)情況下確實達到了這個目標,但是它可能會帶來一個你應(yīng)該注意的復(fù)雜情況 - 如果在SVR GC發(fā)生的同時,有其他線程也以THREAD_PRIORITY_HIGHEST或更高的速度運行,它們會導(dǎo)致SVR GC花費更長的時間,因為每個GC線程只在其各自的核心上運行(我們將在后面的章節(jié))看到如何診斷長GC的問題。而這種情況通常非常罕見,但是有一個注意事項,那就是當你在同一臺機器上有多個使用SVR GC的進程時。在運行時的早期,這種情況很少見,但是隨著這種情況越來越少,我們創(chuàng)建了一些配置,允許你為使用SVR GC的進程指定更少的GC堆/線程。這些配置的解釋是這里。

    我見過一些人故意把一個大的服務(wù)器進程分成多個小的進程,這樣每個進程都會有一個較小的堆,通過使用堆數(shù)較少的服務(wù)器GC。他們用這種方式取得了更好的效果(更小的堆意味著更短的暫停時間,如果它確實需要做完全阻塞的GC的話)。這是一個有效的方法,但當然只能在有意義的情況下使用它 - 對于某些應(yīng)用來說,將一個進程分成多個進程是非常尷尬的。

  • 多長時間觸發(fā)一次GC?

    如前所述,當gen0的分配預(yù)算用完時,就會觸發(fā)GC。當一個GC被觸發(fā)時,發(fā)生的第一步是我們決定這個GC將是哪一代。在工具那一章節(jié),我們將看到哪些原因會導(dǎo)致GC從gen0升級到可能的gen1或gen2,但其中的一個主要因素是gen1和gen2的分配預(yù)算。如果我們檢測到gen2的分配預(yù)算已經(jīng)用完,我們就會把這個GC升級到完全的GC。

    因此,"多長時間觸發(fā)一次GC "的答案是由gen0/LOH預(yù)算耗盡的頻率決定的,而gen1或gen2的GC被觸發(fā)的頻率主要由gen1和gen2的預(yù)算耗盡的頻率決定。你自然會問 "那么預(yù)算是如何計算的?"。預(yù)算主要是根據(jù)我們看到的那一代的存活率來計算的。存活率越高,預(yù)算就越大。如果GC收集了一代對象并發(fā)現(xiàn)大多數(shù)對象都存活了,那么這么快再收集它就沒有意義了,因為GC的目標是回收內(nèi)存。如果GC做了所有這些工作,而能回收的內(nèi)存卻很少,那么它的效率就會非常低。

    這如何轉(zhuǎn)化為觸發(fā)GC的頻率是,如果一個代被頻繁地使用(即,它的存活率很低),它將被更頻繁地收集。這就解釋了為什么我們最頻繁地收集gen0,因為gen0是用于非常臨時的對象,其存活率非常低。根據(jù)代際假說,對象要么活得很久,要么很臨時,gen2持有長壽的對象,所以它們被收集的次數(shù)最少。

    如前所述,在高內(nèi)存負載情況下,我們會更積極地觸發(fā)gen2阻塞式GC。當內(nèi)存負載很高的時候,我們傾向于做完全阻塞的GC,這樣我們就可以進行整理。雖然BGC對暫停時間有好處,但它對縮小堆沒有好處,而當GC認為它的內(nèi)存不足時,縮小堆就更重要了。

    當內(nèi)存負載不高時,我們做完全阻塞的GC的另一個原因是當gen2碎片非常高時,GC認為大幅減少堆的大小是有成效的。如果這對你來說是不必要的(即你有足夠的可用內(nèi)存),而且你寧愿避免長時間的停頓,你可以將延遲模式設(shè)置為SustainedLowLatency,告訴GC只在必須的時候做全阻塞的GC。

    要記住的一條規(guī)則

    那是很多材料,但如果我們把它總結(jié)為一條規(guī)則,這就是我在談?wù)揋C被觸發(fā)的頻率和單個GC持續(xù)的時間時總是告訴人們的事情。

    存活的對象數(shù)量通常決定了GC需要做多少工作;不存活的對象數(shù)量通常決定了GC被觸發(fā)的頻率

    下面是一些極端的例子,當我們應(yīng)用這一規(guī)則時-

    情況1 - gen0根本沒有任何存活對象。這意味著gen0的GC被頻繁地觸發(fā)。但是單次gen0的暫停時間非常短,因為基本上沒有工作要做。

    情況2 - 大部分gen2對象都存活。這意味著gen2的GC被觸發(fā)的頻率很低。對于單個gen2的暫停,如果GC作為阻塞GC進行,那暫停時間會非常長;如果作為BGC進行,會持續(xù)很長時間(但暫停時間仍然很短)。

    你不能處于分配率和生存率都很高的情況下 - 你會很快耗盡內(nèi)存。

    是什么使一個對象得以存活

    從GC的角度來看,它被各種運行時組件告知哪些對象應(yīng)該存活。它并不關(guān)心這些對象是什么類型;它只關(guān)心有多少內(nèi)存可以存活,以及這些對象是否有引用,因為它需要通過這些引用來追蹤那些也應(yīng)該存活的子對象。我們一直在對GC本身進行改進,以改善GC暫停,但作為一個寫托管代碼的人,知道是什么讓對象存活下來是一個重要的方法,你可以通過它來改善你這邊的個別GC暫停。

    1. 分代方面

    我們已經(jīng)談到了分代GC的效果,所以第一條規(guī)則是
    當一個代沒有被回收,這意味著該代的所有對象都是活的。

    因此,如果我們正在收集gen2,代數(shù)方面是不相關(guān)的,因為所有的代數(shù)都會被收集。我收到的一個常見問題是:"我已經(jīng)多次調(diào)用GC.Collect()了,對象還在那里,為什么GC不把它處理掉呢?"。這是因為當你誘導(dǎo)一個完全阻塞的GC時,GC并不參與決定哪些對象應(yīng)該是活的 - 它只會由我們將在下面討論的用戶根(堆棧/GC句柄/等等)告知是否存活,我們將在下面談?wù)摗R虼?#xff0c;這意味著無論什么東西還活著,都是因為它需要活著,而GC根本無法回收它。

    不幸的是,很少有性能工具會強調(diào)生成效應(yīng),盡管這是.NET GC的一個基石。許多性能工具會給你一個堆轉(zhuǎn)儲--有些會告訴你哪些堆棧變量或哪些GC句柄持有對象。你可以擺脫很大比例的GC句柄,但你的GC暫停時間幾乎沒有改善。為什么呢?如果你的大部分GC暫停是由于gen0的GC被gen2中的一些對象持有而造成的,那么如果你設(shè)法擺脫一些gen2的對象,而這些對象并不持有這些gen0的對象,那也是沒有用的。是的,這將減少gen2的工作,但是如果gen2的GC發(fā)生的頻率很低,那就不會有太大的區(qū)別,如果你的目標是減少gen2的GC的數(shù)量,你就不會有什么進展。

    2. 用戶根

    你最有可能聽到的常見類型的根是指向?qū)ο蟮亩褩W兞俊C句柄和終結(jié)器隊列。我把這些稱為用戶根,因為它們來自用戶代碼。由于這些是用戶代碼可以直接影響的東西,所以我將詳細地討論它們。

    • 堆棧變量

      堆棧變量,特別是對于C#程序來說,實際上并沒有被談及很多。原因是JIT也能很好地意識到堆棧變量何時不再被使用。當一個方法完成后,堆棧根保證會消失。但即使在這之前,JIT也能知道什么時候不再需要一個堆棧變量,所以不會向GC報告,即使GC發(fā)生在一個方法的中間。請注意,在DEBUG構(gòu)建中不是這種情況。

    • GC句柄

      GC句柄是一種方式,用戶代碼可以持有一個對象,或者檢查一個對象而不持有它。前者被稱為強柄,后者被稱為弱柄。強句柄需要被釋放,以使它不再保留一個對象,也就是說,你需要在句柄上調(diào)用Free。有一些人給我看了!gcroot(SoS調(diào)試器的一個擴展命令,可以顯示一個對象的根部)的輸出,說有一個強句柄指向一個對象,問我為什么GC還沒有回收這個對象。根據(jù)設(shè)計,這個句柄告訴GC這個對象需要是活的,所以GC不能回收它。目前,以下用戶暴露的句柄類型是強句柄。Strong和Pinned;而弱柄是Weak和WeakTrackResurrection。但是如果你看過SoS的 !gchandles輸出,Pinned句柄也可以包括AsyncPinned。

    釘住

    我在上面提到過幾次釘住。大多數(shù)人都知道釘住是什么 - 它向GC表示一個對象不能被移動。但從GC的角度來看,釘住的意義是什么呢?由于GC不能移動這些被釘住的對象,它使被釘住的對象之前的死角變成了一個自由對象,這個自由對象可以用來容納年輕一代的生存者。但這里有一個問題 - 正如我們從上面的代際討論中看到的,如果我們簡單地將這些被釘住的對象提升到老一代,就意味著這些自由空間也是老一代的一部分,要用它們來容納年輕一代的幸存者,唯一的辦法就是我們真的對年輕一代做一次GC(否則我們甚至沒有 "年輕一代的幸存者")。然而,如果我們能在gen0中保留這些自由空間,它們就可以被用戶分配使用。這就是為什么GC有一個叫做降代的功能,我們將把這些被釘住的對象降代到gen0,這意味著它們之間的空閑空間將是gen0的一部分,當用戶代碼分配時,我們可以立即使用它們。

    圖5 - 降代(我從一張舊的幻燈片上取下來的,所以這看起來與之前的片段圖片有些不同。)

    由于gen0分配可以發(fā)生在這些自由空間中,這意味著它們將消耗gen0預(yù)算而不增加gen0的大小(除非自由空間不能滿足所有的gen0預(yù)算,在這種情況下它將需要增長gen0)。

    然而,GC 不會無條件地降代,因為我們不想在 gen0 中留下許多固定對象,這意味著我們必須在每次 GC 中再次查看它們,可能會有很多次 GC(因為它們是 gen0 的一部分,每當我們執(zhí)行 gen0 GC 我們需要查看它們)。這意味著如果您遇到嚴重的固定情況,它仍然會導(dǎo)致 gen2 中的碎片問題。同樣,GC 確實有機制來應(yīng)對這些情況。但是如果你想對 GC 施加更少的壓力,你可以從用戶的 POV 中遵循這個規(guī)則—

    早點釘住對象,分批釘住對象

    我們的想法是,如果你把對象釘在已經(jīng)整理的那部分堆里,意味著這些對象已經(jīng)不需要移動了,所以碎片化就不是問題。如果你以后確實需要釘住,通常的做法是分配一批緩沖區(qū),然后把它們釘在一起,而不是每次都分配一個并釘住它。在.NET 5中,我們引入了一個名為POH(Pinned Object Heap(固定堆))的新特性,允許你告訴GC在分配時將釘住的對象放在一個特定的堆上。因此,如果你有這樣的控制權(quán),在POH上分配它們將有助于緩解碎片化問題,因為它們不再散落在普通堆上。

    • 終結(jié)器

    終結(jié)隊列是另一個根來源。如果你已經(jīng)寫了一段時間的.NET應(yīng)用程序,你有可能聽說過終結(jié)器是你需要避免的東西。然而,有時終結(jié)器并不是你的代碼,而是來自你所使用的庫。由于這是一個非常面向用戶的特性,我們來詳細了解一下。下面是終結(jié)器的基本性能含義 -

    分配

    · 如果你分配了一個可終結(jié)的對象(意味著它的類型有一個終結(jié)器),就在GC返回到VM端的分配助手之前,它將把這個對象的地址記錄在終結(jié)隊列中。

    · 有一個終結(jié)者意味著你不能再使用快速分配器進行分配,因為每個可終結(jié)的對象的分配都要到GC去注冊。

    然而,這種成本通常是不明顯的,因為你不太可能分配大部分可終結(jié)的對象。更重要的成本通常來自于GC實際發(fā)生的時間,以及在GC期間如何處理可終結(jié)的對象。

    回收

    當GC發(fā)生時,它將發(fā)現(xiàn)那些仍然活著的對象,并對它們升代。然后它將檢查終結(jié)隊列中的對象,看它們是否被升代 - 如果一個對象沒有被升代,就意味著它已經(jīng)死了,盡管它不能被回收(見下一段的原因)。如果你在被收集的幾代中有成噸的可終結(jié)的對象,僅這一成本就可能是明顯的。比方說,你有一大堆被提升到gen2的可終結(jié)對象(只是因為它們一直在存活),而你正在做大量的gen2 GC,在每個gen2 GC中,我們需要花時間來掃描所有的可終結(jié)對象。如果你很不頻繁地做gen2 GC,你就不需要支付這個成本。

    這里就是你聽到 "終結(jié)器不好 "的原因了 - 為了運行GC已經(jīng)發(fā)現(xiàn)的這個對象的終結(jié)器,這個對象需要是存活的。由于我們的GC是一代一代的,這意味著它將被提升到更高的一代,正如我們上面所談到的,這反過來意味著它將需要一個更高的一代GC,也就是說,一個更昂貴的GC來收集這個對象。因此,如果一個可終結(jié)的對象在第一代GC中被發(fā)現(xiàn)死亡,它將需要等到下一次做第二代GC時才會被收集,而這可能是相當長的一段時間。也就是說,這個對象的內(nèi)存的回收可能會被推遲很多。

    然而,如果你用GC.SuppressFinalize來抑制終結(jié)器,你告訴GC的是你不需要運行這個對象的終結(jié)器。所以GC就沒有理由去提升(升代)它。當GC發(fā)現(xiàn)它死亡時,它將被回收。

    運行終結(jié)器

    這是由終結(jié)器線程處理的。在GC發(fā)現(xiàn)死的、可終結(jié)的對象(然后被升代)后,它將其移至終結(jié)隊列的一部分,告訴終結(jié)者線程何時向GC請求運行終結(jié)者,并向終結(jié)者線程發(fā)出信號,表示有工作要做。在GC完成后,終結(jié)器線程將運行這些終結(jié)器。被移到終結(jié)隊列這一部分的對象被說成是 "準備好終結(jié)了"。你可能已經(jīng)看到各種工具提到了這個術(shù)語,例如,sos的 !finalizequeue命令告訴你finalize隊列的哪一部分儲存了準備好的對象,像這樣:

    Ready for finalization 0 objects (000002E092FD9920->000002E092FD9920)

    您經(jīng)常會看到這是 0,因為終結(jié)器線程以高優(yōu)先級運行,因此終結(jié)器將快速運行(除非它們被某些東西阻塞)。

    下圖說明了2個對象以及可最終確定的對象F是如何演變的。正如你所看到的,在它被提升到gen1之后,如果有一個gen0的GC,F仍然是活的,因為gen1沒有被收集;只有當我們做一個gen1的GC時,F才能真正成為死的,我們看一下F所處的代。

    圖 6 - O 是不可終結(jié)的,F 是可終結(jié)的

    3. 托管內(nèi)存泄漏

    現(xiàn)在我們了解了不同類別的根,我們可以談?wù)劰芾硇詢?nèi)存泄漏的定義了

    托管內(nèi)存泄漏意味著你至少有一個用戶根,隨著進程的運行,直接或間接地引用了越來越多的對象。這是一個泄漏,因為根據(jù)定義,GC不能回收這些對象的內(nèi)存,所以即使GC盡了最大努力(即做一個全堆阻塞的GC),堆的大小最終還是會增長。

    所以最簡單的方法,如果可行的話,識別你是否有托管內(nèi)存泄漏,就是在你知道你應(yīng)該有相同的內(nèi)存使用量的時候,簡單地誘導(dǎo)全阻塞GC(例如,在每個請求結(jié)束時),并驗證堆的大小沒有增長。顯然,這只是一種幫助調(diào)查內(nèi)存泄漏的方法--當你在生產(chǎn)中運行你的應(yīng)用程序時,你通常不希望誘發(fā)全阻塞的GCs。

    • “主線GC場景”?vs “非主線”

    如果你有一個程序只是使用堆棧并創(chuàng)建一些對象來使用,GC已經(jīng)優(yōu)化了很多年了。基本上是 "掃描堆棧以獲得根部,并從那里處理對象"。這就是許多GC論文所假設(shè)的主線GC方案,也是唯一的方案。當然,作為一個已經(jīng)存在了幾十年的商業(yè)產(chǎn)品,并且必須滿足各種客戶的要求,我們還有一堆其他的東西,比如GC句柄和終結(jié)器。需要了解的是,雖然多年來我們也對這些東西進行了優(yōu)化,但我們的操作是基于 "這些東西不多 "的假設(shè),這顯然不是對每個人都是如此。因此,如果你確實有很多這樣的東西,那么如果你在診斷內(nèi)存問題時,就值得關(guān)注了。換句話說,如果你沒有任何內(nèi)存問題,你不需要關(guān)心;但如果你有(例如,在GC時間百分比高),它們是值得懷疑的好東西。

    • **完全不做 GC 的部分 GC 暫停—線程掛起 **

    我們沒有提到的GC暫停的最后一個部分是根本不做GC工作的部分--我指的是運行時中的線程暫停機制。GC調(diào)用暫停機制,讓進程中的線程在GC工作開始前停止。我們調(diào)用這個機制是為了讓線程到達它們的安全點。因為GC可能會移動對象,所以線程不能在隨機的點上停止;它們需要在運行時知道如何向GC報告對GC堆對象的引用的點上停止,這樣GC才能在必要時更新它們。這是一個常見的誤解,認為GC在做暫停工作--GC只是調(diào)用暫停機制來讓你的線程停止。然而暫停被報告為GC暫停的一部分,因為GC是使用它的主要組件。

    我們談到了并發(fā)與阻塞的GC,所以我們知道阻塞的GC會讓你的線程在GC期間保持暫停狀態(tài),而BGC(并發(fā)的味道)會讓它們在短時間內(nèi)暫停,并在用戶線程運行時做大部分的GC工作。不太常見的是,讓線程進入暫停狀態(tài)可能需要一段時間。大多數(shù)情況下這是非常快的,但是緩慢的暫停是一類與管理內(nèi)存相關(guān)的性能問題,我們將專門討論如何診斷這些問題。

    注意,在GC的暫停部分,只有運行托管代碼的線程被暫停。運行本地代碼的線程可以自由運行。然而,如果它們需要在這樣的暫停部分返回到托管代碼,它們將需要等待,直到暫停部分結(jié)束。

    知道什么時候該擔(dān)心

    與任何性能調(diào)查一樣,首要的是弄清楚你是否應(yīng)該擔(dān)心這個問題。

    頂層應(yīng)用指標

    如上所述,關(guān)鍵是要有性能目標?- 這些應(yīng)該由一個或多個頂級應(yīng)用指標來表示。它們是應(yīng)用指標,因為它們直接告訴你應(yīng)用的性能方面的數(shù)據(jù),例如,你處理的并發(fā)請求數(shù),平均、最大和/或P95請求延遲。

    使用頂級應(yīng)用指標來表明你在開發(fā)產(chǎn)品時是否有性能退步或改進,這是相對容易理解的,所以我們不會在這里花太多時間。但有一點值得指出的是,有時要讓這些指標穩(wěn)定到有一個月到一個月的趨勢,甚至一天到一天的趨勢并不容易,原因很簡單,因為工作負載并不是每天都保持不變,特別是對尾部延遲的測量。我們?nèi)绾谓鉀Q這個問題呢?

    · 這正是衡量能影響它們的因素的重要原因之一。當然,你很可能在前期不知道所有的因素。當你知道得越多,你就可以把它們加入到你要測量的東西的范圍內(nèi)。

    · 有一些頂級的組件指標,幫助你決定工作負載中有多少變化。對于內(nèi)存,一個簡單的指標是做了多少分配。如果在今天的高峰時段,你的分配量是昨天的兩倍,你就知道這表明今天的工作負荷也許給GC帶來了更大的壓力(分配量絕對不是影響GC暫停的唯一因素,見上面的GC暫停一節(jié))。然而,有一個原因使得這成為一個受歡迎的追蹤對象,因為它與用戶代碼直接相關(guān)--你可以在一行代碼中看到分配何時發(fā)生,而將GC與一行代碼關(guān)聯(lián)起來則比較困難。

    頂層的GC指標

    既然你在閱讀本文檔,顯然你關(guān)心的組件之一就是GC。那么,你應(yīng)該跟蹤哪些頂層的GC指標,以及如何決定何時應(yīng)該擔(dān)心?

    我們提供了許多不同的GC指標,你可以測量 - 顯然你不需要關(guān)心所有的指標。事實上,要確定你是否/何時應(yīng)該開始擔(dān)心GC,你只需要一到兩個頂級的GC指標。表3列出了哪些頂級GC指標是基于你的性能目標相關(guān)的。如何收集這些指標將在[后面的章節(jié)]中描述(#如何收集頂層的GC指標)。

    表格3

    Application perf goal?應(yīng)用性能目標Relevant GC metrics?相關(guān)的GC指標
    Throughput 吞吐量% Pause time in GC (maybe also % CPU time in GC) 在GC中暫停時間的百分比(也許還有GC中CPU時間的百分比)
    Tail latency 尾部延時Individual GC pauses 個別的GC停頓
    Memory footprint 內(nèi)存占用率GC heap size histogram GC堆大小直方圖

    何時應(yīng)擔(dān)心GC

    如果你理解了GC基本原理,那么GC行為是由應(yīng)用行為驅(qū)動的,這一點應(yīng)該是非常明顯的。頂層的應(yīng)用指標應(yīng)該告訴你什么時候出現(xiàn)了性能問題。而GC指標可以幫助你對這些性能問題進行調(diào)查。例如,如果你知道你的工作負載在一天中長時間處于休眠狀態(tài),那么你看一天中 "GC中暫停時間百分比 "指標的平均值是沒有意義的,因為 "GC中暫停時間百分比 "的平均值會非常小。看這些GC指標的一個更合理的方法是:"我們在X點左右發(fā)生了故障,讓我們看一下那段時間的GC指標,看看GC是否可能是故障的原因"。

    當相關(guān)的GC指標顯示GC的影響很小的時候,把你的精力放在其他地方會更有成效。如果它們表明GC確實有很大的影響,這時你應(yīng)該開始擔(dān)心如何進行內(nèi)存管理分析,這就是本文檔的大部分內(nèi)容。

    讓我們詳細看看每個目標,以了解為什么你應(yīng)該看他們相應(yīng)的GC指標 -

    吞吐量

    為了提高你的吞吐量,你希望GC盡可能少地干擾你的線程。GC會在以下兩個方面進行干擾

    · GC可以暫停你的線程 - 阻塞的GC會在整個GC期間暫停它們,BGC會暫停一小段時間。這種暫停由 "GC中的%暫停時間(% Pause time in GC)"來表示。

    · GC線程會消耗CPU來完成工作,雖然BGC不會讓你的線程暫停太多,但它確實會與你的線程競爭CPU。所以還有一個指標叫做 "GC花費的CPU時間%(% CPU time in GC)"。

    這兩個數(shù)字可能有很大差別。"GC中的暫停時間百分比 "的計算方法是

    線程被GC暫停時的耗時/進程的總耗時

    因此,如果從進程開始到現(xiàn)在已經(jīng)10s了,線程由于GC而暫停了1s,那么GC中的暫停時間百分比就是10%。

    即使BGC不在其中,GC中的CPU時間百分比也可能多于或少于GC中的暫停時間百分比,因為這取決于CPU在進程中被其他事物使用的情況。當GC正在進行時,我們希望看到它盡可能快地完成;所以我們希望看到它在執(zhí)行期間有盡可能高的CPU使用率。這曾經(jīng)是一個非常令人困惑的概念,但現(xiàn)在似乎發(fā)生得更少了。我曾經(jīng)收到過一些擔(dān)心的人的報告,說 "當我看到一個服務(wù)器GC時,它使用了100%的CPU! 我需要減少這個!"。我向他們解釋說,這實際上正是我們希望看到的--當GC暫停了你的線程時,我們希望能使用所有的CPU,這樣我們就能更快地完成GC工作。假設(shè)GC的暫停時間為10%,在GC暫停期間,CPU使用率為100%(例如,如果你有8個核心,GC會完全使用所有8個核心),在GC之外,你的線程的CPU使用率為50%,并且沒有BGC發(fā)生(意味著GC只在你的線程暫停時做工作),那么GC的CPU時間將為

    (100% * 10%) / (100% * 10% + 50% * 90%) = 18%

    我建議首先監(jiān)測GC中的%暫停時間,因為它的監(jiān)測開銷很低,而且是一個很好的衡量標準,可以確定你是否應(yīng)該把GC作為一個最高級別的指標來關(guān)注。監(jiān)測GC中的CPU時間百分比的成本較高(需要實際收集CPU樣本),而且通常沒有那么關(guān)鍵,除非你的應(yīng)用程序正在做大量的BGC,而且CPU真的飽和了。

    通常情況下,一個行為良好的應(yīng)用程序在GC中的暫停時間小于5%,而它正在積極處理工作負載。如果你的應(yīng)用程序的暫停時間是3%,那么你把精力放在GC上就沒有什么成效了--即使你能去掉一半的暫停時間(這很困難),你也不會使總的性能提高多少。

    尾部延時

    之前我們討論了如何考慮測量導(dǎo)致你的尾部延遲的因素。如果尾部延遲是你的目標,除了其他因素外,GC或最長的GC可能發(fā)生在那些最長的請求中。因此,測量這些單獨的GC暫停是很重要的,看看它們是否/在多大程度上導(dǎo)致了你的延遲。有一些輕量級的方法可以知道一個單獨的GC暫停何時開始和結(jié)束,我們會看到在本文檔后面。

    內(nèi)存占用率

    如果你還沒有正確閱讀GC堆只是你進程中的一種內(nèi)存使用情況,以及如何測量GC heap size,我強烈建議你現(xiàn)在就去做。實際上,一個被管理的進程在GC堆之外還有明顯的甚至是大量的內(nèi)存使用,這并不罕見,所以了解是否是這樣的情況很重要。如果GC堆在整個進程的內(nèi)存使用中只占很小的比例,那么你專注于減少GC堆的大小就沒有意義了。

    挑選正確的工具和解釋數(shù)據(jù)

    性能工具一覽

    我怎么強調(diào)挑選正確工具的重要性都不為過。我經(jīng)常看到一些人花了很多時間(有時是幾個月)試圖弄清一個問題,因為他們沒有發(fā)現(xiàn)正確的工具和/或如何使用它。這并不是說即使有了正確的工具,你也不需要花費一些精力--有時只需要一分鐘,但其他時候可能需要許多分鐘或幾個小時。

    挑選合適的工具的另一個挑戰(zhàn)是,除非你在做一個基本的分析,否則根本沒有很多工具可以選擇。也就是說,有更多的工具能夠解決簡單的問題,所以如果你正在解決其中的一個問題,你選擇哪一個并不那么重要。例如,如果你有一個嚴重的托管內(nèi)存泄漏,你可以在你的開發(fā)環(huán)境中重現(xiàn),你將能夠很容易地找到一個能夠比較堆快照的工具,這樣你就可以看到哪些對象存活了,而不應(yīng)該。你很有可能通過這種方式來解決你的問題。你不需要關(guān)心像何時測量堆的大小這樣的事情,就像我們在"如何正確測量GC堆的大小?"部分廣泛談?wù)摰哪菢印?/p>

    我們使用的工具以及它是如何完成工作的

    運行時團隊制作的、我經(jīng)常使用的工具是PerfView?- 你們中的很多人可能都聽說過它。但我還沒有看到很多人充分使用它。PerfView的核心是使用TraceEvent,這是一個解碼ETW(Event Tracing for Windows)事件的庫,它來自運行時提供者、內(nèi)核提供者和其他一些提供者。如果你以前沒有接觸過ETW事件,你可以把它們看作是各種組件隨著時間的推移所發(fā)出的數(shù)據(jù)。它們具有相同的格式,所以來自不同組件的事件可以被那些知道如何解釋ETW事件的工具放在一起看。這對香水調(diào)查來說是一個非常強大的東西。在ETW術(shù)語中,事件按提供者(例如,運行時提供者)、關(guān)鍵字(例如,GC)和粗略程度(例如,通常意味著輕量級的Informational和通常更重的Verbose)進行分類。使用ETW的成本與你所收集的事件量成正比。在GC信息級別,開銷非常小,如果你需要,你可以一直收集這些事件。

    由于.NET Core是跨平臺的,而ETW在Linux上并不存在,我們有其他的事件機制,旨在使這個過程透明,如LTTng或EventPipe。因此,如果你使用TraceEvent庫,你可以在Linux上獲得這些事件的信息,就像你在Windows上使用ETW事件那樣。然而,有不同的工具可以在Linux上收集這些事件。

    PerfView中的另一個功能,我不太經(jīng)常使用,但作為GC的用戶,你可能更經(jīng)常使用,那就是堆快照,即顯示堆上有哪些對象,它們之間是如何連接的。我不經(jīng)常使用它的原因是,GC并不關(guān)心對象的類型。

    你可能也用過我們的調(diào)試器擴展SoS。我很少用SoS來分析性能,因為它更像是一個調(diào)試工具,而不是剖析工具。它也不怎么看GCs,主要是看堆,即堆統(tǒng)計和轉(zhuǎn)儲單個對象。

    在本節(jié)的其余部分,我將向你展示如何用PerfView的正確方式進行內(nèi)存分析。我將多次引用內(nèi)存基礎(chǔ)來解釋為什么要用這種方式進行分析,這樣做是有意義的,而不是讓你記住我們在這里做什么。

    如何開始進行內(nèi)存性能分析

    當你開始進行內(nèi)存性能分析時,這些步驟是否聽起來很熟悉?

  • 捕獲一個CPU采樣文件,看看你是否可以減少任何高開銷的方法的CPU

  • 在一個工具中打開一個堆快照,看看你能擺脫什么?

  • 捕獲內(nèi)存分配,看看你能擺脫什么?

  • 根據(jù)你要解決的問題,這些可能是有缺陷的。比方說,你有一個尾部延遲的問題,你正在做1)。你可能正在尋找是否可以減少你的代碼中的CPU使用率,或者是一些庫代碼。但是,如果你的尾部延遲受到長GC的影響,減少這些就不太可能影響你的長GC情況。

    處理問題的有效方法是推理出有助于實現(xiàn)性能目標的因素,然后從那里開始。我們談到了對不同性能目標有貢獻的頂級GC指標。我們將討論如何收集它們,并看看我們?nèi)绾畏治雒恳粋€指標。

    本文檔的大多數(shù)讀者已經(jīng)知道如何收集與內(nèi)存有關(guān)的一般指標,所以我將簡要介紹一下。由于我們知道GC堆只是進程中內(nèi)存使用的一部分,但GC知道物理內(nèi)存負載,我們想測量進程的內(nèi)存使用和機器的物理內(nèi)存負載。在Windows上,你可以通過收集以下性能計數(shù)器來實現(xiàn)這一目標。

    Memory\Available MBytes(內(nèi)存\可用內(nèi)存 單位MB)
    Process\Private Bytes(進程\私有內(nèi)存占用 單位MB)

    對于一般的CPU使用率,你可以監(jiān)控

    Process\% Processor time(進程\占用處理器時間百分比)

    計數(shù)器。調(diào)查CPU時間的一個常見做法是,每隔一段時間就進行一次CPU抽樣調(diào)查(例如,有些人可能每小時做一次,每次一分鐘),并查看匯總的CPU堆棧。

    如何收集頂層的GC指標

    GC發(fā)出輕量級的信息級事件,你可以收集(如果你愿意,可以一直開著),涵蓋所有頂級的GC指標。使用PerfView的命令行來收集這些事件 -

    perfview /GCCollectOnly /AcceptEULA /nogui collect

    完成后,在perfview cmd窗口中按下s來停止它。

    這應(yīng)該運行足夠長的時間來捕獲足夠多的GC活動,例如,如果你知道問題發(fā)生的時間,這應(yīng)該涵蓋問題發(fā)生前的時間(不僅僅是在問題發(fā)生的時間)。如果你不確定問題何時開始發(fā)生,你可以讓它開很長時間。

    如果你知道要運行多長時間的集合,可以使用下面的方法(實際上這個方法用得更多)。

    perfview /GCCollectOnly /AcceptEULA /nogui /MaxCollectSec:1800 collect

    并將1800(半小時)替換為你需要的任何秒數(shù)。當然,你也可以將這個參數(shù)應(yīng)用于其他命令行。這將產(chǎn)生一個名為PerfViewGCCollectOnly.etl.zip的文件。用PerfView的話來說,我們稱之為GCCollectOnly跟蹤。

    在Linux上,有一種類似的方法,就是這個dotnet trace命令行:

    dotnet trace collect -p <pid> -o <outputpath with .nettrace extension> --profile gc-collect --duration <in hh:mm:ss format>

    這算是一種等價的方法,因為它收集了同樣的GC事件,但只針對已經(jīng)啟動的一個進程,而perfview命令行收集的是整個機器的ETW,即該機器上每個進程的GC事件,在收集開始后啟動的進程也會收集到。

    還有其他方法來收集頂級的GC指標,例如,在.NET Framework上,我們有GC perf計數(shù)器;在.NET Core上,也增加了一些GC計數(shù)器。計數(shù)器和事件之間最重要的區(qū)別是,計數(shù)器是抽樣的,而事件則捕獲所有的GC,但抽樣也足夠好。在.NET 5中,我們還添加了一個GC采樣API--

    public static GCMemoryInfo GetGCMemoryInfo(GCKind kind);

    它返回的GCMemoryInfo是基于 "GCKind "的,在GCMemoryInfo.cs中做了解釋。

    /// <summary>指定垃圾收集的種類</summary> /// <remarks> /// GC可以是3種類型中的一種--短暫的、完全阻塞的或后臺的。 /// 它們的發(fā)生頻率是非常不同的。短暫的GC比其他兩種GC發(fā)生的頻率高得多。 /// 其他兩種類型。后臺GC通常不經(jīng)常發(fā)生,而 /// 完全阻塞的GC通常發(fā)生的頻率很低。為了對那些非常 /// 不經(jīng)常發(fā)生的GC,集合被分成不同的種類,因此調(diào)用者可以要求所有三種GC /// 同時保持 /// 合理的采樣率,例如,如果你每秒鐘采樣一次,如果沒有這個 /// 區(qū)分,你可能永遠不會觀察到一個后臺GC。有了這個區(qū)別,你可以 /// 總是能得到你指定的最后一個GC的信息。 /// </remarks> public enum GCKind {/// <summary>任何種類的回收</summary>Any = 0,/// <summary>gen0或gen1會后.</summary>Ephemeral = 1,/// <summary>阻塞的gen2回收.</summary>FullBlocking = 2,/// <summary>后臺GC回收</summary>/// <remarks>這始終是一個gen2回收</remarks>Background = 3 };

    GCMemoryInfo提供了與這個GC相關(guān)的各種信息,比如它的暫停時間、提交的字節(jié)數(shù)、提升的字節(jié)數(shù)、它是壓縮的還是并發(fā)的,以及每一代被收集的信息。請參閱GCMemoryInfo.cs了解完整的列表。你可以調(diào)用這個API,在過程中以你希望的頻率對GCs進行采樣。

    顯示頂級的GC指標

    在PerfView的 "GCStats "視圖中,這些數(shù)據(jù)與你剛剛收集的軌跡一起被方便地顯示。

    在PerfView中打開PerfViewGCCollectOnly.etl.zip文件,即通過運行PerfView并瀏覽到該目錄,雙擊該文件;或者運行 "PerfView PerfViewGCCollectOnly.etl.zip "命令行。你會看到該文件名下有多個節(jié)點。我們感興趣的是 "Memory Group"節(jié)點下的 "GCStats "視圖。雙擊它來打開它。在頂部,我們有這樣的內(nèi)容

    我運行了Visual Studio,它是一個托管應(yīng)用程序--這就是頂部的devenv進程。

    對于每一個進程,你都會得到以下細節(jié)--我對那些命令行的進程添加了注釋。

    Summary?– 這包括像命令行、CLR啟動標志、GC的%暫停時間等。

    GC stats rollup by generation?– 對于gen0/1/2,它有一些東西,如該gen的多少個GCs被完成,它們的平均/平均停頓,等等。

    GC stats for GCs whose pause time was > 200ms?暫停時間大于200ms的GC的統(tǒng)計數(shù)字

    LOH Allocation Pause (due to background GC) > 200 Msec for this process?-?Gen2 GC stats該進程的LOH分配暫停(由于后臺GC)>200Msec?對于大型對象的分配,有一個注意事項,即在BGC進行時,我們不允許過多的LOH分配。如果你在BGC期間分配了太多的對象,你會看到一個表格出現(xiàn)在這里,告訴你哪些線程在BGC期間被阻塞了(以及多久),因為有太多的LOH分配。這通常是一個信號,告訴你減少LOH分配將有助于不使你的線程被阻塞。

    All GC stats?所有GC統(tǒng)計資料

    Condemned reasons for GCsGC被觸發(fā)的原因

    Finalized Object Counts?終結(jié)器對象數(shù)量

    Summary?explanation "摘要 "解釋

    ·?Commandline?self-explanatory. 命令行

    ·?Runtime version運行時版本是相當無用的,因為我們只是顯示一些通用版本。然而,你可以使用事件視圖中的FileVersion事件來獲得你所使用的運行時的確切版本。

    ·?CLR Startup FlagsGC啟動標志, 在GC調(diào)查中,主要是尋找CONCURRENT_GC和SERVER_GC。如果你沒有這兩個標志,通常意味著你使用的是工作站GC,并且禁用了并發(fā)的GC。不幸的是,這不是絕對的,因為在我們捕獲這個事件之后,它可能會被改變。你可以用其他東西來驗證這一點。注意:請注意,目前.NET Core/.NET 5沒有這些標志,所以你在這里不會看到任何東西。

    ·?Total CPU Time and Total GC CPU Time總的CPU時間和總的GC CPU時間這些總是0,除非你真的在收集CPU樣本。

    ·?Total Allocs你在這次追蹤中為這個進程所做的總分配。

    ·?MSec/MB Alloc?是0,除非你收集CPU樣本(它將是GC CPU總時間/分配的總字節(jié)數(shù))。

    ·?Total GC pause被GC停頓的總時間。注意,這包括暫停時間,即在GC開始之前暫停被管理的線程所需的時間。

    ·?% Time paused for Garbage Collection?暫停垃圾收集的時間這是 "GC中暫停時間的%"指標。

    ·?% CPU Time spent Garbage Collecting?花在垃圾收集上的CPU時間%這是 "GC中的CPU時間%"指標。它是NaN%,除非你收集CPU樣本。

    ·?Max GC Heap Size在本次跟蹤過程中,該進程的最大托管堆尺寸。

    · 其余的都是鏈接,我們將在本文件中介紹其中一些。

    所有?All GC stats表 中顯示了在跟蹤收集過程中發(fā)生的每一個GC(如果有太多的話,它會有一個鏈接到?jīng)]有顯示的GC)。在這個表中有很多列。由于這是一個非常廣泛的表格,我將只顯示這個表格中與每個主題有關(guān)的列。

    其他頂級的GC指標,個別暫停和堆大小,作為這個表格的一部分被顯示出來,就像這樣(Peak MB指的是該GC進入時的GC堆大小,After是退出時的大小)。

    GCPausePeakAfter
    IndexMSecMBMB
    8045.7431,796.841,750.63
    8056.9841,798.191,742.18
    8065.5571,794.521,736.69
    8076.7481,798.731,707.85
    8085.4371,798.421,762.68
    8097.1091,804.951,736.88

    現(xiàn)在,這是一個html表格,你不能進行排序,所以如果你確實想進行排序(例如,找出最長的單個GC停頓),你可以點擊每個過程開始時的 "在Excel中查看 "鏈接 --

    ·?Individual GC Events

    o?View in Excel

    這將在Excel中打開上面的表格,所以你可以對你喜歡的任何一列進行排序。在GC團隊中,由于我們需要對數(shù)據(jù)進行更多的切分,我們有自己的性能基礎(chǔ)設(shè)施?,直接使用TraceEvent。

    PerfView中的其他相關(guān)視圖

    除了GCStats視圖之外,介紹PerfView中的其他幾個視圖也很有用,因為我們會用到它們。

    CPU Stacks是你所期望的--如果你在追蹤中收集了CPU的樣本事件,這個視圖就會亮起來。有一點值得一提的是,我總是先清除3個高亮的文本框--在你這樣做之后,你需要點擊更新來刷新。

    我很少發(fā)現(xiàn)這3個文本框有用。偶爾我會想按模塊分組,你可以閱讀PerfView的幫助文檔,看看如何做到這一點。

    Events就是我們上面提到的 - 原始事件視圖。由于是原始的,它可能聽起來很不友好,但它有一些功能,使它相當方便。你可以通過 "過濾器 "文本框過濾事件。如果你需要用多個字符串進行過濾,你可以使用"|"。如果我想獲得所有名稱中帶有file的事件和GC/Start事件,我使用file|GC/Start(沒有空格)。

    雙擊一個事件的名稱會顯示該事件的所有發(fā)生情況,你可以搜索具體細節(jié)。例如,如果我想找出coreclr.dll的確切版本,我可以在查找文本框中輸入coreclr

    然后你可以看到你正在使用的coreclr.dll的確切版本信息。

    我還經(jīng)常使用 "開始/結(jié)束 "來限制事件的時間范圍,使用 "進程過濾器 "將其限制在某個進程中,使用 "顯示列 "來限制要顯示的事件字段(這使我能夠?qū)κ录哪硞€字段進行排序)。

    內(nèi)存組下的GC Heap Alloc Ignore Free是我們要用來查看分配的東西。

    Any Stacks顯示所有的事件和它們的堆棧(如果堆棧被收集)。如果我想看一個特定的事件,但還沒有一個既定的視圖,或者既定的視圖沒有提供我所要的東西,我覺得這很方便。

    對比堆棧視圖

    像CPU堆棧視圖一樣的視圖(即堆快照視圖或GC堆分配視圖)提供了一個Diff-ing功能。如果你在同一個PerfView實例中打開2個跟蹤,當你為每個跟蹤打開一個堆棧視圖時,并且Diff菜單提供了一個 "with Baseline "選項(在Help/Users Guide中搜索 "Diffing Two Traces")。

    關(guān)于對比2個運行的問題--當你對比2個運行以查看什么是退步時,最好是讓工作負載盡可能的相似。比方說,你做了一個改變以減少分配,與其在相同的時間內(nèi)運行2個運行,不如用相同數(shù)量的請求來運行它們,因為你知道你的新構(gòu)建正在處理相同數(shù)量的工作。否則,一個運行可能運行得更快,因此處理更多的請求,這本來就意味著它已經(jīng)需要做不同數(shù)量的分配。這只是意味著它更難做比較。

    GC暫停時間百分比高

    如果你不知道如何收集GC暫停時間數(shù)據(jù),請按照"如何收集頂級GC指標"中的說明進行。

    如果總的暫停時間很高,可能是由于GC太多(即GC觸發(fā)太頻繁),GC暫停時間太長或兩者都有。

    • 太多的停頓,即太多的GC

    根據(jù)我們的一條規(guī)則的一部分,觸發(fā)GC的頻率是由不存活的東西決定的。因此,如果你正在做大量的臨時對象分配(意味著它們不能存活),這意味著你將觸發(fā)大量的GC。在這種情況下,看一下這些分配是有意義的。如果你能消除一些,那就太好了,但有時這并不實際。我們提到了3種方法來剖析分配。讓我們看看如何進行每個分析。

    測量分配

    獲得分配的字節(jié)數(shù)

    我們已經(jīng)知道,你可以在摘要中得到分配字節(jié)的總數(shù)。在GCStats視圖中,你還可以得到每個GC的分配字節(jié)數(shù)gen0。

    GC Index(GC編號)Gen (代)Gen0 Alloc MB (Gen分配數(shù)量(MB))
    70N95.373
    81N71.103
    90N103.02
    102B0
    110N111.28
    121N94.537

    在Gen一欄中,'N'表示Nonconcurrent GC,'B'表示Background。所以完全阻塞的GC顯示為2N。因為只有g(shù)en2的GC可以是后臺,所以你只能看到2B,而不是0B或1B。你也可能看到'F',這意味著前景GC--當BGC正在進行時發(fā)生的短暫GC。

    注意,對于2B來說是0,因為如果gen0或gen1的分配預(yù)算被超過,我們會在BGC的開始做一個gen0或gen1的GC,所以gen0分配的字節(jié)數(shù)會顯示在gen0或gen1的GC上。

    我們知道,當gen0的分配預(yù)算被超過時,就會觸發(fā)GC。這個數(shù)據(jù)在GCStats中默認是不顯示的(只是因為表格中已經(jīng)有很多列了)。但你可以通過點擊GCStats中表格前的Raw Data XML file(用于調(diào)試)鏈接來獲得。一個xml文件將被生成并包含更詳細的數(shù)據(jù)。對于每個GC,你會看到這個(我把它修剪了一下,所以不會太寬)-

    <GlobalHeapHistory?FinalYoungestDesired="9,830,400"?NumHeaps="12"/>

    FinalYoungestDesired是為這個GC計算的最終gen0預(yù)算。由于我們對所有堆的預(yù)算進行了均衡,這意味著每個堆都有這個相同的預(yù)算。由于有12個堆,任何一個堆用完它的gen0預(yù)算都會導(dǎo)致下一次GC被觸發(fā)。所以在這種情況下,這意味著最多只有12*9,830,400=117MB的分配,直到下一次GC被觸發(fā)。我們可以看到下一個GC是一個BGC,它的Gen0 Alloc MB是0,因為我們把這個BGC開始時做的短暫GC歸結(jié)為GC#11,它在GC#9結(jié)束后在Gen0中分配了111.28 MB。

    查看帶有堆棧信息的采樣分配

    當然,你會想知道這些分配的情況。GC提供了一個叫做AllocationTick的事件,大約每100KB的分配就會被觸發(fā)。對于小對象堆來說,100KB意味著很多對象(這意味著對于SOH來說是采樣),但對于LOH來說,這實際上是準確的,因為每個對象至少有85000字節(jié)大。這個事件有一個叫做AllocationKind的字段--small意味著它是由分配為SOH而觸發(fā)的,而這個分配恰好使該SOH上的累積分配量超過了100KB(那么這個量會被重置)。所以你實際上不知道最后的分配量是多大。但是根據(jù)這個事件的頻率,看看哪些類型被分配的最多,以及分配它們的調(diào)用棧,仍然是一個非常好的近似值。

    很明顯,與只收集GCCollectOnly跟蹤相比,收集這個會增加明顯的開銷,但這仍然是可以容忍的。

    PerfView.exe /nogui /accepteula /KernelEvents=Process+Thread+ImageLoad /ClrEvents:GC+Stack /BufferSize:3000 /CircularMB:3000 collect

    這將收集AllocationTick事件及其分配被采樣對象的調(diào)用棧。然后你可以在內(nèi)存組下的 "GC Heap Alloc Ignore Free (Coarse Sampling) "視圖中打開它。

    點擊一個類型,你就可以看到分配該類型實例的堆棧。

    注意,當你在同一個PerfView實例中打開兩個追蹤時,你可以比較兩個GC對的分配視圖

    而且你可以雙擊每一種類型來查看分配給它們的調(diào)用棧。

    查看AllocationTick事件的另一種方法是使用Any Stacks視圖,因為它按大小分組。例如,這是我從一個客戶的跟蹤中看到的情況(類型名稱已匿名或縮短)。

    NameInc
    Event Microsoft-Windows-DotNETRuntime/GC/AllocationTick627,509
    + EventData TypeName Entry[CustomerType,CustomerCollection][]221,581
    |+ EventData Size 1064964,172
    ||+ EventData Kind Small4,172
    || + coreclr4,172
    || + corelib!System.Collections.Generic.Dictionary`2[CustomerType,System.__Canon].Resize(int32,bool)4,013
    || + corelib!System.Collections.Generic.Dictionary`2[CustomerType,System.__Canon].Initialize(int32)159
    |+ EventData Size 1146883,852
    ||+ EventData Kind Small3,852
    || + coreclr3,852
    || + corelib!System.Collections.Generic.Dictionary`2[CustomerType,System.__Canon].Resize(int32,bool)3,742
    || + corelib!System.Collections.Generic.Dictionary`2[CustomerType,System.__Canon].Initialize(int32)110

    這說明大部分分配來自于字典的重新調(diào)整,你也可以從 GC Heap Alloc 視圖中看到,但樣本計數(shù)信息給了你更多的線索(Resize 有 4013 次,而 Initialize 有 159 次)。所以,如果你能更好地預(yù)測字典會有多大,你可以把初始容量設(shè)置得更大,以大大減少這些分配。

    **使用 CPU 樣本查看內(nèi)存清理成本 **

    如果你沒有這些AllocationTick事件的跟蹤,但有CPU樣本(這很常見),你也可以看一下內(nèi)存清除的成本-

    如果你看一下memset_repmovs的調(diào)用者,突出顯示的2個調(diào)用者來自GC在把新對象遞出之前的內(nèi)存清除:

    (這是在.NET 5下,如果你有舊版本,你會看到WKS::gc_heap::bgc_loh_alloc_clr而不是WKS::gc_heap::bgc_uoh_alloc_clr)。

    在我的例子中,因為分配幾乎是測試的全部內(nèi)容,所以分配成本非常高--占CPU總使用量的25.6%

    理解為什么GC決定收集這一代

    在GCStats中,每個GC都有一列叫做 "Trigger Reason"。這告訴你這個GC是如何被觸發(fā)的。可能的觸發(fā)原因在PerfView repo的ClrTraceEventParser.cs中定義為GCReason。

    public enum GCReason {AllocSmall = 0x0,Induced = 0x1,LowMemory = 0x2,Empty = 0x3,AllocLarge = 0x4,OutOfSpaceSOH = 0x5,OutOfSpaceLOH = 0x6,InducedNotForced = 0x7,Internal = 0x8,InducedLowMemory = 0x9,InducedCompacting = 0xa,LowMemoryHost = 0xb,PMFullGC = 0xc,LowMemoryHostBlocking = 0xd }

    在這些原因中,最常見的是AllocSmall?- 這是說gen0的預(yù)算被超過了。如果你看到的最常見的是AllocLarge,那么它很可能表明了一個問題--它是說你的GC被觸發(fā)了,因為你分配了大的對象,超過了LOH預(yù)算。正如我們所知,這意味著它將觸發(fā)gen2 GC

    而我們知道,觸發(fā)頻繁的完全GC通常是性能問題的秘訣。其他由于分配引起的觸發(fā)原因是OutOfSpaceSOH和OutOfSpaceLOH?- 你看到這些比AllocSmall和AllocLarge要少得多--這些是為你接近物理空間極限時準備的(例如,如果我們內(nèi)存分配正在接近短暫段的終點)。

    那些幾乎總是引起危險信號的事件是 "Induced",因為它們意味著一些代碼實際上是自己觸發(fā)了GC。我們有一個GCTriggered事件,專門用于發(fā)現(xiàn)什么代碼用其調(diào)用棧觸發(fā)了GC。你可以用堆棧和最小的內(nèi)核事件收集一個非常輕量級的GC信息級別的跟蹤:

    PerfView.exe /nogui /accepteula /KernelEvents=Process+Thread+ImageLoad /ClrEvents:GC+Stack /ClrEventLevel=Informational /BufferSize:3000 /CircularMB:3000 collect

    然后在任意堆棧視圖中查看GCTriggered事件的堆棧:

    因此,"觸發(fā)原因 "是指GC是如何開始或產(chǎn)生的。如果一個GC開始的最常見原因是由于在SOH上分配,那么這個GC將作為一個gen0的GC開始(因為gen0的預(yù)算被超過了)。現(xiàn)在在GC開始之后,我們再決定我們實際上會收集哪一代。它可能保持為0代GC,或者升級為1代甚至2代GC-這是我們在GC中最先決定的事情之一。導(dǎo)致我們升級到更高世代的GC的因素就是我們所說的 "派遣的原因"(所以對于一個GC來說,只有一個觸發(fā)的原因,但可以有多個派遣的原因)。

    以下是出現(xiàn)在表格本身之前的 "GC的派遣理由 "部分的解釋文本

    本表更詳細地說明了GC決定收集那一代的確切原因。將鼠標懸停在各列標題上,以獲得更多信息。

    我不會在這里重復(fù)這些信息。最有趣的是那些升級到gen2的GC - 通常這些是由gen2的高內(nèi)存負載或高碎片引起的。

    • 個別的長時間停頓

    如果你不知道如何收集GC暫停時間數(shù)據(jù),請按照"如何收集頂級GC指標"中的說明進行。

    如果你不熟悉是什么導(dǎo)致了單個GC暫停,請先閱讀GC暫停部分,它解釋了哪些因素導(dǎo)致了GC暫停時間。

    我們知道,所有短暫的GCs都是阻塞的,而gen2 GC可以是阻塞的,也可以是背景的(BGC)。短暫的GC和BGC應(yīng)該會產(chǎn)生短暫的停頓。但是事情可能出錯,我們將展示如何分析這些情況。

    如果堆的大小很大,我們知道一個阻塞的gen2 GC會產(chǎn)生一個很長的停頓。但是當我們需要做gen2 GC的時候,我們一般傾向于BGC。所以長的GC暫停是由于阻塞的gen2 GC造成的,我們會想弄清楚為什么我們要做這些阻塞的gen2 GC。

    如此長時間的個別停頓可能是由以下因素或它們的組合造成的—

    · 在暫停期間有很多GC工作要做。

    · GC正在嘗試執(zhí)行工作,但無法執(zhí)行,因為CPU被占用

    讓我們看看如何分析每個場景。

    首先,您是否存在托管內(nèi)存泄漏?

    如果你不知道什么是管理型內(nèi)存泄露,請先回顧一下那一節(jié)。根據(jù)定義,這不是GC能幫你解決的問題。如果你有一個托管的內(nèi)存泄漏,保證GC會有越來越多的工作要做。

    在生產(chǎn)中觸發(fā)完全阻塞的GC可能是非常不可取的,所以在開發(fā)階段做盡職調(diào)查很重要。例如,如果你的產(chǎn)品處理請求,你可以在每個請求或每N個請求結(jié)束時觸發(fā)一個完全阻塞的GC。如果你認為內(nèi)存使用量應(yīng)該是相同的,你應(yīng)該能夠用工具來驗證。在這種情況下可以使用很多工具,因為這是一個簡單的場景。所以PerfView當然也有這個功能。你可以通過Memory/Take Heap Snapshot來獲取堆快照。它確實有一些不完全直截了當?shù)倪x項-

    "Max Dump K Objs "是一個 "聰明 "的嘗試,所以它不會轉(zhuǎn)儲每一個對象。我總是把它增加到至少是默認值(250)的10倍。凍結(jié)選項是為生產(chǎn)診斷而設(shè)的,當你不想招致完全阻塞的GC暫停時。但是,如果你在開發(fā)過程中這樣做,并試圖了解你的應(yīng)用程序的基本行為,你沒有理由不檢查它,這樣你就能得到一個準確的圖片,而不是用非Freeze選項試圖 "盡最大努力來跟蹤對象圖"。

    然后在PerfView中打開生成的.gcDump文件,它有一個類似堆棧的視圖,顯示根信息(例如,GC句柄持有這個對象)和轉(zhuǎn)儲中的類型實例的聚合信息。由于這是一個類似于堆棧的視圖,它提供了差分功能,所以你可以在PerfView中取兩個gcDump文件并進行差分。

    當你在生產(chǎn)中這樣做時,你可以先嘗試不使用凍結(jié)。

    長時間的停頓是由于短暫的GCs、完全阻塞的GCs還是BGCs?

    GCStats視圖在每個進程的頂部都有一個方便的滾動表,顯示了各代的最大/平均/總停頓時間(我確實應(yīng)該把全阻塞的GC和BGC分開,但現(xiàn)在你可以參考Gen2的表格)。一個例子。

    計算出gen2 GC的工作量

    對于gen2 GCs,我們希望看到大部分或所有的GC都以BGC的形式完成。但是如果你看到一個完全阻塞的GC(在GCStats中表示為2N),如果你的堆很大的話,它的暫停時間有可能很長(你會看到gen2 GCs有非常高的升代內(nèi)存數(shù)量 MB,與短暫的GC相比)。通常人們在這個時候要做的是進行堆快照,看看堆上有什么東西,并嘗試減少這些東西。然而,首先要弄清楚的是,為什么你首先要做完全阻塞的GCs。你可以查看Condemned Reasons表來了解這個問題。最常見的原因是高內(nèi)存負載和gen2碎片化。要想知道每個GC觀察到的內(nèi)存負載,點擊GCStats中 "GC Rollup By Generation "表格上方的 "Raw Data XML file (for debugging) "鏈接,它將生成一個xml文件,其中包括內(nèi)存負載等額外信息。一個例子是(我剪掉了大部分的信息)-

    <GCEvent GCNumber= "45" GCGeneration="2" ><GlobalHeapHistory FinalYoungestDesired="69,835,328" NumHeaps="32"/><PerHeapHistories Count="32" MemoryLoad="47"></PerHeapHistory></GCEvent>

    這說明當GC#45發(fā)生時,它觀察到的內(nèi)存負載為47%。

    由于bug導(dǎo)致的長時間停頓

    通常BGC的停頓都很小。唯一的一次是由于運行時的一個罕見的bug(例如,我們修復(fù)了一個bug,即模塊迭代器占用了一個鎖,當過程中有很多很多模塊時,這種鎖的爭奪意味著每個GC線程需要很長時間來迭代這些模塊),或者你正在做一些只在BGC的STW標記部分做的工作。由于這可能是由于非GC工作造成的,我們將在 "弄清長時間的GC是否是由于GC工作 "中討論如何診斷這個問題。

    計算出短暫GC的工作量

    GC的工作量大致與幸存者成正比,這由 "Promoted Bytes "指標表示,該指標是GCStats表格中的一列 -

    這是有道理的--gen1 GCs比gen0 GCs升代的對象更多,因此它們需要更長的時間。而且它們不會進行太多升代,因為它們只收集(通常是一小部分)堆。

    如果你看到短暫的GCs突然增加了很多,那么估計暫停時間會長很多。我所看到的一個原因是,它進入了一個不經(jīng)常被調(diào)用的代碼路徑,對象存活下來,而這些對象是不應(yīng)該存活的。不幸的是,我們用于找出導(dǎo)致短暫對象存活的原因的工具不是很好--我們已經(jīng)在.NET 5中添加了運行時支持,你可以使用PerfView中一個特殊的視圖,稱為 "Generational Aware "視圖,以查看哪些老年代對象導(dǎo)致年輕代對象存活--我將很快寫出更多細節(jié)。你將看到的是這樣的情況:

    我不知道有什么其他工具可以方便地告訴你這些信息(如果你知道有什么工具可以告訴你老一代的對象在年輕一代的對象上保持著什么,使它們在GC期間存活,請好心地告訴我!)。

    請注意,如果你在gen2/LOH中有一個對象持有年輕gen對象的引用,如果你不再需要它們引用那些對象,你需要手動將這些引用字段設(shè)置為null。否則,它們將繼續(xù)持有那些對象的引用,并導(dǎo)致它們被升代。對于C#程序來說,這是導(dǎo)致短暫對象存活的一個主要原因(對于F#程序來說,就不是這樣了)。你可以從GCStats視圖生成的Raw XML中看到這一點(點擊 "Raw Data XML file (for debugging) "鏈接,就在 "GC Rollup By Generation "表的上方),我把大部分屬性從xml中修剪掉了 -

    <GCEvent GCNumber="9" GCGeneration="0"><PerHeapHistories Count="12" MemoryLoad="20"><PerHeapHistory MarkStack="0.145(10430)" MarkFQ="0.001(0)" MarkHandles="0.005(296)" MarkOldGen="2.373(755538)"><PerHeapHistory MarkStack="0.175(14492)" MarkFQ="0.001(0)" MarkHandles="0.003(72)" MarkOldGen="2.335(518580)">

    每個GC線程由于各種根而升代的字節(jié)數(shù)是PerHeapHistory數(shù)據(jù)的一部分-MarkStack/FQ/Handles分別是標記堆棧變量、終結(jié)隊列和GC句柄,MarkOldGen表示由于來自老一代的引用而升代的字節(jié)數(shù)量。因此,舉例來說,如果你正在做一個gen1的GC,這就是gen2對象對gen0/gen1對象的持有數(shù)量,以使其存活。我們在.NET 5中對服務(wù)器GC所做的一個改進是,當我們標記OldGen根時,平衡GC線程的工作,因為這通常會導(dǎo)致最大的升代數(shù)量。因此,如果你在你的應(yīng)用程序中看到這個數(shù)字非常不平衡,升級到.NET 5會有幫助。

    弄清楚長的GC是否是由于GC工作造成的

    如果一個GC很長,但卻不符合上述任何一種情況,也就是說,沒有很多工作需要GC去做,但還是會造成長時間的停頓,這意味著我們需要弄清楚為什么GC在它想做工作的時候卻沒有做到。而通常當這種情況發(fā)生時,它似乎是隨機發(fā)生的。

    偶爾長停的一個例子 -

    我們在PerfView中做了一個非常方便的功能,叫做停止觸發(fā)器,意思是 "當觀察到某些條件滿足時,盡快停止跟蹤,這樣我們就能捕捉到最相關(guān)的最后部分"。它已經(jīng)有一些專門用于GC目的的內(nèi)置停止觸發(fā)器。

    GC事件發(fā)生的順序

    為了了解它們是如何工作的,我們首先需要簡要地看一下GC的事件序列。這里有6個相關(guān)的事件-

    Microsoft-Windows-DotNETRuntime/GC/SuspendEEStart //開始暫停托管線程運行 Microsoft-Windows-DotNETRuntime/GC/SuspendEEStop //暫停托管線程完成 Microsoft-Windows-DotNETRuntime/GC/Start // GC開始回收 Microsoft-Windows-DotNETRuntime/GC/Stop // GC回收結(jié)束 Microsoft-Windows-DotNETRuntime/GC/RestartEEStart //恢復(fù)之前暫停的托管線程 Microsoft-Windows-DotNETRuntime/GC/RestartEEStop //恢復(fù)托管線程運行完成

    (你可以在事件視圖中看到這些內(nèi)容)

    在一個典型的阻塞式GC中(這意味著所有短暫的GC和完全阻塞的GC),事件發(fā)生順序非常簡單:

    GC/SuspendEEStart GC/SuspendEEEnd <– 暫停托管線程完成 GC/Start GC/End <– actual GC is done GC/RestartEEStart GC/RestartEEEnd <– 恢復(fù)托管線程運行完成

    GC/SuspendEEStart和GC/SuspendEEEnd是用于暫停;GC/RestartStart和GC/RestartEEEnd是用于恢復(fù)。恢復(fù)只需要很少的時間,所以我們不需要討論它。暫停是可能需要很長時間的。

    BGC要復(fù)雜得多,一個完整的BGC事件序列看起來是這樣的

    1) GC/SuspendEEStart 2) GC/SuspendEEStop 3) GC/Start <– BGC/ starts<- there might be an ephemeral GC happen here, if so you'd see(這里可能有一個短暫的GC發(fā)生,如果是這樣,你會看到) GC/Start GC/Stop4) GC/RestartEEStart 5) GC/RestartEEStop <– done with the initial suspension (完成了最初的暫停)<- there might be 0 or more foreground ephemeral GC/s here, an example would be (這里可能有0個或更多的前臺瞬時的GC/s,一個例子是) GC/SuspendEEStart GC/SuspendEEStop GC/Start GC/Stop GC/RestartEEStart GC/RestartEEStop6) GC/SuspendEEStart 7) GC/SuspendEEStop 8) GC/RestartEEStart 9) GC/RestartEEStop <– done with BGC/'s 2nd suspension (完成了BGC/的第二次停牌)<- there might be 0 or more foreground ephemeral GC/s here (這里可能有0個或更多的前臺短暫GC/s)10) GC/Stop <– BGC/ Stops

    所以BGC在它的中間有兩對暫停/重啟。目前在GCStats視圖中,我們實際上是將這兩個暫停合并在一起(我正計劃將它們分開),但如果你確實看到一個長的BGC暫停,你總是可以使用事件視圖來找出哪個暫停是長的。在下面的例子中,我從事件視圖中復(fù)制并粘貼了一個客戶跟蹤的事件序列,它遇到了我提到的bug導(dǎo)致長時間暫停。

    Event NameTime MSecReasonCountDepthTypeexplanation
    GC/Start160,551.74AllocSmall1882BackgroundGC
    GC/Start160,551.89AllocSmall1890NonConcurrentGCWe are doing a gen0 at the beginning of this BGC(這個BGC開始時正在做一個gen0)
    GC/Stop160,577.48
    1890

    GC/RestartEEStart160,799.87



    There's a long period of time here between last event and this one due to the bug(由于錯誤,在上次活動和這次活動之間,這里有一段很長的時間)
    GC/RestartEEStop160,799.91




    GC/SuspendEEStart161,803.36SuspendForGC188

    A Foreground gen1 happens(前臺gen1發(fā)生)
    GC/SuspendEEStop161,803.42




    GC/Start161,803.61AllocSmall1901ForegroundGC
    GC/Stop161,847.14
    1901

    GC/RestartEEStart161,847.15




    GC/RestartEEStop161,847.23



    The Foreground gen1 ends(前臺gen1結(jié)束)
    GC/SuspendEEStart161,988.57SuspendForGCPrep188

    BGC's 2nd suspension starts with SuspendForGCPrep as its reason(BGC的第二次暫停開始,理由是SuspendForGCPrep)
    GC/SuspendEEStop161,988.71




    GC/RestartEEStart162,239.84




    GC/RestartEEStop162,239.94



    BGC's 2nd suspension ends, another long pause due to the same bug(BGC的第二次停頓結(jié)束,由于同樣的錯誤,又一次長時間停頓)
    GC/Stop162,413.70
    1882

    我所做的是在CPU堆棧視圖中查找那些長時間停頓的時間范圍(160,577.482-160,799.868161,988.57-162,239.94),發(fā)現(xiàn)了這個錯誤。

    PerfView GC停止觸發(fā)器

    有3個GC特定的停止觸發(fā)器 -

    Trigger nameWhat it measures
    StopOnGCOverMsectrigger if the time between GC/Start and GC/Stop is over this value, and it's not a BGC(如果GC/Start和GC/Stop之間的時間超過這個值,并且不是BGC,則觸發(fā)。)
    StopOnGCSuspendOverMSectrigger if the time between GC/SuspendEEStart and GC/SuspendEEStop is over this value(如果GC/SuspendEEStart和GC/SuspendEEStop之間的時間超過這個值,則觸發(fā)。)
    StopOnBGCFinalPauseOverMSectrigger if the time between GC/SuspendEEStart (with Reason SuspendForGCPrep) and GC/RestartEEStop is over this value(如果GC/SuspendEEStart(與原因是 SuspendForGCPrep)和GC/RestartEEStop之間的時間超過這個值,則觸發(fā)。)

    我通常與/StopOnGCOverMSec和/StopOnBGCFinalPauseOverMSec一起使用的命令行是 --

    PerfView.exe /nogui /accepteula /StopOnGCOverMSec:15 /Process:A /DelayAfterTriggerSec:0 /CollectMultiple:3 /KernelEvents=default /ClrEvents:GC+Stack /BufferSize:3000 /CircularMB:3000 /Merge:TRUE /ZIP:True collect

    如果你的進程被稱為A.exe,你會想指定/Process:A。我在這篇博客中對每個參數(shù)都有詳細解釋。

    調(diào)試一個隨機的長GC

    這里有一個例子,演示了如何調(diào)試一個突然比其他升代了類似數(shù)量的GC要長很多的GC。我用上面的命令行收集了一個跟蹤,我可以在GCStats中看到有一個GC比15個長 - 它是GC#4022,是20.963ms,而且它沒有比正常情況下更多的升代(你可以看到在它上面的gen0,升代的數(shù)量非常相似,但花的時間卻少很多)。

    所以我在CPU堆棧視圖中輸入GC#4022的開始和結(jié)束時間戳(30633.741到30654.704),我看到對于執(zhí)行實際GC工作的coreclr!SVR::gc_heap::gc_thread_function,有兩部分沒有CPU占用,而應(yīng)該有很多--____ 部分意味著沒有CPU占用。

    此,我們可以在CPU堆棧視圖中突出顯示第一個平面部分,右擊它并選擇 "設(shè)置時間范圍"。這將向我們顯示這個進程在這段時間內(nèi)的CPU樣本,當然我們將看到?jīng)]有。

    我們看到mpengine模塊,它來自MsMpEng.exe進程(雙擊mpengine單元會告訴你它屬于哪個進程)。要確認這個進程干擾了我們的進程,就是在Events中輸入開始和結(jié)束的時間戳,然后看一下原始的CPU樣本事件(如果你不知道如何使用這個視圖,請看PerfView中的其他相關(guān)視圖部分) -

    你可以看到MsMpEng.exe進程的樣本的優(yōu)先級非常高--15。服務(wù)器GC線程運行的優(yōu)先級是11左右。

    為了調(diào)試長時間的暫停,我通常采取ThreadTime跟蹤,其中包括ContextSwitch和ReadyThread事件--它們是大量的,但應(yīng)該準確地告訴我們GC線程在調(diào)用SuspendEE時正在等待什么-

    PerfView.exe /nogui /accepteula /StopOnGCSuspendOverMSec:200 /Process:A /DelayAfterTriggerSec:0 /CollectMultiple:3 /KernelEvents=ThreadTime /ClrEvents:GC+Stack /BufferSize:3000 /CircularMB:3000 /Merge:TRUE /ZIP:True collect

    然而,ThreadTime追蹤可能太多,可能會導(dǎo)致你的應(yīng)用程序運行得不夠 "正常",無法表現(xiàn)出你所調(diào)試的行為。在這種情況下,我會從默認的內(nèi)核事件開始追蹤,這通常會揭示問題或給你足夠的線索。你可以簡單地把ThreadTime替換成Default -

    PerfView.exe /nogui /accepteula /StopOnGCSuspendOverMSec:200 /Process:A /DelayAfterTriggerSec:0 /CollectMultiple:3 /KernelEvents=Default /ClrEvents:GC+Stack /BufferSize:3000 /CircularMB:3000 /Merge:TRUE /ZIP:True collect

    我在這篇博客中有一個詳細的調(diào)試長懸掛問題的例子。

    大尺寸的GC堆

    如果你不知道如何收集GC堆大小數(shù)據(jù),請按照"如何收集頂級GC指標"中的說明進行操作。

    • 調(diào)試OOM

    在我們談?wù)摯蟮腉C堆大小作為一個一般的問題類別之前,我想特別提到調(diào)試OOM,因為這是一個大多數(shù)讀者都熟悉的例外。而且有些人可能已經(jīng)使用了SoS !"AnalyzeOOM "命令,它可以顯示2個方面--1)是否確實存在一個托管堆的OOM。因為GC堆只是你進程中的一種內(nèi)存使用,OOM不一定是GC堆造成的;2)如果是托管堆OOM,什么操作造成的,例如,GC試圖保留一個新段,但做不到(你在64位上永遠不會真正看到這個)或在試圖做分配時無法提交。

    在不使用SoS的情況下,你也可以通過簡單地查看GC堆使用多少內(nèi)存與進程使用多少內(nèi)存來驗證GC堆是否是OOM的罪魁禍首。我們將在下面討論堆大小的分析。如果你確認GC堆占用了大部分的內(nèi)存,而且我們知道OOM只是在GC非常努力地減少堆的大小,但是不能之后才被拋出,這意味著有大量的內(nèi)存由于用戶根而幸存下來,這意味著GC無法回收它。而你可以按照管理性內(nèi)存泄露調(diào)查來弄清楚哪些內(nèi)存幸存下來。

    現(xiàn)在讓我們來談?wù)勥@樣的情況:你沒有得到OOM,但需要看一下堆的大小,看看是否可以或如何優(yōu)化它。在"如何正確看待GC堆的大小?"一節(jié)中,我們談到了堆的大小以及如何廣泛地測量。所以我們知道,堆的大小在很大程度上取決于你在GC發(fā)生時的測量和分配預(yù)算。GCStats視圖顯示了GC進入和退出時的大小,即峰值和后值。

    剖析一下這些尺寸是有幫助的。After MB這一欄是以下的總和

    Gen0 MB + Gen1 MB + Gen2 MB + LOH MB

    還注意到Gen0 Frag %說的是99.99%。我們知道這是由于pinning。因此,部分gen0分配將適合于這種碎片化。所以對于GC #2來說,在GC #1結(jié)束時以26.497 MB開始,然后分配了101.04 MB,在GC #2開始時以108.659 MB的大小結(jié)束。

    • 峰值尺寸太大,但GC后尺寸不大?

    如果是這種情況,這通常意味著在觸發(fā)下一次GC之前有太多的gen0分配。在.NET Core 3.0中,我們啟用了一個限制這種情況的配置,叫做GCGen0MaxBudget--我通常不建議人們設(shè)置這個配置,因為你可能會把它設(shè)置得太小,從而導(dǎo)致GC過于頻繁。這是為了限制gen0的最大分配預(yù)算。當你使用Server GC時,GC在設(shè)置gen0預(yù)算時相當積極,因為它認為進程使用機器上的大量資源是可以的。這通常是可以的,因為如果一個進程使用了服務(wù)器GC,往往意味著它可以負擔(dān)得起使用大量的資源。但是,如果你確實有這樣的情況,你對更頻繁地觸發(fā)GC以交換到更小的堆大小沒有意見,你可以用這種配置來做。我的希望是,在未來,我們將使這成為一些高級配置的一部分,允許你向我們傳達你想做這種交換,這樣GC就可以為你自動調(diào)整,而不是你自己使用一個非常低級的配置。

    在過去使用GC性能計數(shù)器的人認識到 "#Total Committed Bytes "計數(shù)器,他們問如何在.NET Core中獲得這個計數(shù)器。首先,如果你用這種方式測量已提交的字節(jié),你可能會看到它更接近峰值大小,而不是之后的大小,這是因為在短暫段上對已提交的特殊處理。因為 "After size "沒有報告我們將要使用但還沒有使用的gen0預(yù)算部分。所以你可以直接使用GCStats中報告的峰值大小作為你的近似的總投入。但如果你使用的是.NET 5,你可以通過調(diào)用我們前面提到的GetGCMemoryInfoAPI得到這個數(shù)字--它是GCMemoryInfo結(jié)構(gòu)上返回的屬性之一。

    有一個不太方便的方法,就是每堆歷史事件中的ExtraGen0Commit字段。你可以在你已經(jīng)得到的堆大小信息(即在GCHeapStats事件中)的基礎(chǔ)上添加這個字段(如果你使用的是服務(wù)器GC,它將是所有堆的ExtraGen0Commit之和)。但是我們沒有在PerfView的用戶界面中公開這一點,所以你需要自己去使用TraceEvent庫來獲得這些信息。

    • GC后尺寸很大?

    如果是這樣,大部分的尺寸是在gen2/LOH中嗎?你是否主要在做后臺GC(不壓縮)?如果你已經(jīng)在做完全阻塞的GC,而After的大小還是太大,這僅僅意味著你有太多的數(shù)據(jù)存活下來。你可以按照管理性內(nèi)存泄露調(diào)查來弄清楚存活的數(shù)據(jù)。

    另一種可能的情況是有很大比例的堆在gen0中,但大部分是碎片。這種情況會發(fā)生,特別是當你把一些對象釘住了很久,而且它們在堆上足夠分散時。所以即使GC已經(jīng)降代它們到了gen0,只要這些引腳沒有消失,堆的那一部分仍然不能被回收。你可以收集GCHandle事件來計算它們何時被釘住。PerfView的命令行是

    perfview /nogui /KernelEvents=Process+Thread+ImageLoad /ClrEvents:GC+Stack+GCHandle /clrEventLevel=Informational collect

    • gen2 GC是否主要為后臺GC?

    如果GC主要是后臺GC,那么需要看看碎片的使用是否高效,也就是說,當后臺GC開始時,gen2 Frag %是否非常大?如果不是非常大,這意味著它的工作是最優(yōu)化的。否則這表明后臺GC的調(diào)度問題--請讓我知道。要看后臺GC開始時的碎片情況,你可以使用GCStats視圖中的Raw XML鏈接來查看它。我已經(jīng)把數(shù)據(jù)修剪成只有相關(guān)的部分-

    <GCEvent GCNumber= "1174" GCGeneration="2" Type= "BackgroundGC" Reason= "AllocSmall"><PerHeapHistory><GenData Name="Gen2" SizeBefore="187,338,680" SizeAfter="187,338,680" ObjSpaceBefore="177,064,416" FreeListSpaceBefore="10,200,120" FreeObjSpaceBefore="74,144"/><GenData Name="GenLargeObj" SizeBefore="134,424,656" SizeAfter="131,069,928" ObjSpaceBefore="132,977,592" FreeListSpaceBefore="1,435,640" FreeObjSpaceBefore="11,424"/>

    SizeBefore = ObjSpaceBefore + FreeListSpaceBefore + FreeObjSpaceBefore

    SizeBefore` 這一代的總規(guī)模

    ObjSpaceBefore?這一代的有效對象所占的大小

    FreeListSpaceBefore?這一代的自由列表所占的大小

    FreeObjSpaceBefore?在這一代中,太小的自由對象所占用的大小,不能進入自由列表。

    (FreeListSpaceBefore + FreeObjSpaceBefore) 就是我們所說的碎片化

    在這種情況下,我們看到((FreeListSpaceBefore + FreeObjSpaceBefore)/ SizeBefore)是5%,這是相當小的,這意味著我們已經(jīng)用掉了大部分BGC建立好的自由空間。當然,我們希望看到這個比例越小越好,但如果自由空間太小,就意味著GC可能無法使用它們。一般來說,如果這個比例是15%或更小,我不會擔(dān)心,除非我們看到自由空間足夠大但沒有被使用。

    你也可以從我們前面提到的GetGCMemoryInfoAPI中獲得這些數(shù)據(jù)。

    • 你看到的堆的大小從GC的角度來看是合理的,但仍然希望有一個更小的堆?

    在你經(jīng)歷了上述情況后,你可能會發(fā)現(xiàn),從GC的角度來看,這一切都可以解釋。但如果你仍然希望堆的大小更小呢?

    你可以把你的進程放在一個內(nèi)存受限的環(huán)境中,也就是說,一個有內(nèi)存限制的容器,這意味著GC會自動識別為它可以使用的內(nèi)存。然而,如果你使用的是Server GC,你至少要升級到.NET Core 3.0,它對容器的支持更加強大。在該版本中,我們還添加了2個新的配置,允許你指定GC堆的內(nèi)存限制 -?GCHeapHardLimit和GCHeapHardLimitPercent。它們在本博客文章中得到了解釋。

    當你的進程運行在有其他進程的機器上時,GC開始反應(yīng)的默認內(nèi)存負載可能不是每個進程都想要的。你可以考慮使用GCHighMemPercent配置,并將該閾值設(shè)置得更低--這將使GC更積極地進行完全阻塞的GC,所以即使有內(nèi)存可用,它也不會使你的堆增長得那么多。

    • GC是否為自己的記賬工作使用了太多的內(nèi)存?

    偶爾我也收到一些人的報告,他們確實觀察到有一大塊內(nèi)存被用于GC記賬。你可以通過GC的gc_heap::grow_brick_card_tables的VirtualAlloc調(diào)用看到。這是因為,由于在地址空間中保留了一些意想不到的區(qū)域,堆的范圍被拉得太長了。如果你確實遇到了這個問題,并且無法防止意外的保留,你可以考慮用GCHeapHardLimit/GCHeapHardLimitPercent指定一個內(nèi)存限制,那么整個限制將被提前保留,這樣你就不會遇到這個問題了。

    性能問題的明確跡象

    如果你看到以下任何情況,毫無疑問你有性能問題。與任何性能問題一樣,正確確定優(yōu)先次序總是很重要的。例如,你可能有很長的GC暫停,但如果它們不影響你所關(guān)心的性能指標,你把時間花在其他地方會更有成效。

    我使用PerfView中的GCStats視圖來顯示這些癥狀。如果你不熟悉這個視圖,請看本節(jié)。你不一定要使用PerfView,只要能夠顯示下面的數(shù)據(jù),使用任何工具都可以。

    暫停時間太長

    暫停通常在每次發(fā)生時都會少于1ms。如果你看到的是10秒或100秒的東西,你不需要懷疑你是否有寧問題--這是一個明確的信號,說明你有。

    如果你看到你的大部分GC暫停都被暫停占用了,尤其是持續(xù)的暫停,而且你的總GC暫停太多,你肯定應(yīng)該調(diào)試它。我在這篇博客中有一個詳細的調(diào)試長暫停問題的例子。

    這可以通過GCStats視圖中的 "Suspend Msec "和 "Pause Msec "列來表示。我模擬了一個例子 --

    GC Index(索引)Suspend Msec(暫停時間)Pause Msec(停頓時間)
    10150180
    11190200

    兩個GCs的大部分停頓時間都是在暫停中度過的。

    隨機的長時間GC停頓

    "隨機長的GC停頓 "意味著你突然看到一個GC并沒有比平時升代更多,但卻需要更長的時間。下面是一個模擬的例子

    GC Index(索引)Suspend Msec(暫停時間)Pause Msec(停頓時間)Promoted MB(升代MB)
    100.0152.0
    110.012002.1
    120.0162.2

    所有的GCs都升代了~2MB,但是GC#10和#12花了幾毫秒,而GC#11花了200。這就說明在GC#11期間出了問題。有時你可能會看到突然花了很長時間的GC也招致了很長時間的暫停,因為導(dǎo)致長時間暫停的原因也影響了GC的工作。

    我已經(jīng)給出了一個例子上面如何調(diào)試這個問題。

    大多數(shù)GC是完全阻塞的GC

    如果你看到大多數(shù)GC是完全阻塞的,如果你有一個大的堆,這通常需要相當長的時間,這就是一個性能問題。我們不應(yīng)該一直做完全阻塞的GCs,就是這樣。即使你處于高內(nèi)存負載的情況下,做完全阻塞GC的目的是為了減少堆的大小,這樣內(nèi)存負載就不再高了。而GC有辦法應(yīng)對有挑戰(zhàn)的情況,比如針對高內(nèi)存負載的臨時模式+沉重的固定,以避免做更多的完全阻塞GC,而不是必要。我見過的最常見的原因?qū)嶋H上是誘導(dǎo)的完全阻塞的GCs,這對調(diào)試來說是很容易的,因為GCStats會告訴你觸發(fā)原因是誘導(dǎo)的。下面是一個模擬的例子

    GC IndexTrigger ReasonGenPause Msec
    10Induced2NI1000
    11Induced2NI1100
    12Induced2NI1000

    本節(jié)講述了如何找出誘發(fā)GC的原因。

    有助于我們幫助你調(diào)試性能問題的信息

    在某些時候,在你遵循本文件中的建議并做了詳盡的調(diào)查后,你仍然發(fā)現(xiàn)你的性能問題沒有得到解決。我們很愿意幫助你! 為了節(jié)省你和我們的時間,我們建議你準備以下信息 -

    運行時的文件版本

    每個版本都會有新的GC變化,所以我們很自然地想知道你使用的是哪個版本的運行時,以便我們知道該版本的運行時有哪些GC變化。所以提供這些信息是非常必要的。版本如何映射到 "公共名稱",如.NET 4.7,是不容易追蹤的,所以提供dll的 "FileVersion "屬性會對我們有很大幫助,它可以告訴我們版本號與分支名稱(對于.NET Framework)或?qū)嶋H提交(對于.NET Core)。你可以通過像這樣的powerhell命令來獲得這些信息:

    PS C:\Windows\Microsoft.NET\Framework64\v4.0.30319> (Get-Item C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll).VersionInfo.FileVersion 4.8.4250.0 built by: NET48REL1LAST_CPS C:\> (Get-Item C:\temp\coreclr.dll).VersionInfo.FileVersion 42,42,42,42424 @Commit: a545d13cef55534995115eb5f761fd0cecf66fc1

    獲得這些信息的另一個方法是通過調(diào)試器通過lmvm命令(部分省略)-

    0:000> lmvm coreclr Browse full module list start end module name 00007ff8`f1ec0000 00007ff8`f4397000 CoreCLR (deferred) Image path: C:\runtime-reg\artifacts\tests\coreclr\windows.x64.Debug\Tests\Core_Root\CoreCLR.dllImage name: CoreCLR.dllInformation from resource tables:FileVersion: 42,42,42,42424 @Commit: a545d13cef55534995115eb5f761fd0cecf66fc1

    如果你捕捉到ETW跟蹤,你也可以找到KernelTraceControl/ImageID/FileVersion事件。它看起來像這樣(部分省略)。

    ThreadID="-1" ProcessorNumber="2" ImageSize="10,412,032" TimeDateStamp="1,565,068,727" BuildTime="8/5/2019 10:18:47 PM" OrigFileName="clr.dll" FileVersion="4.7.3468.0 built by: NET472REL1LAST_C"

    你已經(jīng)進行了哪些診斷

    如果你已經(jīng)按照本文件中的技術(shù),自己做了一些診斷,這是強烈建議的,請與我們分享你做了什么,得出了什么結(jié)論。這不僅可以節(jié)省我們的工作,還可以告訴我們我們提供的信息對你的診斷有什么幫助或沒有幫助,這樣我們就可以對我們提供給客戶的信息進行調(diào)整,使他們的生活更輕松。

    性能數(shù)據(jù)

    就像任何性能問題一樣,在沒有任何性能跟蹤數(shù)據(jù)的情況下,我們真的只能給出一些一般性的指導(dǎo)和建議。要真正找出問題所在,我們需要性能跟蹤數(shù)據(jù)。

    正如本文檔中多次提到的,性能跟蹤是我們調(diào)試性能問題的主要方法,除非你已經(jīng)進行了診斷,表明不需要頂級GC跟蹤,否則我們總是要求你收集這樣的跟蹤來開始。我通常也會要求你提供帶有CPU樣本的追蹤,特別是當我們要診斷長時間的GC暫停時。我們可能會要求你根據(jù)我們從最初的追蹤中得到的線索,收集更多的追蹤信息。

    一般來說,轉(zhuǎn)儲不太適合調(diào)查性能問題。但是,我們了解有時可能無法獲得跟蹤,而您所擁有的只是轉(zhuǎn)儲(dump)。如果情況確實如此,請盡可能與我們分享(即,在沒有隱私問題的情況下,因為dump可能會泄漏源碼和內(nèi)存數(shù)據(jù))。


    另外插播一個小廣告

    [蘇州-同程旅行] - .NET后端研發(fā)工程師

    招聘中級及以上工程師,優(yōu)秀應(yīng)屆生也可以,我會全程跟進,從職位匹配,到面試建議與準備,再到面試流程和每輪面試的結(jié)果等。大家可以直接發(fā)簡歷給我。

    工作職責(zé)
    負責(zé)全球前三中文在線旅游平臺機票業(yè)務(wù)系統(tǒng)的研發(fā)工作,根據(jù)需求進行技術(shù)文檔編寫和編碼工作

    任職要求

    • 擁有至少1年以上的工作經(jīng)驗,優(yōu)秀的候選人可放寬

    • 熟悉.NET Core和ASP.Net Core

    • C#基礎(chǔ)扎實,了解CLR原理,包括多線程、GC等

    • 有DDD 微服務(wù)拆分 重構(gòu)經(jīng)驗者優(yōu)先

    • 能對線上常見的性能問題進行診斷和處理

    • 熟悉Mysql Redis MongoDB等數(shù)據(jù)庫中間件,并且進行調(diào)優(yōu)

    • 必須有扎實的計算機基礎(chǔ)知識,熟悉常用的數(shù)據(jù)結(jié)構(gòu)與算法,并能在日常研發(fā)中靈活使用

    • 熟悉分布式系統(tǒng)的設(shè)計和開發(fā),包括但不限于緩存、消息隊列、RPC及一致性保證等技術(shù)

    • 海量HC 歡迎投遞~

    薪資福利

    • 月薪:15K~30K 根據(jù)職級不同有所不同

    • 年假:10天帶薪年假 春節(jié)提前1天放假 病假有補貼

    • 年終:根據(jù)職級不同有 2-4 個月

    • 餐補:有餐補,自有食堂

    • 交通:有打車報銷

    • 五險一金:基礎(chǔ)五險一金,12%的公積金、補充醫(yī)療、租房補貼等

    • 節(jié)日福利:端午、中秋、春節(jié)有節(jié)日禮盒

    • 通訊補貼:根據(jù)職級不同,每個月有話費補貼 50~400

    簡歷投遞方式

    大家把簡歷發(fā)到我郵箱即可,記得一定要附上聯(lián)系(微信 or 手機號)方式喲~

    郵箱(這是啥格式大家都懂):aW5jZXJyeUBmb3htYWlsLmNvbQ==

    總結(jié)

    以上是生活随笔為你收集整理的.NET内存性能分析指南的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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

    99精品视频在线观看免费 | 日日噜噜噜噜夜夜爽亚洲精品 | 熟女体下毛毛黑森林 | 国产精品手机免费 | 国产亚洲日韩欧美另类第八页 | 久久综合色之久久综合 | 少妇无套内谢久久久久 | 高潮毛片无遮挡高清免费 | av无码久久久久不卡免费网站 | 国产激情综合五月久久 | 真人与拘做受免费视频 | 亚洲男女内射在线播放 | 国产精品美女久久久久av爽李琼 | 少妇无码一区二区二三区 | 国产熟妇高潮叫床视频播放 | 人人妻人人澡人人爽人人精品 | 思思久久99热只有频精品66 | 无码吃奶揉捏奶头高潮视频 | 粉嫩少妇内射浓精videos | 伊在人天堂亚洲香蕉精品区 | 我要看www免费看插插视频 | 人人妻人人澡人人爽精品欧美 | 亚洲呦女专区 | 国产精品丝袜黑色高跟鞋 | 97久久精品无码一区二区 | 精品久久综合1区2区3区激情 | 国产xxx69麻豆国语对白 | 免费网站看v片在线18禁无码 | 亚洲精品午夜无码电影网 | 大地资源网第二页免费观看 | 成人性做爰aaa片免费看 | 丰满人妻翻云覆雨呻吟视频 | 色综合久久久久综合一本到桃花网 | 成人aaa片一区国产精品 | 国产一区二区三区精品视频 | 强开小婷嫩苞又嫩又紧视频 | 又粗又大又硬毛片免费看 | 无码帝国www无码专区色综合 | 欧美野外疯狂做受xxxx高潮 | 久久无码人妻影院 | 乌克兰少妇xxxx做受 | 精品国产麻豆免费人成网站 | 中国女人内谢69xxxx | 日产国产精品亚洲系列 | 无码纯肉视频在线观看 | 久久精品中文闷骚内射 | 97资源共享在线视频 | 思思久久99热只有频精品66 | 老熟女重囗味hdxx69 | 狂野欧美性猛交免费视频 | 国产精品久久久av久久久 | 蜜臀aⅴ国产精品久久久国产老师 | 波多野结衣av在线观看 | 成人女人看片免费视频放人 | 久久精品中文字幕大胸 | 熟妇女人妻丰满少妇中文字幕 | 精品 日韩 国产 欧美 视频 | 2020最新国产自产精品 | 亚洲狠狠婷婷综合久久 | 中文字幕精品av一区二区五区 | 亚洲欧美国产精品专区久久 | 无遮挡啪啪摇乳动态图 | 亚洲成av人综合在线观看 | 无码国模国产在线观看 | 国产农村妇女高潮大叫 | 国产精品丝袜黑色高跟鞋 | 无码乱肉视频免费大全合集 | 国产日产欧产精品精品app | 国产精品亚洲а∨无码播放麻豆 | 亚洲精品国产精品乱码不卡 | 奇米影视888欧美在线观看 | 国产香蕉尹人视频在线 | 中文字幕精品av一区二区五区 | 激情国产av做激情国产爱 | 国产xxx69麻豆国语对白 | 国产特级毛片aaaaaa高潮流水 | 亚洲精品久久久久avwww潮水 | 牲欲强的熟妇农村老妇女视频 | 精品一区二区三区无码免费视频 | 色 综合 欧美 亚洲 国产 | 国产香蕉尹人综合在线观看 | 久久精品中文闷骚内射 | 黑人大群体交免费视频 | 激情国产av做激情国产爱 | 九九综合va免费看 | 老熟女重囗味hdxx69 | 久久精品99久久香蕉国产色戒 | 极品嫩模高潮叫床 | 亚洲精品国产品国语在线观看 | 又粗又大又硬毛片免费看 | 午夜免费福利小电影 | 欧美 日韩 人妻 高清 中文 | 偷窥日本少妇撒尿chinese | 精品欧洲av无码一区二区三区 | 国产免费久久精品国产传媒 | 亚洲精品鲁一鲁一区二区三区 | 给我免费的视频在线观看 | 丰满人妻精品国产99aⅴ | 欧美性色19p | 国产精品a成v人在线播放 | 国产精品国产三级国产专播 | 欧美兽交xxxx×视频 | 伊人久久婷婷五月综合97色 | 日本一区二区三区免费播放 | 中文字幕乱妇无码av在线 | 亚洲熟妇色xxxxx亚洲 | 青青青手机频在线观看 | 久久亚洲国产成人精品性色 | 伊人久久婷婷五月综合97色 | 成人免费视频一区二区 | 波多野42部无码喷潮在线 | 久久亚洲国产成人精品性色 | 亚洲啪av永久无码精品放毛片 | 欧美 日韩 人妻 高清 中文 | 国产成人无码午夜视频在线观看 | 精品无码国产一区二区三区av | 日韩 欧美 动漫 国产 制服 | 纯爱无遮挡h肉动漫在线播放 | 久久精品女人的天堂av | v一区无码内射国产 | 熟女少妇在线视频播放 | 国产色视频一区二区三区 | 牛和人交xxxx欧美 | 人妻少妇精品无码专区二区 | 一本久道久久综合婷婷五月 | 偷窥日本少妇撒尿chinese | 欧美怡红院免费全部视频 | 正在播放东北夫妻内射 | 性欧美牲交xxxxx视频 | 国产手机在线αⅴ片无码观看 | 国产av一区二区三区最新精品 | 精品乱码久久久久久久 | 在线精品亚洲一区二区 | 成人精品视频一区二区 | 亚洲国产精华液网站w | 国产精品久久久av久久久 | 亚洲 a v无 码免 费 成 人 a v | 成人试看120秒体验区 | 婷婷五月综合激情中文字幕 | 久久人妻内射无码一区三区 | 色诱久久久久综合网ywww | 日欧一片内射va在线影院 | 狠狠躁日日躁夜夜躁2020 | 中文字幕精品av一区二区五区 | 欧美 日韩 人妻 高清 中文 | 中文字幕乱码亚洲无线三区 | 偷窥日本少妇撒尿chinese | 天天躁夜夜躁狠狠是什么心态 | 欧美人与物videos另类 | 亚洲s色大片在线观看 | 4hu四虎永久在线观看 | 精品国产精品久久一区免费式 | 荫蒂被男人添的好舒服爽免费视频 | 中文字幕人妻无码一区二区三区 | 无码帝国www无码专区色综合 | 精品偷拍一区二区三区在线看 | 亚洲精品国产第一综合99久久 | 伊人久久大香线蕉亚洲 | 国产偷国产偷精品高清尤物 | 人妻少妇精品无码专区二区 | 精品久久久久久亚洲精品 | 国产莉萝无码av在线播放 | 丰满妇女强制高潮18xxxx | 国产免费无码一区二区视频 | 一区二区传媒有限公司 | 国产精品无套呻吟在线 | 国产真实伦对白全集 | 久久久久久久人妻无码中文字幕爆 | 麻花豆传媒剧国产免费mv在线 | 最新国产乱人伦偷精品免费网站 | 国产卡一卡二卡三 | 天天摸天天碰天天添 | 国内少妇偷人精品视频 | 成人无码视频免费播放 | 夜精品a片一区二区三区无码白浆 | 亚洲精品www久久久 | 亚洲精品美女久久久久久久 | 国产 精品 自在自线 | 亚洲狠狠婷婷综合久久 | 国产色xx群视频射精 | 蜜桃无码一区二区三区 | 久久综合九色综合97网 | 精品水蜜桃久久久久久久 | 亚洲成av人综合在线观看 | 亚洲自偷自拍另类第1页 | 水蜜桃色314在线观看 | 四虎国产精品免费久久 | 亚洲一区二区三区国产精华液 | 亚洲一区二区三区播放 | 久久99久久99精品中文字幕 | 国产精品人妻一区二区三区四 | 国产又爽又黄又刺激的视频 | 久久综合给合久久狠狠狠97色 | 97精品国产97久久久久久免费 | 成人一区二区免费视频 | 国产人成高清在线视频99最全资源 | 久久伊人色av天堂九九小黄鸭 | 麻豆国产97在线 | 欧洲 | 日本欧美一区二区三区乱码 | 国内老熟妇对白xxxxhd | 少妇性俱乐部纵欲狂欢电影 | 精品久久8x国产免费观看 | 丰满少妇女裸体bbw | 狂野欧美性猛xxxx乱大交 | 国产情侣作爱视频免费观看 | 激情内射亚州一区二区三区爱妻 | 牲欲强的熟妇农村老妇女 | 日韩精品a片一区二区三区妖精 | 性色欲情网站iwww九文堂 | 国产午夜福利亚洲第一 | 午夜免费福利小电影 | 日韩精品成人一区二区三区 | 久久午夜无码鲁丝片午夜精品 | 丰腴饱满的极品熟妇 | 樱花草在线播放免费中文 | 精品乱子伦一区二区三区 | 中文字幕无码av激情不卡 | 免费乱码人妻系列无码专区 | 久久99热只有频精品8 | 精品国精品国产自在久国产87 | 小泽玛莉亚一区二区视频在线 | 真人与拘做受免费视频 | 午夜时刻免费入口 | 久久综合九色综合欧美狠狠 | 76少妇精品导航 | 亚洲综合另类小说色区 | 秋霞特色aa大片 | 色一情一乱一伦一区二区三欧美 | 中文字幕无码乱人伦 | 中文字幕中文有码在线 | 成人片黄网站色大片免费观看 | 国产精品久久久久无码av色戒 | 国产精品国产三级国产专播 | 人妻少妇精品无码专区动漫 | 国产一区二区三区精品视频 | 久久无码专区国产精品s | 丝袜 中出 制服 人妻 美腿 | 少妇无码av无码专区在线观看 | 日韩av无码一区二区三区不卡 | 日韩亚洲欧美精品综合 | 国产精品香蕉在线观看 | 亚洲の无码国产の无码影院 | 精品久久久无码人妻字幂 | 亚洲成av人影院在线观看 | 亚洲成色在线综合网站 | 久久久久av无码免费网 | 亚洲人成无码网www | 97久久精品无码一区二区 | 亚洲gv猛男gv无码男同 | 国产综合久久久久鬼色 | 老头边吃奶边弄进去呻吟 | 国产香蕉97碰碰久久人人 | 日日碰狠狠躁久久躁蜜桃 | 日日摸日日碰夜夜爽av | 鲁鲁鲁爽爽爽在线视频观看 | 国产日产欧产精品精品app | 日韩欧美中文字幕在线三区 | 国模大胆一区二区三区 | 福利一区二区三区视频在线观看 | 久久99精品国产.久久久久 | 亚洲伊人久久精品影院 | 伊人久久婷婷五月综合97色 | 国产电影无码午夜在线播放 | 国产乱人无码伦av在线a | 日韩精品无码免费一区二区三区 | 国产特级毛片aaaaaa高潮流水 | 日韩av无码一区二区三区不卡 | 亚洲人成网站在线播放942 | 天天爽夜夜爽夜夜爽 | 国产精品99爱免费视频 | 亚洲 a v无 码免 费 成 人 a v | 欧美性黑人极品hd | 人人妻人人藻人人爽欧美一区 | 精品少妇爆乳无码av无码专区 | 日韩精品a片一区二区三区妖精 | 国产乱人伦偷精品视频 | 少妇性l交大片欧洲热妇乱xxx | 国产一区二区不卡老阿姨 | 99视频精品全部免费免费观看 | 欧美兽交xxxx×视频 | 丰满少妇女裸体bbw | 麻豆果冻传媒2021精品传媒一区下载 | 日日摸夜夜摸狠狠摸婷婷 | 成人免费视频在线观看 | 荫蒂被男人添的好舒服爽免费视频 | 精品国精品国产自在久国产87 | 蜜桃视频插满18在线观看 | 中文无码成人免费视频在线观看 | 国产精品无码一区二区桃花视频 | 特黄特色大片免费播放器图片 | 中文字幕无线码免费人妻 | 美女极度色诱视频国产 | 伊人久久大香线蕉亚洲 | 久久国产精品偷任你爽任你 | 300部国产真实乱 | 成人亚洲精品久久久久 | 久久久久久九九精品久 | 少妇太爽了在线观看 | 亚洲一区二区三区 | aⅴ在线视频男人的天堂 | 国产乱人偷精品人妻a片 | 无码人妻精品一区二区三区下载 | 亚洲国产精品一区二区第一页 | 国产 浪潮av性色四虎 | 亚洲国产成人a精品不卡在线 | 最近中文2019字幕第二页 | 久久国产精品精品国产色婷婷 | 西西人体www44rt大胆高清 | 狂野欧美性猛xxxx乱大交 | 久久精品国产99久久6动漫 | 亚洲伊人久久精品影院 | 99久久人妻精品免费二区 | 中国女人内谢69xxxxxa片 | 国产热a欧美热a在线视频 | 2019nv天堂香蕉在线观看 | 国产乱子伦视频在线播放 | 小泽玛莉亚一区二区视频在线 | 久久精品中文闷骚内射 | 又大又紧又粉嫩18p少妇 | 呦交小u女精品视频 | 欧美阿v高清资源不卡在线播放 | 国产熟女一区二区三区四区五区 | 亚洲理论电影在线观看 | 成熟女人特级毛片www免费 | 欧洲欧美人成视频在线 | 精品国产一区二区三区四区 | 国産精品久久久久久久 | 亚洲中文字幕无码中字 | 色综合久久88色综合天天 | 一个人看的视频www在线 | 欧美老妇与禽交 | 精品国产青草久久久久福利 | 欧美野外疯狂做受xxxx高潮 | 亚洲熟妇色xxxxx欧美老妇 | 久热国产vs视频在线观看 | 久久午夜无码鲁丝片午夜精品 | 亚洲人成网站在线播放942 | 国产精品久久久久无码av色戒 | 正在播放东北夫妻内射 | 东京热无码av男人的天堂 | 夜夜躁日日躁狠狠久久av | 无码国产色欲xxxxx视频 | 国产成人无码专区 | 日本www一道久久久免费榴莲 | 中国女人内谢69xxxx | 麻豆精产国品 | 波多野结衣aⅴ在线 | 日产国产精品亚洲系列 | 国产手机在线αⅴ片无码观看 | 日产国产精品亚洲系列 | 又紧又大又爽精品一区二区 | 日韩精品无码免费一区二区三区 | 欧美黑人性暴力猛交喷水 | 久久精品人人做人人综合试看 | 中文字幕无码免费久久99 | 高潮毛片无遮挡高清免费视频 | 东京无码熟妇人妻av在线网址 | 无码精品人妻一区二区三区av | 国产极品美女高潮无套在线观看 | 国产 精品 自在自线 | 亚洲精品中文字幕乱码 | 久久婷婷五月综合色国产香蕉 | 少妇邻居内射在线 | 少妇无码一区二区二三区 | 纯爱无遮挡h肉动漫在线播放 | 色综合久久88色综合天天 | 中文字幕中文有码在线 | 在线播放无码字幕亚洲 | 色综合久久中文娱乐网 | 波多野结衣av在线观看 | 久久99精品国产麻豆 | 久久久久久a亚洲欧洲av冫 | 福利一区二区三区视频在线观看 | 午夜时刻免费入口 | av人摸人人人澡人人超碰下载 | 一个人免费观看的www视频 | 无码av中文字幕免费放 | 伊人色综合久久天天小片 | 日韩av无码中文无码电影 | 亚欧洲精品在线视频免费观看 | 亚洲午夜久久久影院 | 色婷婷av一区二区三区之红樱桃 | 久久久久人妻一区精品色欧美 | 国产精品无码mv在线观看 | 色噜噜亚洲男人的天堂 | 欧美精品无码一区二区三区 | 久久 国产 尿 小便 嘘嘘 | 国产精品无码mv在线观看 | 自拍偷自拍亚洲精品被多人伦好爽 | 亚洲中文字幕在线无码一区二区 | 午夜福利不卡在线视频 | 亚洲男女内射在线播放 | 久精品国产欧美亚洲色aⅴ大片 | 鲁一鲁av2019在线 | 国产精品久久久一区二区三区 | 男女作爱免费网站 | 毛片内射-百度 | 国产午夜福利亚洲第一 | 精品国产av色一区二区深夜久久 | 久久精品国产大片免费观看 | 免费人成在线视频无码 | 欧美大屁股xxxxhd黑色 | 国产激情一区二区三区 | 久久综合九色综合97网 | 日韩av无码一区二区三区 | 国内综合精品午夜久久资源 | 国产黑色丝袜在线播放 | 亚洲精品午夜无码电影网 | 久久久久久久久蜜桃 | 天天燥日日燥 | 青青青手机频在线观看 | 欧美高清在线精品一区 | 天海翼激烈高潮到腰振不止 | 久精品国产欧美亚洲色aⅴ大片 | 秋霞成人午夜鲁丝一区二区三区 | 人妻熟女一区 | 日韩欧美中文字幕在线三区 | 鲁大师影院在线观看 | 亚洲午夜福利在线观看 | 鲁一鲁av2019在线 | 亚洲精品国产精品乱码视色 | 无遮无挡爽爽免费视频 | 国产成人精品无码播放 | 亚洲 日韩 欧美 成人 在线观看 | 欧美性猛交xxxx富婆 | 中文字幕乱妇无码av在线 | 红桃av一区二区三区在线无码av | 国产69精品久久久久app下载 | 国产激情综合五月久久 | 真人与拘做受免费视频 | 人妻夜夜爽天天爽三区 | 少妇被黑人到高潮喷出白浆 | 无码乱肉视频免费大全合集 | 99久久人妻精品免费二区 | 国产suv精品一区二区五 | 欧美一区二区三区视频在线观看 | 国产精品国产自线拍免费软件 | 亚洲精品国偷拍自产在线麻豆 | 中文字幕乱码中文乱码51精品 | 玩弄人妻少妇500系列视频 | 亚洲成av人影院在线观看 | 夜夜躁日日躁狠狠久久av | 日韩精品无码一本二本三本色 | 日本乱人伦片中文三区 | 波多野结衣av在线观看 | 日韩精品一区二区av在线 | 无码福利日韩神码福利片 | 九九热爱视频精品 | 精品乱码久久久久久久 | 377p欧洲日本亚洲大胆 | 玩弄人妻少妇500系列视频 | 丰满少妇弄高潮了www | 台湾无码一区二区 | 天堂久久天堂av色综合 | 免费看男女做好爽好硬视频 | 日韩视频 中文字幕 视频一区 | 俺去俺来也www色官网 | 成人欧美一区二区三区黑人 | 成人动漫在线观看 | 亚洲人亚洲人成电影网站色 | 久久99热只有频精品8 | 亚洲成a人片在线观看日本 | 亚洲精品一区三区三区在线观看 | 国产精品多人p群无码 | 国产熟女一区二区三区四区五区 | 欧美xxxx黑人又粗又长 | 亚洲国精产品一二二线 | 国产在线aaa片一区二区99 | 兔费看少妇性l交大片免费 | 18无码粉嫩小泬无套在线观看 | 色欲人妻aaaaaaa无码 | 无码午夜成人1000部免费视频 | 高清国产亚洲精品自在久久 | 国产网红无码精品视频 | 曰本女人与公拘交酡免费视频 | 亚洲精品国偷拍自产在线观看蜜桃 | 人人妻人人澡人人爽欧美一区九九 | 精品无码一区二区三区爱欲 | 黄网在线观看免费网站 | 欧美日本免费一区二区三区 | 国产精品无码成人午夜电影 | 欧美亚洲国产一区二区三区 | 色一情一乱一伦一视频免费看 | 黑人玩弄人妻中文在线 | 婷婷丁香六月激情综合啪 | 久久精品国产精品国产精品污 | 国产成人综合在线女婷五月99播放 | 激情内射亚州一区二区三区爱妻 | 男人和女人高潮免费网站 | 亚洲爆乳大丰满无码专区 | 无人区乱码一区二区三区 | 99精品久久毛片a片 | 久久人人爽人人爽人人片av高清 | 日日摸夜夜摸狠狠摸婷婷 | 日本丰满熟妇videos | 国产深夜福利视频在线 | 黑人巨大精品欧美一区二区 | 亚洲精品国偷拍自产在线观看蜜桃 | 久久国内精品自在自线 | 亚洲精品久久久久久一区二区 | 性欧美疯狂xxxxbbbb | 亚洲欧洲日本综合aⅴ在线 | 亚洲成熟女人毛毛耸耸多 | 亚洲中文字幕在线无码一区二区 | 日本高清一区免费中文视频 | 色综合久久久久综合一本到桃花网 | 精品国精品国产自在久国产87 | √天堂中文官网8在线 | 欧美丰满少妇xxxx性 | 性欧美大战久久久久久久 | 亚洲小说春色综合另类 | 亚洲精品欧美二区三区中文字幕 | 少妇无码一区二区二三区 | 欧美激情一区二区三区成人 | 美女极度色诱视频国产 | 沈阳熟女露脸对白视频 | 久久精品成人欧美大片 | 日韩精品无码免费一区二区三区 | 夫妻免费无码v看片 | 99国产精品白浆在线观看免费 | 精品久久综合1区2区3区激情 | 亚洲精品一区二区三区婷婷月 | 国产精品久久久久久无码 | 国产精品久久久久久久影院 | 牲欲强的熟妇农村老妇女 | 国产精品高潮呻吟av久久 | av无码久久久久不卡免费网站 | 国产手机在线αⅴ片无码观看 | 乱中年女人伦av三区 | 中文字幕无码免费久久99 | 欧美freesex黑人又粗又大 | 又粗又大又硬又长又爽 | 欧洲极品少妇 | 国产精品亚洲一区二区三区喷水 | 久久天天躁夜夜躁狠狠 | 久久天天躁狠狠躁夜夜免费观看 | 国产免费观看黄av片 | 欧美激情一区二区三区成人 | www国产亚洲精品久久久日本 | 久久久国产一区二区三区 | 中文字幕精品av一区二区五区 | 亚洲a无码综合a国产av中文 | 中文字幕av伊人av无码av | 国产精品久久久久久无码 | 乱码午夜-极国产极内射 | 麻豆国产人妻欲求不满 | 熟女少妇人妻中文字幕 | 在线欧美精品一区二区三区 | 亚洲成熟女人毛毛耸耸多 | 亚洲熟妇色xxxxx欧美老妇 | 久久精品人人做人人综合 | 国产精品成人av在线观看 | 99久久人妻精品免费一区 | 任你躁国产自任一区二区三区 | 内射爽无广熟女亚洲 | 国产精品久久久久久亚洲影视内衣 | 麻豆av传媒蜜桃天美传媒 | 最近免费中文字幕中文高清百度 | 欧美熟妇另类久久久久久多毛 | 波多野结衣 黑人 | 大地资源中文第3页 | 十八禁真人啪啪免费网站 | 亚洲经典千人经典日产 | 牛和人交xxxx欧美 | 亚洲国产精品久久久天堂 | 国产超碰人人爽人人做人人添 | 欧美日本免费一区二区三区 | 三上悠亚人妻中文字幕在线 | 波多野结衣乳巨码无在线观看 | 亚洲第一网站男人都懂 | 日日橹狠狠爱欧美视频 | 青青草原综合久久大伊人精品 | 久久久久久久久蜜桃 | 亚洲综合色区中文字幕 | av无码电影一区二区三区 | 国产精品多人p群无码 | 三上悠亚人妻中文字幕在线 | 少妇性l交大片欧洲热妇乱xxx | 久久亚洲精品中文字幕无男同 | 超碰97人人射妻 | 国产精品美女久久久久av爽李琼 | 天堂亚洲免费视频 | 国产亚洲人成在线播放 | 狠狠色噜噜狠狠狠狠7777米奇 | 久久午夜无码鲁丝片午夜精品 | 学生妹亚洲一区二区 | 乱人伦人妻中文字幕无码 | 亚洲国产成人a精品不卡在线 | 精品国产av色一区二区深夜久久 | 亚洲精品一区二区三区四区五区 | 无码毛片视频一区二区本码 | 亚洲欧美日韩综合久久久 | 丰满少妇弄高潮了www | 熟妇女人妻丰满少妇中文字幕 | 狠狠亚洲超碰狼人久久 | 丰满少妇人妻久久久久久 | 国产亚洲精品久久久久久久久动漫 | a片免费视频在线观看 | 妺妺窝人体色www在线小说 | 一本大道伊人av久久综合 | 欧美阿v高清资源不卡在线播放 | 久久午夜夜伦鲁鲁片无码免费 | 无码播放一区二区三区 | 中文字幕av日韩精品一区二区 | 老头边吃奶边弄进去呻吟 | 亚洲人亚洲人成电影网站色 | 成人无码视频免费播放 | 天堂亚洲2017在线观看 | 蜜桃无码一区二区三区 | 乱码午夜-极国产极内射 | 又黄又爽又色的视频 | 老头边吃奶边弄进去呻吟 | 国产av久久久久精东av | 国产后入清纯学生妹 | 大乳丰满人妻中文字幕日本 | 午夜无码人妻av大片色欲 | 夜夜躁日日躁狠狠久久av | 一本久道高清无码视频 | 久久综合九色综合97网 | 久久久久av无码免费网 | 亚洲第一网站男人都懂 | 色综合久久久久综合一本到桃花网 | 久久伊人色av天堂九九小黄鸭 | 亚洲精品一区二区三区在线 | 一本加勒比波多野结衣 | 久久99精品久久久久婷婷 | 中文字幕乱码中文乱码51精品 | 久久精品中文字幕一区 | 国产一区二区三区四区五区加勒比 | 中国女人内谢69xxxxxa片 | 日韩av无码一区二区三区 | 少妇人妻大乳在线视频 | 中文字幕 亚洲精品 第1页 | 国产xxx69麻豆国语对白 | 超碰97人人做人人爱少妇 | 欧美丰满少妇xxxx性 | 欧美亚洲日韩国产人成在线播放 | 亚洲一区二区三区 | 午夜精品一区二区三区在线观看 | 影音先锋中文字幕无码 | 爽爽影院免费观看 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 婷婷五月综合缴情在线视频 | 四虎影视成人永久免费观看视频 | 欧美黑人性暴力猛交喷水 | 乌克兰少妇xxxx做受 | 日本精品高清一区二区 | 中文精品无码中文字幕无码专区 | 日韩在线不卡免费视频一区 | 国产人妻精品午夜福利免费 | 久久亚洲中文字幕精品一区 | 18精品久久久无码午夜福利 | 色五月丁香五月综合五月 | 国产精品久久久久久亚洲影视内衣 | 国产特级毛片aaaaaa高潮流水 | 女人和拘做爰正片视频 | 国产精品久免费的黄网站 | 中文字幕无码免费久久9一区9 | 人妻插b视频一区二区三区 | 67194成是人免费无码 | 免费无码肉片在线观看 | 日日天干夜夜狠狠爱 | 精品少妇爆乳无码av无码专区 | 国产精品人人妻人人爽 | 成人精品天堂一区二区三区 | 夜夜夜高潮夜夜爽夜夜爰爰 | 国产黑色丝袜在线播放 | 无码国模国产在线观看 | 久久国产精品_国产精品 | 精品人妻中文字幕有码在线 | 强开小婷嫩苞又嫩又紧视频 | 一本色道久久综合亚洲精品不卡 | 日本精品人妻无码免费大全 | 国产亚洲视频中文字幕97精品 | 激情亚洲一区国产精品 | 麻豆人妻少妇精品无码专区 | 色综合久久久无码中文字幕 | 国产真实乱对白精彩久久 | 夜精品a片一区二区三区无码白浆 | 久久精品丝袜高跟鞋 | 日韩精品无码免费一区二区三区 | 国产精品久免费的黄网站 | 人人妻人人藻人人爽欧美一区 | 成人免费视频视频在线观看 免费 | 亚洲大尺度无码无码专区 | 欧美日韩综合一区二区三区 | 男女猛烈xx00免费视频试看 | 国产国产精品人在线视 | 亚洲区欧美区综合区自拍区 | 日韩人妻无码中文字幕视频 | 免费观看黄网站 | 亚洲中文字幕成人无码 | 高清国产亚洲精品自在久久 | 亚洲欧美日韩综合久久久 | 黑人巨大精品欧美一区二区 | 乱人伦中文视频在线观看 | 撕开奶罩揉吮奶头视频 | 久久精品成人欧美大片 | 熟女俱乐部五十路六十路av | 亚洲精品综合五月久久小说 | 小泽玛莉亚一区二区视频在线 | 中文字幕 人妻熟女 | 国产精品久久久 | 少妇人妻av毛片在线看 | 少妇性l交大片欧洲热妇乱xxx | 国产熟妇另类久久久久 | 日本一本二本三区免费 | 3d动漫精品啪啪一区二区中 | 一二三四社区在线中文视频 | 欧美兽交xxxx×视频 | 国精产品一品二品国精品69xx | 国产精品福利视频导航 | 人人妻人人澡人人爽欧美一区 | 两性色午夜免费视频 | 天天摸天天透天天添 | 性开放的女人aaa片 | 午夜嘿嘿嘿影院 | 亚洲 高清 成人 动漫 | 久久久精品欧美一区二区免费 | 丰腴饱满的极品熟妇 | 熟妇女人妻丰满少妇中文字幕 | 无遮挡啪啪摇乳动态图 | 中文字幕日韩精品一区二区三区 | 国产色xx群视频射精 | 撕开奶罩揉吮奶头视频 | 四虎4hu永久免费 | 久久 国产 尿 小便 嘘嘘 | 国产午夜福利亚洲第一 | 无码人妻av免费一区二区三区 | 久久天天躁狠狠躁夜夜免费观看 | 日本饥渴人妻欲求不满 | 免费中文字幕日韩欧美 | 国产精品香蕉在线观看 | 狂野欧美性猛交免费视频 | 丝袜美腿亚洲一区二区 | 国产精品欧美成人 | 国内精品久久毛片一区二区 | 香港三级日本三级妇三级 | 亚洲午夜福利在线观看 | 特级做a爰片毛片免费69 | 亚洲国产成人a精品不卡在线 | 亚洲精品中文字幕乱码 | 99精品久久毛片a片 | 中文字幕人妻丝袜二区 | 亚洲性无码av中文字幕 | 日本精品少妇一区二区三区 | 国产suv精品一区二区五 | 午夜福利不卡在线视频 | 久久精品视频在线看15 | 曰本女人与公拘交酡免费视频 | 爽爽影院免费观看 | 久久精品女人天堂av免费观看 | 亚洲日韩av一区二区三区中文 | 成人动漫在线观看 | 国产成人综合在线女婷五月99播放 | 亚洲国产精品一区二区第一页 | 人人妻人人澡人人爽欧美精品 | 精品国产一区二区三区四区在线看 | 日本熟妇人妻xxxxx人hd | a在线观看免费网站大全 | 高清不卡一区二区三区 | a片免费视频在线观看 | 美女黄网站人色视频免费国产 | 露脸叫床粗话东北少妇 | 乱中年女人伦av三区 | 无码帝国www无码专区色综合 | 国产真实乱对白精彩久久 | 国产精品高潮呻吟av久久4虎 | 18精品久久久无码午夜福利 | 亚洲精品综合五月久久小说 | 麻豆蜜桃av蜜臀av色欲av | 国产区女主播在线观看 | 精品一区二区三区波多野结衣 | 久久99久久99精品中文字幕 | 岛国片人妻三上悠亚 | 亚洲经典千人经典日产 | 欧美日韩一区二区免费视频 | 国产一区二区不卡老阿姨 | 妺妺窝人体色www婷婷 | 成人av无码一区二区三区 | 欧美野外疯狂做受xxxx高潮 | 中文字幕av日韩精品一区二区 | 亚洲精品国产第一综合99久久 | 国语自产偷拍精品视频偷 | 国产成人无码a区在线观看视频app | 人妻中文无码久热丝袜 | 亚洲春色在线视频 | 国产成人精品优优av | 青青青爽视频在线观看 | 亚洲色欲久久久综合网东京热 | 日韩精品a片一区二区三区妖精 | 无码午夜成人1000部免费视频 | 无码成人精品区在线观看 | 欧美日韩人成综合在线播放 | 亚洲色偷偷男人的天堂 | 精品国产一区二区三区四区 | 中文字幕+乱码+中文字幕一区 | 国产97在线 | 亚洲 | 99久久精品日本一区二区免费 | 日韩欧美中文字幕在线三区 | 狠狠噜狠狠狠狠丁香五月 | 夜夜影院未满十八勿进 | 精品欧美一区二区三区久久久 | 国产精品爱久久久久久久 | 中文字幕乱码中文乱码51精品 | 国产香蕉尹人视频在线 | 日日摸天天摸爽爽狠狠97 | 欧美成人午夜精品久久久 | 丰满妇女强制高潮18xxxx | 亚洲爆乳精品无码一区二区三区 | 婷婷色婷婷开心五月四房播播 | 国产精华av午夜在线观看 | 国内揄拍国内精品少妇国语 | 国产精品久久久久9999小说 | 熟妇人妻激情偷爽文 | 99riav国产精品视频 | av人摸人人人澡人人超碰下载 | 人人超人人超碰超国产 | 国产麻豆精品一区二区三区v视界 | 秋霞成人午夜鲁丝一区二区三区 | 一个人看的www免费视频在线观看 | 久久久久人妻一区精品色欧美 | 帮老师解开蕾丝奶罩吸乳网站 | 内射爽无广熟女亚洲 | 亚洲va欧美va天堂v国产综合 | 亚洲一区二区三区国产精华液 | 久久久久成人片免费观看蜜芽 | 亚洲最大成人网站 | 成人一在线视频日韩国产 | 给我免费的视频在线观看 | 久热国产vs视频在线观看 | 鲁鲁鲁爽爽爽在线视频观看 | 麻豆国产人妻欲求不满谁演的 | 欧美亚洲国产一区二区三区 | 一本色道久久综合亚洲精品不卡 | 亚欧洲精品在线视频免费观看 | 国产两女互慰高潮视频在线观看 | 蜜臀av在线播放 久久综合激激的五月天 | 亚洲日韩一区二区 | 一本大道久久东京热无码av | 欧美人与牲动交xxxx | 亚洲国产精品久久久天堂 | 国产乱人偷精品人妻a片 | 久久99精品久久久久久动态图 | 日韩欧美成人免费观看 | 国产精品久久久一区二区三区 | 久精品国产欧美亚洲色aⅴ大片 | 亚洲人成人无码网www国产 | 亚洲日韩av一区二区三区四区 | 无码国产乱人伦偷精品视频 | 天堂亚洲免费视频 | 少妇人妻偷人精品无码视频 | 国产午夜无码精品免费看 | 日日躁夜夜躁狠狠躁 | 亚洲一区二区三区播放 | 国产热a欧美热a在线视频 | 精品国产麻豆免费人成网站 | 欧美自拍另类欧美综合图片区 | 国产在线精品一区二区三区直播 | 亚洲综合无码一区二区三区 | 亚洲日韩一区二区 | 精品人妻人人做人人爽 | 无码人妻久久一区二区三区不卡 | 熟女俱乐部五十路六十路av | 妺妺窝人体色www婷婷 | 欧美三级不卡在线观看 | 曰本女人与公拘交酡免费视频 | 性开放的女人aaa片 | 日本va欧美va欧美va精品 | 老司机亚洲精品影院无码 | 大肉大捧一进一出视频出来呀 | 7777奇米四色成人眼影 | 天天摸天天碰天天添 | 一本色道久久综合亚洲精品不卡 | 精品国产成人一区二区三区 | 亚洲一区二区三区播放 | 九九在线中文字幕无码 | 久久国产精品萌白酱免费 | 国产精品资源一区二区 | 欧美丰满老熟妇xxxxx性 | 精品国产青草久久久久福利 | 最近的中文字幕在线看视频 | 日本饥渴人妻欲求不满 | 色欲人妻aaaaaaa无码 | 国产乱码精品一品二品 | 青青久在线视频免费观看 | 一个人看的www免费视频在线观看 | 亚洲男人av天堂午夜在 | 国内揄拍国内精品少妇国语 | 成人免费视频在线观看 | 樱花草在线社区www | 国产亚洲日韩欧美另类第八页 | 色婷婷av一区二区三区之红樱桃 | 亚洲精品久久久久avwww潮水 | 动漫av网站免费观看 | 三上悠亚人妻中文字幕在线 | 亚拍精品一区二区三区探花 | 啦啦啦www在线观看免费视频 | 图片区 小说区 区 亚洲五月 | 亚洲欧美精品aaaaaa片 | 国产精品美女久久久网av | 熟女少妇人妻中文字幕 | 欧美性生交xxxxx久久久 | 麻豆国产97在线 | 欧洲 | 亚洲色www成人永久网址 | 少妇人妻av毛片在线看 | 美女黄网站人色视频免费国产 | 国产色视频一区二区三区 | 成人片黄网站色大片免费观看 | 中文字幕乱妇无码av在线 | 又紧又大又爽精品一区二区 | 激情五月综合色婷婷一区二区 | 东京热男人av天堂 | 国产三级精品三级男人的天堂 | 领导边摸边吃奶边做爽在线观看 | 扒开双腿疯狂进出爽爽爽视频 | 色五月丁香五月综合五月 | 人妻互换免费中文字幕 | 99精品国产综合久久久久五月天 | 国产区女主播在线观看 | 狠狠cao日日穞夜夜穞av | 丰满人妻精品国产99aⅴ | 四虎4hu永久免费 | 好爽又高潮了毛片免费下载 | 色狠狠av一区二区三区 | 久久午夜无码鲁丝片午夜精品 | 色 综合 欧美 亚洲 国产 | 国产真实乱对白精彩久久 | 97夜夜澡人人爽人人喊中国片 | 无套内射视频囯产 | 日本肉体xxxx裸交 | 少妇性俱乐部纵欲狂欢电影 | 99riav国产精品视频 | 国产精品亚洲一区二区三区喷水 | 天堂а√在线中文在线 | 亚洲中文字幕无码中文字在线 | 天天摸天天透天天添 | 精品夜夜澡人妻无码av蜜桃 | 无套内谢老熟女 | 午夜成人1000部免费视频 | 色五月丁香五月综合五月 | 国产精品二区一区二区aⅴ污介绍 | 18禁止看的免费污网站 | 超碰97人人射妻 | 九九热爱视频精品 | 国产麻豆精品精东影业av网站 | 中文字幕无线码 | 97久久国产亚洲精品超碰热 | 午夜精品一区二区三区在线观看 | 久久亚洲精品中文字幕无男同 | 成人av无码一区二区三区 | 久久人人爽人人人人片 | 婷婷色婷婷开心五月四房播播 | 中文字幕无码乱人伦 | 99久久久无码国产精品免费 | 国产乱人伦av在线无码 | 国产亚洲精品久久久久久国模美 | 国产熟妇高潮叫床视频播放 | 欧美日韩精品 | www国产亚洲精品久久久日本 | 日本欧美一区二区三区乱码 | 国产情侣作爱视频免费观看 | 中国女人内谢69xxxxxa片 | 夜夜高潮次次欢爽av女 | 欧美xxxx黑人又粗又长 | 性欧美熟妇videofreesex | 55夜色66夜色国产精品视频 | 一本色道久久综合狠狠躁 | 亚洲 a v无 码免 费 成 人 a v | 天堂无码人妻精品一区二区三区 | 日韩av无码一区二区三区不卡 | 精品国偷自产在线 | 精品一区二区三区波多野结衣 | 久久久久99精品成人片 | 亚无码乱人伦一区二区 | 帮老师解开蕾丝奶罩吸乳网站 | 亚洲精品国偷拍自产在线麻豆 | 亚洲热妇无码av在线播放 | 国产农村妇女高潮大叫 | 亚洲成色在线综合网站 | 日韩精品无码免费一区二区三区 | 97人妻精品一区二区三区 | 伊人久久大香线蕉亚洲 | 国产精品久久久久久亚洲毛片 | 日本肉体xxxx裸交 | 丰满人妻一区二区三区免费视频 | 青草视频在线播放 | 十八禁视频网站在线观看 | 亚洲va中文字幕无码久久不卡 | 国产在线无码精品电影网 | 亚洲精品中文字幕 | 中文字幕亚洲情99在线 | 国产亚洲精品久久久久久久久动漫 | 思思久久99热只有频精品66 | 精品无码国产一区二区三区av | 国产精品久久久久7777 | 无人区乱码一区二区三区 | 国产三级精品三级男人的天堂 | 国产精品va在线播放 | 亚洲国产精品久久久久久 | 一本无码人妻在中文字幕免费 | а天堂中文在线官网 | 婷婷丁香六月激情综合啪 | 久久伊人色av天堂九九小黄鸭 | 性色欲情网站iwww九文堂 | 水蜜桃亚洲一二三四在线 | 强奷人妻日本中文字幕 | 久久精品中文字幕一区 | 精品欧美一区二区三区久久久 | 成人无码视频免费播放 | 中文字幕无码日韩专区 | 四虎影视成人永久免费观看视频 | 日日夜夜撸啊撸 | 亚洲一区av无码专区在线观看 | 日本熟妇人妻xxxxx人hd | 亚洲va中文字幕无码久久不卡 | 亚洲va中文字幕无码久久不卡 | 欧美日韩在线亚洲综合国产人 | 一个人免费观看的www视频 | 人人妻人人澡人人爽欧美一区九九 | 丁香啪啪综合成人亚洲 | 国产精品久久久久久久影院 | 樱花草在线社区www | 人妻无码αv中文字幕久久琪琪布 | 成人免费视频一区二区 | 无码成人精品区在线观看 | 国产精品资源一区二区 | 国产熟妇高潮叫床视频播放 | 日本丰满熟妇videos | 天天爽夜夜爽夜夜爽 | 一本大道伊人av久久综合 | 麻豆精品国产精华精华液好用吗 | 亚洲精品成人福利网站 | 国产精品亚洲lv粉色 | 国产熟妇高潮叫床视频播放 | 精品人妻av区 | 婷婷色婷婷开心五月四房播播 | 国产激情无码一区二区 | 一区二区三区高清视频一 | 国产午夜亚洲精品不卡 | 国产精品无码一区二区桃花视频 | 国产av人人夜夜澡人人爽麻豆 | 成人欧美一区二区三区 | 国产无遮挡又黄又爽又色 | 午夜福利电影 | 国产乱人伦av在线无码 | 亚洲成a人片在线观看无码3d | 国産精品久久久久久久 | 中文字幕乱妇无码av在线 | 扒开双腿疯狂进出爽爽爽视频 | 国产精品无码mv在线观看 | 67194成是人免费无码 | 又黄又爽又色的视频 | 精品成人av一区二区三区 | 久久国产精品萌白酱免费 | 亚洲欧洲日本综合aⅴ在线 | 六十路熟妇乱子伦 | 日日天日日夜日日摸 | 国产精品无码久久av | 欧美 日韩 人妻 高清 中文 | 熟妇人妻无乱码中文字幕 | 丁香花在线影院观看在线播放 | 亚洲精品一区二区三区婷婷月 | 欧美 丝袜 自拍 制服 另类 | 国产女主播喷水视频在线观看 | 亚洲成色在线综合网站 | 国产精品理论片在线观看 | 精品夜夜澡人妻无码av蜜桃 | 日韩精品一区二区av在线 | 一本一道久久综合久久 | 国产精品福利视频导航 | 内射白嫩少妇超碰 | 色综合视频一区二区三区 | 少妇被粗大的猛进出69影院 | 亚洲精品无码人妻无码 | 99久久亚洲精品无码毛片 | 国产成人精品必看 | 国产真实夫妇视频 | 日本爽爽爽爽爽爽在线观看免 | 99久久久国产精品无码免费 | 九九热爱视频精品 | 久久久久久久女国产乱让韩 | aa片在线观看视频在线播放 | 红桃av一区二区三区在线无码av | 少妇无码av无码专区在线观看 | 国产无遮挡又黄又爽免费视频 | 成人欧美一区二区三区黑人 | 亚洲国产欧美国产综合一区 | 学生妹亚洲一区二区 | 综合人妻久久一区二区精品 | 无码一区二区三区在线观看 | 性欧美疯狂xxxxbbbb | 色妞www精品免费视频 | 国产 精品 自在自线 | 狂野欧美性猛交免费视频 | 水蜜桃av无码 | 牲交欧美兽交欧美 | 无码免费一区二区三区 | 成人免费视频视频在线观看 免费 | 性色av无码免费一区二区三区 | 99久久精品无码一区二区毛片 | 午夜不卡av免费 一本久久a久久精品vr综合 | 亚洲小说图区综合在线 | 久久久精品欧美一区二区免费 | 色一情一乱一伦一区二区三欧美 | 国产成人综合色在线观看网站 | 给我免费的视频在线观看 | 国产va免费精品观看 | 青春草在线视频免费观看 | 日本爽爽爽爽爽爽在线观看免 | 国内精品九九久久久精品 | 亚洲经典千人经典日产 | 国产精品人人妻人人爽 | 欧美日韩在线亚洲综合国产人 | 国产 浪潮av性色四虎 | 精品无码av一区二区三区 | 久久aⅴ免费观看 | 兔费看少妇性l交大片免费 | 人人妻人人藻人人爽欧美一区 | 久久亚洲a片com人成 | 老太婆性杂交欧美肥老太 | 亚洲国产欧美国产综合一区 | 国产精品-区区久久久狼 | 免费无码一区二区三区蜜桃大 | 亚洲日韩中文字幕在线播放 | 国产无av码在线观看 | 狠狠亚洲超碰狼人久久 | 免费无码午夜福利片69 | 国产熟妇高潮叫床视频播放 | 3d动漫精品啪啪一区二区中 | 麻豆蜜桃av蜜臀av色欲av | 成人免费视频一区二区 | 久久精品人人做人人综合 | 国产精品久久久午夜夜伦鲁鲁 | 亚洲熟熟妇xxxx | 一本久久伊人热热精品中文字幕 | 日本护士毛茸茸高潮 | 亚洲精品国产精品乱码视色 | 天干天干啦夜天干天2017 | 夜精品a片一区二区三区无码白浆 | 日日碰狠狠躁久久躁蜜桃 | 欧美人与动性行为视频 | 嫩b人妻精品一区二区三区 | 中文字幕人成乱码熟女app | 欧美日韩一区二区三区自拍 | 精品亚洲韩国一区二区三区 | 97精品国产97久久久久久免费 | 美女极度色诱视频国产 | 国产精品无码一区二区三区不卡 | ass日本丰满熟妇pics | 4hu四虎永久在线观看 | 欧美精品在线观看 | 久久亚洲国产成人精品性色 | 玩弄中年熟妇正在播放 | 国内综合精品午夜久久资源 | 免费无码一区二区三区蜜桃大 | 精品国产精品久久一区免费式 | 精品久久久久久人妻无码中文字幕 | 乱人伦人妻中文字幕无码久久网 | 永久免费观看美女裸体的网站 | 2020久久香蕉国产线看观看 | 精品国产一区二区三区四区在线看 | av无码电影一区二区三区 | 亚洲色偷偷男人的天堂 | 亚洲一区av无码专区在线观看 | 学生妹亚洲一区二区 | 天天爽夜夜爽夜夜爽 | 日产精品99久久久久久 | 亚洲国产精品一区二区第一页 | 男女下面进入的视频免费午夜 | 亚洲另类伦春色综合小说 | 亚洲熟妇色xxxxx亚洲 | 国产成人综合美国十次 | 丰满诱人的人妻3 | 午夜熟女插插xx免费视频 | 久久aⅴ免费观看 | 亚洲国产精品久久人人爱 | 久久久久se色偷偷亚洲精品av | 国产成人无码区免费内射一片色欲 | 黑人巨大精品欧美一区二区 | 日日噜噜噜噜夜夜爽亚洲精品 | 男女性色大片免费网站 | 性做久久久久久久免费看 | 强开小婷嫩苞又嫩又紧视频 | 成熟人妻av无码专区 | 一区二区三区乱码在线 | 欧洲 | 亚洲一区二区三区四区 | 久久综合激激的五月天 | 国产精品久久久久久久影院 | aa片在线观看视频在线播放 | 九九在线中文字幕无码 | 中文无码精品a∨在线观看不卡 | 帮老师解开蕾丝奶罩吸乳网站 | 黑人巨大精品欧美一区二区 | 丰满人妻精品国产99aⅴ | 乱码av麻豆丝袜熟女系列 | 国产亚洲视频中文字幕97精品 | 欧美性猛交内射兽交老熟妇 | 男人的天堂2018无码 | 久久久精品成人免费观看 | 粗大的内捧猛烈进出视频 | 亚洲中文字幕成人无码 | 无码国产乱人伦偷精品视频 | 黄网在线观看免费网站 | 国产成人亚洲综合无码 | 婷婷色婷婷开心五月四房播播 | 亚洲综合色区中文字幕 | 亚洲综合色区中文字幕 | 永久免费观看美女裸体的网站 | 99久久精品午夜一区二区 | 丰满妇女强制高潮18xxxx | 亚洲国产av精品一区二区蜜芽 | 久久久精品人妻久久影视 | 欧美老熟妇乱xxxxx | 精品久久久久久人妻无码中文字幕 | 精品国精品国产自在久国产87 | 久久亚洲国产成人精品性色 | 美女黄网站人色视频免费国产 | 婷婷丁香五月天综合东京热 | a在线亚洲男人的天堂 | 国产莉萝无码av在线播放 | 亚洲小说图区综合在线 | 无码av免费一区二区三区试看 | 成人免费视频视频在线观看 免费 | 成人综合网亚洲伊人 | 亚洲熟悉妇女xxx妇女av | 性色av无码免费一区二区三区 | 色欲综合久久中文字幕网 | 婷婷六月久久综合丁香 | av无码不卡在线观看免费 | 亚洲中文无码av永久不收费 | 无遮挡啪啪摇乳动态图 | 狠狠色色综合网站 | 国产精品久久久久影院嫩草 | 亚洲精品中文字幕 | 无码人妻精品一区二区三区下载 | 国产片av国语在线观看 | 国产成人综合在线女婷五月99播放 | 国产精品办公室沙发 | 无码av中文字幕免费放 | 精品国偷自产在线视频 | 丝袜美腿亚洲一区二区 | 国产亚洲欧美日韩亚洲中文色 | 中文字幕久久久久人妻 | 性欧美熟妇videofreesex | 久久久久久九九精品久 | 国产精品亚洲lv粉色 | 男女下面进入的视频免费午夜 | 国产色在线 | 国产 | 国产内射老熟女aaaa | a在线亚洲男人的天堂 | 久久视频在线观看精品 | 亚洲国产欧美在线成人 | 国产亚洲人成a在线v网站 | 久久综合香蕉国产蜜臀av | www成人国产高清内射 | 国产精品免费大片 | 成人三级无码视频在线观看 | 国产乱人伦偷精品视频 | 纯爱无遮挡h肉动漫在线播放 | 亚洲国产av精品一区二区蜜芽 | 久久久久免费看成人影片 | 欧美精品免费观看二区 | 色爱情人网站 | 欧美自拍另类欧美综合图片区 | 秋霞成人午夜鲁丝一区二区三区 | 99视频精品全部免费免费观看 | 99久久99久久免费精品蜜桃 | 兔费看少妇性l交大片免费 | 亚洲国产成人a精品不卡在线 | 欧美黑人性暴力猛交喷水 | 玩弄中年熟妇正在播放 | 久久无码专区国产精品s | 国産精品久久久久久久 | 捆绑白丝粉色jk震动捧喷白浆 | 久久午夜夜伦鲁鲁片无码免费 | 国产精品久久久久久无码 | 妺妺窝人体色www婷婷 | 国产午夜精品一区二区三区嫩草 | 欧洲熟妇精品视频 | 亚洲高清偷拍一区二区三区 | 国产人妻精品午夜福利免费 | 秋霞特色aa大片 | 久久久精品国产sm最大网站 | aa片在线观看视频在线播放 | 一区二区三区乱码在线 | 欧洲 | 好屌草这里只有精品 | 内射白嫩少妇超碰 | 国产精品免费大片 | 日韩人妻少妇一区二区三区 | 亚洲欧美色中文字幕在线 | 无码午夜成人1000部免费视频 | 国产69精品久久久久app下载 | 中文字幕无码免费久久99 | 4hu四虎永久在线观看 | 日韩精品a片一区二区三区妖精 | 少妇性l交大片欧洲热妇乱xxx | 午夜时刻免费入口 | 成年美女黄网站色大免费全看 | 欧美国产日韩亚洲中文 | 亚洲精品一区二区三区四区五区 | 宝宝好涨水快流出来免费视频 | 国产热a欧美热a在线视频 | 波多野结衣av一区二区全免费观看 | 亚洲国产精品久久人人爱 | 亚洲欧洲日本无在线码 | 亚洲国产成人av在线观看 | 亚洲狠狠色丁香婷婷综合 | 亚洲理论电影在线观看 | 人妻夜夜爽天天爽三区 | 亚洲性无码av中文字幕 | 国产精品对白交换视频 | 婷婷色婷婷开心五月四房播播 | 亚洲精品美女久久久久久久 | 麻豆蜜桃av蜜臀av色欲av | 成人精品视频一区二区 | 成 人 网 站国产免费观看 | 99riav国产精品视频 | 日韩人妻无码一区二区三区久久99 | 日本免费一区二区三区最新 | 精品久久久久久人妻无码中文字幕 | 精品国精品国产自在久国产87 | 国产猛烈高潮尖叫视频免费 | 亚洲国产综合无码一区 | 欧美精品无码一区二区三区 | 麻豆国产丝袜白领秘书在线观看 | 性生交片免费无码看人 | 男女性色大片免费网站 | 色诱久久久久综合网ywww | 中文字幕人妻无码一区二区三区 | 国内综合精品午夜久久资源 | 国产成人无码一二三区视频 | 纯爱无遮挡h肉动漫在线播放 | 国产精品久久久久7777 | 清纯唯美经典一区二区 | 天堂а√在线地址中文在线 | 18无码粉嫩小泬无套在线观看 | 日本护士xxxxhd少妇 | 久久精品视频在线看15 | 国产成人无码一二三区视频 | 国产精品亚洲一区二区三区喷水 | 夫妻免费无码v看片 | 秋霞成人午夜鲁丝一区二区三区 | 亚洲自偷精品视频自拍 | 四虎国产精品免费久久 | 一区二区三区高清视频一 | 国产口爆吞精在线视频 | 国产真人无遮挡作爱免费视频 | 女人被男人躁得好爽免费视频 | 亚洲国产欧美在线成人 | 免费看少妇作爱视频 | 性啪啪chinese东北女人 | 午夜精品一区二区三区在线观看 | 国产亚洲精品久久久久久久 | 亚洲男人av天堂午夜在 | 波多野结衣高清一区二区三区 | 国产女主播喷水视频在线观看 | 人妻插b视频一区二区三区 | 久久精品女人天堂av免费观看 | 国产精品高潮呻吟av久久4虎 | 内射巨臀欧美在线视频 | 国产一区二区不卡老阿姨 | 久久精品人人做人人综合试看 | 亚洲精品一区二区三区四区五区 | 亚洲狠狠色丁香婷婷综合 | 久久综合激激的五月天 | 色一情一乱一伦一视频免费看 | 国产在线无码精品电影网 | 宝宝好涨水快流出来免费视频 | 88国产精品欧美一区二区三区 | 精品aⅴ一区二区三区 | 久久精品女人天堂av免费观看 | 亚洲国产精品毛片av不卡在线 | 5858s亚洲色大成网站www | 成人欧美一区二区三区黑人 | 国产精品多人p群无码 | 午夜福利试看120秒体验区 | 国产精品毛片一区二区 | 国产精品高潮呻吟av久久 | 国产亚洲美女精品久久久2020 | 婷婷丁香五月天综合东京热 | 免费观看又污又黄的网站 | 无码成人精品区在线观看 | 亚洲色欲色欲欲www在线 | 撕开奶罩揉吮奶头视频 | 内射白嫩少妇超碰 | 强奷人妻日本中文字幕 | 国产又粗又硬又大爽黄老大爷视 | 无码一区二区三区在线 | 久久精品国产亚洲精品 | 人妻尝试又大又粗久久 | 国产午夜福利100集发布 | 日韩精品a片一区二区三区妖精 | 国产精品自产拍在线观看 | 少妇无码一区二区二三区 | 久久久国产精品无码免费专区 | 一本色道久久综合亚洲精品不卡 | 黄网在线观看免费网站 | 国产激情无码一区二区app | 丰满肥臀大屁股熟妇激情视频 | 波多野结衣av一区二区全免费观看 | 精品aⅴ一区二区三区 | 亚洲欧美日韩国产精品一区二区 | 亚洲国产精品成人久久蜜臀 | 中文字幕无码免费久久9一区9 | 国产电影无码午夜在线播放 | 久久久精品国产sm最大网站 | 在线天堂新版最新版在线8 | 精品无码成人片一区二区98 | 欧美人与禽zoz0性伦交 | 日韩精品久久久肉伦网站 | 久久久久久久久蜜桃 | 中国女人内谢69xxxxxa片 | 亚洲成熟女人毛毛耸耸多 | 久久精品国产大片免费观看 | 免费国产成人高清在线观看网站 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 人妻天天爽夜夜爽一区二区 | 国内精品人妻无码久久久影院蜜桃 | 国产精品99爱免费视频 | 亚洲国产av美女网站 | 亚洲综合精品香蕉久久网 | 99久久精品无码一区二区毛片 | 牲欲强的熟妇农村老妇女 | 红桃av一区二区三区在线无码av | 成 人影片 免费观看 | 亚洲国产精品久久人人爱 | 免费看少妇作爱视频 | 精品亚洲韩国一区二区三区 | 午夜福利不卡在线视频 | 正在播放老肥熟妇露脸 | 国产成人综合色在线观看网站 | 欧美激情内射喷水高潮 | 国产亚洲精品久久久久久国模美 | 久在线观看福利视频 | 麻豆果冻传媒2021精品传媒一区下载 | 免费人成网站视频在线观看 | 亚洲精品无码人妻无码 | 久久99精品久久久久婷婷 | 无码国模国产在线观看 | 亚洲国产精品成人久久蜜臀 | 99久久久无码国产精品免费 | 久久综合九色综合欧美狠狠 | 国产成人无码a区在线观看视频app | 性做久久久久久久久 | 欧美老妇与禽交 | 欧美人与物videos另类 | 日本高清一区免费中文视频 | 全黄性性激高免费视频 | 久久久久av无码免费网 | 亚洲人成人无码网www国产 | 人人妻人人澡人人爽欧美一区 | 欧美丰满少妇xxxx性 | 天天躁日日躁狠狠躁免费麻豆 | 久久zyz资源站无码中文动漫 | 亚洲人成影院在线无码按摩店 | 亚洲乱码日产精品bd | 人人妻人人澡人人爽人人精品浪潮 | 综合激情五月综合激情五月激情1 | 四虎影视成人永久免费观看视频 | 久久99精品国产麻豆 | 黑人巨大精品欧美黑寡妇 | 成人亚洲精品久久久久软件 | 亚洲s码欧洲m码国产av | v一区无码内射国产 | 国内丰满熟女出轨videos | 国产人成高清在线视频99最全资源 | 久久97精品久久久久久久不卡 | 国产明星裸体无码xxxx视频 | 蜜臀aⅴ国产精品久久久国产老师 | 国产亚洲精品精品国产亚洲综合 | 国产精品a成v人在线播放 | 熟妇激情内射com | 亚洲の无码国产の无码步美 | 小泽玛莉亚一区二区视频在线 | 国产一区二区三区四区五区加勒比 | 国产热a欧美热a在线视频 | 全球成人中文在线 | 无码一区二区三区在线 | 亚洲熟熟妇xxxx | 亚洲日韩av片在线观看 | 丰满人妻一区二区三区免费视频 | 亚洲 高清 成人 动漫 | 九月婷婷人人澡人人添人人爽 | 性生交片免费无码看人 | 无码帝国www无码专区色综合 | 久久综合九色综合欧美狠狠 | 欧美老妇交乱视频在线观看 | 亚洲一区av无码专区在线观看 | 丰满妇女强制高潮18xxxx | 噜噜噜亚洲色成人网站 | 最近免费中文字幕中文高清百度 | 久久精品成人欧美大片 | аⅴ资源天堂资源库在线 | 久久久国产精品无码免费专区 | 老子影院午夜精品无码 | 亚洲国产精品久久久久久 | 老头边吃奶边弄进去呻吟 | 99久久人妻精品免费一区 | 双乳奶水饱满少妇呻吟 | 亚洲国产av精品一区二区蜜芽 | 免费观看黄网站 | 国产精品二区一区二区aⅴ污介绍 | 亚洲国产欧美国产综合一区 | 日本爽爽爽爽爽爽在线观看免 | 狠狠噜狠狠狠狠丁香五月 | 欧美午夜特黄aaaaaa片 | 乱码午夜-极国产极内射 | 国产极品美女高潮无套在线观看 | 亚洲精品一区二区三区大桥未久 | 97夜夜澡人人双人人人喊 | 午夜男女很黄的视频 | 精品国产一区二区三区四区 | 中文精品无码中文字幕无码专区 | 亚洲日本va中文字幕 | 三上悠亚人妻中文字幕在线 | 国产超碰人人爽人人做人人添 | 鲁大师影院在线观看 | 成人免费视频在线观看 | 日韩精品无码免费一区二区三区 | 夜夜躁日日躁狠狠久久av | 亚洲精品久久久久中文第一幕 | 亚洲欧美精品伊人久久 | 日韩精品无码一本二本三本色 | 免费中文字幕日韩欧美 | 男女下面进入的视频免费午夜 | 一个人免费观看的www视频 | 97se亚洲精品一区 | 国产精品高潮呻吟av久久4虎 | www国产亚洲精品久久久日本 | 秋霞成人午夜鲁丝一区二区三区 | 日韩精品无码一本二本三本色 | 免费网站看v片在线18禁无码 | 久久亚洲日韩精品一区二区三区 | 乱码av麻豆丝袜熟女系列 | 亚洲成色在线综合网站 | 少妇的肉体aa片免费 | 夜夜躁日日躁狠狠久久av | 国产午夜手机精彩视频 | 牛和人交xxxx欧美 | 亚洲理论电影在线观看 | 精品国产麻豆免费人成网站 | 波多野结衣一区二区三区av免费 | 国产一区二区三区日韩精品 | 国产一区二区三区四区五区加勒比 | 亚洲欧美国产精品久久 | 人人妻人人澡人人爽欧美一区九九 | 亚洲成a人片在线观看日本 | 亚洲中文字幕av在天堂 | 欧美性猛交xxxx富婆 | 少妇高潮喷潮久久久影院 | 巨爆乳无码视频在线观看 | 乱中年女人伦av三区 | 亚洲色欲久久久综合网东京热 | 中文字幕人妻无码一区二区三区 | 三级4级全黄60分钟 | 欧美性猛交xxxx富婆 | 99国产欧美久久久精品 | 亚洲欧美国产精品专区久久 | √8天堂资源地址中文在线 | 亚洲精品国产精品乱码不卡 | 亚洲熟妇自偷自拍另类 | 精品国产麻豆免费人成网站 | 大肉大捧一进一出视频出来呀 | 黑人玩弄人妻中文在线 | 国产成人亚洲综合无码 | 在线а√天堂中文官网 | 日本一本二本三区免费 | 国产美女极度色诱视频www | 东京热一精品无码av | 成人aaa片一区国产精品 | 欧美一区二区三区 | 国产人妻久久精品二区三区老狼 | 人人妻人人澡人人爽欧美精品 | 久久精品人人做人人综合试看 | 亚洲aⅴ无码成人网站国产app | 国产精品18久久久久久麻辣 | 学生妹亚洲一区二区 | 性欧美牲交xxxxx视频 | 亚洲精品一区二区三区在线观看 | 国产97色在线 | 免 | 无码毛片视频一区二区本码 | 亚洲精品www久久久 | 精品国偷自产在线视频 | 国色天香社区在线视频 | 国产精品无码一区二区桃花视频 | 未满成年国产在线观看 | 欧美日韩色另类综合 | 日本丰满护士爆乳xxxx | 国内老熟妇对白xxxxhd | 国产成人综合色在线观看网站 | 国内综合精品午夜久久资源 | 国产高清不卡无码视频 | 欧美日韩一区二区综合 | 亚洲无人区午夜福利码高清完整版 | 国产在线精品一区二区高清不卡 | 成熟妇人a片免费看网站 | 一个人看的www免费视频在线观看 | 99久久人妻精品免费一区 | 东京热无码av男人的天堂 | 久久久久se色偷偷亚洲精品av | 欧洲极品少妇 | 99久久人妻精品免费二区 | 少妇一晚三次一区二区三区 | 免费国产成人高清在线观看网站 | 99久久99久久免费精品蜜桃 | 日日躁夜夜躁狠狠躁 | 六月丁香婷婷色狠狠久久 | 国产在线aaa片一区二区99 | 一个人看的视频www在线 | 十八禁视频网站在线观看 | 四虎永久在线精品免费网址 | 久久久久久久女国产乱让韩 | 中文字幕无码日韩专区 | 一本久久伊人热热精品中文字幕 | 国产精品亚洲а∨无码播放麻豆 | 日日碰狠狠丁香久燥 | 成年美女黄网站色大免费全看 | 夜精品a片一区二区三区无码白浆 | 欧美放荡的少妇 | 亚洲色偷偷男人的天堂 | 高清不卡一区二区三区 | 国产情侣作爱视频免费观看 | 中文字幕无码免费久久99 | 天天摸天天碰天天添 | 老熟女重囗味hdxx69 | 国产精品无套呻吟在线 | 日本熟妇人妻xxxxx人hd | 国产熟妇另类久久久久 | 女高中生第一次破苞av | 精品久久久久久人妻无码中文字幕 | 亚洲成a人片在线观看无码 | 牲欲强的熟妇农村老妇女视频 | 精品成在人线av无码免费看 | 人人妻人人澡人人爽人人精品 | 2019午夜福利不卡片在线 | 成人动漫在线观看 | 欧美亚洲国产一区二区三区 | 亚洲综合在线一区二区三区 | 亚洲狠狠婷婷综合久久 | 精品国精品国产自在久国产87 | 国产亚洲精品久久久久久 | 国产亚洲精品久久久久久久 | 国产精品-区区久久久狼 | 成人试看120秒体验区 | 中文亚洲成a人片在线观看 | 国产深夜福利视频在线 | 粗大的内捧猛烈进出视频 | 无码国产乱人伦偷精品视频 | 狂野欧美激情性xxxx | 国产电影无码午夜在线播放 | 免费无码一区二区三区蜜桃大 | 东京热男人av天堂 | 国产av一区二区精品久久凹凸 | 久9re热视频这里只有精品 | 国产片av国语在线观看 | 疯狂三人交性欧美 | 国产精品无码永久免费888 | 东京热无码av男人的天堂 | 久久精品国产一区二区三区肥胖 | 久久亚洲日韩精品一区二区三区 | 青青青手机频在线观看 | 亚洲中文字幕无码中字 | 男人的天堂av网站 | 亚洲精品欧美二区三区中文字幕 | 国产欧美亚洲精品a | 亚洲成a人片在线观看日本 | 国产片av国语在线观看 | 国产明星裸体无码xxxx视频 | 学生妹亚洲一区二区 | 永久免费观看国产裸体美女 | 欧美丰满熟妇xxxx性ppx人交 | 久久综合色之久久综合 | 国产另类ts人妖一区二区 | 国产精品无码久久av | 国产精品亚洲五月天高清 |