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

歡迎訪問 生活随笔!

生活随笔

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

python

python内存管理和释放_《python解释器源码剖析》第17章--python的内存管理与垃圾回收...

發布時間:2024/1/23 python 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python内存管理和释放_《python解释器源码剖析》第17章--python的内存管理与垃圾回收... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

17.0 序

內存管理,對于python這樣的動態語言是至關重要的一部分,它在很大程度上決定了python的執行效率,因為在python的運行中會創建和銷毀大量的對象,這些都設計內存的管理。同理python還提供了了內存的垃圾回收(GC,garbage collection),將開發者從繁瑣的手動維護內存的工作中解放出來。這一章我們就來分析python的GC是如何實現的。

17.1 內存管理架構

在python中內存管理機制是分層次的,我們可以看成有四層,0 1 2 3。在最底層,也就是第0層是由操作系統提供的內存管理接口,比如C提供了malloc和free接口,這一層是由操作系統實現并且管理的,python不能干涉這一行為。從這一層往上,剩余的三層則都是由python實現并維護的。

第一層是python基于第0層操作系統管理接口包裝而成的,這一層并沒有在第0層上加入太多的動作,其目的僅僅是為python提供一層統一的raw memory的管理接口。這么做的原因就是雖然不同的操作系統都提供了ANSI C標準所定義的內存管理接口,但是對于某些特殊情況不同操作系統有不同的行為。比如調用malloc(0),有的操作系統會返回NULL,表示申請失敗,但是有的操作系統則會返回一個貌似正常的指針, 但是這個指針指向的內存并不是有效的。為了最廣泛的可移植性,python必須保證相同的語義一定代表著相同的運行時行為,為了處理這些與平臺相關的內存分配行為,python必須要在C的內存分配接口之上再提供一層包裝。

在python中,第一層的實現就是一組以PyMem_為前綴的函數族,下面來看一下。

//include.h

PyAPI_FUNC(void *) PyMem_Malloc(size_t size);

PyAPI_FUNC(void *) PyMem_Realloc(void *ptr, size_t new_size);

PyAPI_FUNC(void) PyMem_Free(void *ptr);

//obmalloc.c

void *

PyMem_Malloc(size_t size)

{

/* see PyMem_RawMalloc() */

if (size > (size_t)PY_SSIZE_T_MAX)

return NULL;

return _PyMem.malloc(_PyMem.ctx, size);

}

void *

PyMem_Realloc(void *ptr, size_t new_size)

{

/* see PyMem_RawMalloc() */

if (new_size > (size_t)PY_SSIZE_T_MAX)

return NULL;

return _PyMem.realloc(_PyMem.ctx, ptr, new_size);

}

void

PyMem_Free(void *ptr)

{

_PyMem.free(_PyMem.ctx, ptr);

}

我們看到在第一層,python提供了類似于類似于C中malloc、realloc、free的語義。并且我們發現,比如PyMem_Malloc,如果申請的內存大小超過了PY_SSIZE_T_MAX直接返回NULL,并且調用了_PyMem.malloc,這個C中的malloc幾乎沒啥區別,但是會對特殊值進行一些處理。到目前為止,僅僅是分配了raw memory而已。其實在第一層,python還提供了面向對象中類型的內存分配器。

//pymem.h

#define PyMem_New(type, n) \

( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \

( (type *) PyMem_Malloc((n) * sizeof(type)) ) )

#define PyMem_NEW(type, n) \

( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \

( (type *) PyMem_MALLOC((n) * sizeof(type)) ) )

#define PyMem_Resize(p, type, n) \

( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \

(type *) PyMem_Realloc((p), (n) * sizeof(type)) )

#define PyMem_RESIZE(p, type, n) \

( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \

(type *) PyMem_REALLOC((p), (n) * sizeof(type)) )

#define PyMem_Del PyMem_Free

#define PyMem_DEL PyMem_FREE

很明顯,在PyMem_Malloc中需要程序員自行提供所申請的空間大小。然而在PyMem_New中,只需要提供類型和數量,python會自動偵測其所需的內存空間大小。

第一層所提供的內存管理接口的功能是非常有限的,如果創建一個PyLongObject對象,還需要做很多額外的工作,比如設置對象的類型參數、初始化對象的引用計數值等等。因此為了簡化python自身的開發,python在比第一層更高的抽象層次上提供了第二層內存管理接口。在這一層,是一組以PyObject_為前綴的函數族,主要提供了創建python對象的接口。這一套函數族又被換做Pymalloc機制。因此在第二層的內存管理機制上,python對于一些內建對象構建了更高抽象層次的內存管理策略。而對于第三層的內存管理策略,主要就是對象的緩存機制。因此:

第0層:操作系統負責管理內存,python無權干預

第1層:僅僅對c中原生的malloc進行了簡單包裝

第2層:真正在python中發揮巨大作用,并且也是GC的藏身之處

第3層:緩沖池,比如小整數對象池等等。

下面我們就來對第二層內存管理機制進行剖析。

17.2 小塊空間的內存池

在python中,很多時候申請的內存都是小塊的內存,這些小塊的內存在申請后很快又被釋放,并且這些內存的申請并不是為了創建對象,所以并沒有對象一級的內存池機制。這就意味著python在運行期間需要大量的執行malloc和free操作,導致操作系統在用戶態和內核態之間進行切換,這將嚴重影響python的效率。所以為了提高執行效率,python引入了一個內存池機制,用于管理對小塊內存的申請和釋放,這就是之前說的Pymalloc機制,并且提供了pymalloc_alloc,pymalloc_realloc,pymalloc_free三個接口。

整個小塊內存的內存池可以視為一個層次結構,在這個層次結構中一共分為4層,從下至上分別是:block、pool、arena和內存池。并且block(霧)、pool、arena都是python代碼中可以找到的實體,而最頂層的內存池只是一個概念上的東西,表示python對整個小塊內存分配和釋放行為的內存管理機制。

17.2.1 block

在最底層,block是一個確定大小的內存塊。而python中,有很多種block,不同種類的block都有不同的內存大小,這個內存大小的值被稱之為size?class。為了在當前主流的32位平臺和64位平臺都能獲得最佳性能,所有的block的長度都是8字節對齊的。

//obmalloc.c

#define ALIGNMENT 8 /* must be 2^N */

#define ALIGNMENT_SHIFT 3

同時,python為block的大小設定了一個上限,當申請的內存大小小于這個上限時,python可以使用不同種類的block滿足對內存的需求;當申請的內存大小超過了這個上限,python就會將對內存的請求轉交給第一層的內存管理機制,即PyMem函數族來處理。這個上限值在python中被設置為512,如果超過了這個值還是要經過操作系統臨時申請的。

//obmalloc.c

#define SMALL_REQUEST_THRESHOLD 512

#define NB_SMALL_SIZE_CLASSES (SMALL_REQUEST_THRESHOLD / ALIGNMENT)

根據SMALL_REQUEST_THRESHOLD和ALIGNMENT的限定,實際上我們可以由此得到不同種類的block和size class。block是以8字節對齊,那么每一個塊的大小都是8的整倍數,最大不超過512

* Request in bytes Size of allocated block Size class idx

* ----------------------------------------------------------------

* 1-8 8 0

* 9-16 16 1

* 17-24 24 2

* 25-32 32 3

* 33-40 40 4

* 41-48 48 5

* 49-56 56 6

* 57-64 64 7

* 65-72 72 8

* ... ... ...

* 497-504 504 62

* 505-512 512 63

因此當我們申請一個44字節的內存時,PyObject_Malloc會從內存池中劃分一個48字節的block給我們。

另外在python中,block只是一個概念,在python源碼中沒有與之對應的實體存在。之前我們說對象,對象在源碼中有對應的PyObject,列表在源碼中則有對應的PyListObject,但是這里的block僅僅是概念上的東西,我們知道它是具有一定大小的內存,但是它并不與python源碼里面的某個東西對應。但是,python提供了一個管理block的東西,也就是我們下面要分析的pool。

17.2.2 pool

一組block的集合成為一個pool,換句話說,一個pool管理著一堆具有固定大小的內存塊(block)。事實上,pool管理者一大塊內存,它有一定的策略,將這塊大的內存劃分為多個小的內存塊。在python中,一個pool的大小通常是為一個系統內存頁,也就是4kb。

//obmalloc.c

#define SYSTEM_PAGE_SIZE (4 * 1024)

#define SYSTEM_PAGE_SIZE_MASK (SYSTEM_PAGE_SIZE - 1)

#define POOL_SIZE SYSTEM_PAGE_SIZE /* must be 2^N */

#define POOL_SIZE_MASK SYSTEM_PAGE_SIZE_MASK

雖然python沒有為block提供對應的結構,但是提供了和pool相關的結構,我們來看看

//obmalloc.c

/* Pool for small blocks. */

struct pool_header {

union { block *_padding;

uint count; } ref; /* 當然pool里面的block數量 */

block *freeblock; /* 一個鏈表,指向下一個可用的block */

struct pool_header *nextpool; /* 指向下一個pool */

struct pool_header *prevpool; /* 指向上一個pool "" */

uint arenaindex; /* 在area里面的索引 */

uint szidx; /* block的大小(固定值?后面說) */

uint nextoffset; /* 下一個可用block的內存偏移量 */

uint maxnextoffset; /* 最后一個block距離開始位置的距離 */

};

typedef struct pool_header *poolp;

我們剛才說了一個pool的大小在python中是4KB,但是從當前的這個pool的結構體來看,用鼻子想也知道吃不完4KB的內存。所以呀,這個結構體叫做pool_header,它僅僅一個pool的頭部,除去這個pool_header,還剩下的內存才是維護的所有block的集合所占的內存。

我們注意到,pool_header里面有一個szidx,這就意味著pool里面管理的內存塊大小都是一樣的。也就是說,一個pool可能管理了20個32字節的block、也可能管理了20個64字節的block,但是不會出現管理了10個32字節的block加上10個64字節的block存在。每一個pool都和一個size聯系在一起,更確切的說都和一個size class index聯系在一起,表示pool里面存儲的block都是多少字節的。這就是里面的域szidx存在的意義。

假設我們手里有一塊4kb的內存,來看看python是如何將這塊內存改造為一個管理32字節block的pool,并從中取出第一塊pool的。

//obmalloc.c

#define POOL_OVERHEAD _Py_SIZE_ROUND_UP(sizeof(struct pool_header), ALIGNMENT)

static int

pymalloc_alloc(void *ctx, void **ptr_p, size_t nbytes)

{

block *bp;

poolp pool;

...

//pool指向了一塊4kb的內存

init_pool:

pool->ref.count = 1;

...

//設置pool的size class index

pool->szidx = size;

//將size class index轉換成size,比如:0->8, 1->16, 63->512

size = INDEX2SIZE(size);

//跳過用于pool_header的內存,并進行對齊

bp = (block *)pool + POOL_OVERHEAD;

//等價于pool->nextoffset = POOL_OVERHEAD+size+size

pool->nextoffset = POOL_OVERHEAD + (size << 1);

pool->maxnextoffset = POOL_SIZE - size;

pool->freeblock = bp + size;

*(block **)(pool->freeblock) = NULL;

goto success;

...

success:

UNLOCK();

assert(bp != NULL);

*ptr_p = (void *)bp;

return 1;

}

最后的(void *)bp;就是指向從pool中取出的第一塊block的指針。也就是說pool中第一塊block已經被分配了,所以在ref.count中記錄了當前已經被分配的block的數量,這時為1,特別需要注意的是,bp返回的實際上是一個地址,這個地址之后有將近4kb的內存實際上都是可用的,但是可以肯定申請內存的函數只會使用[bp, bp+size]這個區間的內存,這是由size?class index可以保證的。改造成pool之后的4kb內存如圖所示:

實線箭頭是指針,但是虛線箭頭則是偏移位置的形象表示。在nextoffset,maxnextoffset中存儲的是相對于pool頭部的偏移位置。

在了解初始化之后的pool的樣子之后,可以來看看python在申請block時,pool_header中的各個域是怎么變動的。假設我們從現在開始連續申請5塊28字節內存,由于28字節對應的size class index為3,所以實際上會申請5塊32字節的內存。

//obmalloc.c

static int

pymalloc_alloc(void *ctx, void **ptr_p, size_t nbytes)

{

if (pool != pool->nextpool) {

/*

* There is a used pool for this size class.

* Pick up the head block of its free list.

*/

//首先pool中block數自增1

++pool->ref.count;

//這里的freeblock指向的是下一個可用的block的起始地址

bp = pool->freeblock;

assert(bp != NULL);

if ((pool->freeblock = *(block **)bp) != NULL) {

goto success;

}

//因此當再次申請32字節block時,只需要返回freeblock指向的地址就可以了。那么很顯然,freeblock需要前進,指向下一個可用的block,這個時候nextoffset就現身了

if (pool->nextoffset <= pool->maxnextoffset) {

//當nextoffset小于等于maxoffset時候

//freeblock等于當前block的地址 + nextoffset(下一個可用block的內存偏移量)

//所以freeblock正好指向了下一個可用block的地址

pool->freeblock = (block*)pool +

pool->nextoffset;

//同理,nextoffset也要向前移動一個block的距離

pool->nextoffset += INDEX2SIZE(size);

//依次反復,即可對所有的block進行遍歷。而maxnextoffset指明了該pool中最后一個可用的block距離pool開始位置的偏移

//當pool->nextoffset > pool->maxnextoffset就意味著遍歷完pool中的所有block了

//再次獲取顯然就是NULL了

*(block **)(pool->freeblock) = NULL;

goto success;

}

}

所以,申請、前進、申請、前進,一直重復著相同的動作,整個過程非常自然,也容易理解。但是我們發現,由于無論多少個block,這些block必須都是具有相同大小,導致一個pool中只能滿足POOL_SIZE?/?size次對block的申請,這就讓人不舒服。舉個栗子,現在我們已經進行了5次連續32字節的內存分配,可以想象,pool中5個連續的block都被分配出去了。過了一段時間,程序釋放了其中的第2塊和第4塊block,那么下一次再分配32字節的內存的時候,pool提交的應該是第2塊,還是第6塊呢?顯然為了pool的使用效率,最好分配自由的第二塊block。因此可以想象,一旦python運轉起來,內存的釋放動作將導致pool中出現大量的離散的自由block,python為了知道哪些block是被使用之后再次被釋放的,必須建立一種機制,將這些離散自由的block組合起來,再次使用。這個機制就是所有的自由block鏈表,這個鏈表的關鍵就在pool_header中的那個freeblock身上。

//obmalloc.c

/* Pool for small blocks. */

struct pool_header {

union { block *_padding;

uint count; } ref; /* 當然pool里面的block數量 */

block *freeblock; /* 一個鏈表,指向下一個可用的block */

struct pool_header *nextpool; /* 指向下一個pool */

struct pool_header *prevpool; /* 指向上一個pool "" */

uint arenaindex; /* 在area里面的索引 */

uint szidx; /* block的大小(固定值?后面說) */

uint nextoffset; /* 下一個可用block的內存偏移量 */

uint maxnextoffset; /* 最后一個block距離開始位置的距離 */

};

typedef struct pool_header *poolp;

剛才我們說了,當pool初始化完后之后,freeblock指向了一個有效的地址,也就是下一個可以分配出去的block的地址。然而奇特的是,當python設置了freeblock時,還設置了*freeblock。這個動作看似詭異,然而我們馬上就能看到設置*freeblock的動作正是建立離散自由block鏈表的關鍵所在。目前我們看到的freeblock只是在機械地前進前進,因為它在等待一個特殊的時刻,在這個特殊的時刻,你會發現freeblock開始成為一個蘇醒的精靈,在這4kb的內存上開始靈活地舞動。這個特殊的時刻就是一個block被釋放的時刻。

//obmalloc.c

//基于地址P獲得離P最近的pool的邊界地址

#define POOL_ADDR(P) ((poolp)_Py_ALIGN_DOWN((P), POOL_SIZE))

static int

pymalloc_free(void *ctx, void *p)

{

poolp pool;

block *lastfree;

poolp next, prev;

uint size;

pool = POOL_ADDR(p);

//如果p不在pool里面,直接返回0

if (!address_in_range(p, pool)) {

return 0;

}

LOCK();

//釋放,那么ref.count就是勢必大于0

assert(pool->ref.count > 0); /* else it was empty */

*(block **)p = lastfree = pool->freeblock;

pool->freeblock = (block *)p;

}

在釋放block時,神秘的freeblock驚鴻一現,覆蓋在freeblock身上的那層面紗就要被揭開了。我們知道,這是freeblock雖然指向了一個有效的pool里面的地址,但是*freeblock是為NULL的。假設這時候python釋放的是block A,那么A中的第一個字節的值被設置成了當前freeblock的值,然后freeblock的值被更新了,指向了block A的首地址。就是這兩個步驟,一個block被插入到了離散自由的block鏈表中,所以當第2塊和第4塊block都被釋放之后,我們可以看到一個初具規模的離散自由block鏈表了。

到了這里,這條實現方式非常奇特的block鏈表被我們挖掘出來了,從freeblock開始,我們可以很容易的以freeblock?=?*freeblock的方式遍歷這條鏈表,而當發現了*freeblock為NULL時,則表明到達了該鏈表(可用自由鏈表)的尾部了,那么下次就需要申請新的block了。

//obmalloc.c

static int

pymalloc_alloc(void *ctx, void *p)

{

if (pool != pool->nextpool) {

++pool->ref.count;

bp = pool->freeblock;

assert(bp != NULL);

//如果這里的條件不為真,表明離散自由鏈表中已經不存在可用的block了

//如果可能,則會繼續分配pool的nextoffset指定的下一塊block

if ((pool->freeblock = *(block **)bp) != NULL) {

goto success;

}

/*

* Reached the end of the free list, try to extend it.

*/

if (pool->nextoffset <= pool->maxnextoffset) {

...

}

}

但是如果連pool->nextoffset <= pool->maxnextoffset這個條件都不成立了呢?pool的大小有限制啊,如果我再想申請block的時候,沒空間了怎么辦?再來一個pool不就好了,所以多個block可以組合成一個集合,pool;那么多個pool也可以組合起來,就是我們下面介紹的arena。

17.2.3 arena

在python中,多個pool聚合的結果就是一個arena。上一節提到,pool的大小默認是4kb,同樣每個arena的大小也有一個默認值。#define ARENA_SIZE (256 << 10),顯然這個值默認是256KB,也就是ARENA_SIZE?/?POOL_SIZE?=?64個pool的大小。

//obmalloc.c

struct arena_object {

uintptr_t address;

block* pool_address;

uint nfreepools;

uint ntotalpools;

struct pool_header* freepools;

struct arena_object* nextarena;

struct arena_object* prevarena;

};

一個概念上的arena在python源碼中就對應arena_object結構體,確切的說,arena_object僅僅是arena的一部分。就像pool_header僅僅是pool的一部分一樣,一個完整的pool包括一個pool_header和透過這個pool_header管理著的block集合;一個完整的arena也包括一個arena_object和透過這個arena_object管理著的pool集合。

"未使用的"的arena和"可用"的arena

在arena_object結構體的定義中,我們看到了nextarena和prevarena這兩個東西,這似乎意味著在python中會有一個或多個arena構成的鏈表,這個鏈表的表頭就是arenas。呃,這種猜測實際上只對了一半,實際上,在python中確實會存在多個arena_object構成的集合,但是這個集合不夠成鏈表,而是一個數組。數組的首地址由arenas來維護,這個數組就是python中的通用小塊內存的內存池。另一方面,nextarea和prevarena也確實是用來連接arena_object組成鏈表的,咦,不是已經構成或數組了嗎?為啥又要來一個鏈表。

我們曾說arena是用來管理一組pool的集合的,arena_object的作用看上去和pool_header的作用是一樣的。但是實際上,pool_header管理的內存和arena_object管理的內存有一點細微的差別。pool_header管理的內存pool_header自身是一塊連續的內存,但是arena_object與其管理的內存則是分離的:

咋一看,貌似沒啥區別,不過一個是連著的,一個是分開的。但是這后面隱藏了這樣一個事實:當pool_header被申請時,它所管理的內存也一定被申請了;但是當arena_object被申請時,它所管理的pool集合的內存則沒有被申請。換句話說,arena_object和pool集合在某一時刻需要建立聯系。

當一個arena的arena_object沒有與pool建立聯系的時候,這時的arena就處于"未使用"狀態;一旦建立了聯系,這時arena就轉換到了"可用"狀態。對于每一種狀態,都有一個arena鏈表。"未使用"的arena鏈表表頭是unused_arena_objects,多個arena之間通過nextarena連接,并且是一個單向的鏈表;而"可用的"arena鏈表表頭是usable_arenas,多個arena之間通過nextarena、prevarena連接,是一個雙向鏈表。

申請arena

在運行期間,python使用new_arena來創建一個arena,我們來看看它是如何被創建的。

//obmalloc.c

//arenas,多個arena組成的數組的首地址

static struct arena_object* arenas = NULL;

//當arena數組中的所有arena的個數

static uint maxarenas = 0;

//未使用的arena的個數

static struct arena_object* unused_arena_objects = NULL;

//可用的arena的個數

static struct arena_object* usable_arenas = NULL;

//初始化需要申請的arena的個數

#define INITIAL_ARENA_OBJECTS 16

static struct arena_object*

new_arena(void)

{

//arena,一個arena_object結構體對象

struct arena_object* arenaobj;

uint excess; /* number of bytes above pool alignment */

//[1]:判斷是否需要擴充"未使用"的arena列表

if (unused_arena_objects == NULL) {

uint i;

uint numarenas;

size_t nbytes;

//[2]:確定本次需要申請的arena_object的個數,并申請內存

numarenas = maxarenas ? maxarenas << 1 : INITIAL_ARENA_OBJECTS;

...

nbytes = numarenas * sizeof(*arenas);

arenaobj = (struct arena_object *)PyMem_RawRealloc(arenas, nbytes);

if (arenaobj == NULL)

return NULL;

arenas = arenaobj;

...

/* Put the new arenas on the unused_arena_objects list. */

//[3]:初始化新申請的arena_object,并將其放入"未使用"arena鏈表中

for (i = maxarenas; i < numarenas; ++i) {

arenas[i].address = 0; /* mark as unassociated */

arenas[i].nextarena = i < numarenas - 1 ?

&arenas[i+1] : NULL;

}

/* Update globals. */

unused_arena_objects = &arenas[maxarenas];

maxarenas = numarenas;

}

/* Take the next available arena object off the head of the list. */

//[4]:從"未使用"arena鏈表中取出一個"未使用"的arena

assert(unused_arena_objects != NULL);

arenaobj = unused_arena_objects;

unused_arena_objects = arenaobj->nextarena;

assert(arenaobj->address == 0);

//[5]:申請arena管理的內存,這里我們說的arena指的是arena_object,簡寫了

address = _PyObject_Arena.alloc(_PyObject_Arena.ctx, ARENA_SIZE);

if (address == NULL) {

/* The allocation failed: return NULL after putting the

* arenaobj back.

*/

arenaobj->nextarena = unused_arena_objects;

unused_arena_objects = arenaobj;

return NULL;

}

arenaobj->address = (uintptr_t)address;

//調整個數

++narenas_currently_allocated;

++ntimes_arena_allocated;

if (narenas_currently_allocated > narenas_highwater)

narenas_highwater = narenas_currently_allocated;

//[6]:設置poo集合的相關信息,這是設置為NULL

arenaobj->freepools = NULL;

/* pool_address

nfreepools

arenaobj->pool_address = (block*)arenaobj->address;

arenaobj->nfreepools = ARENA_SIZE / POOL_SIZE;

assert(POOL_SIZE * arenaobj->nfreepools == ARENA_SIZE);

//將pool的起始地址調整為系統頁的邊界

excess = (uint)(arenaobj->address & POOL_SIZE_MASK);

if (excess != 0) {

--arenaobj->nfreepools;

arenaobj->pool_address += POOL_SIZE - excess;

}

arenaobj->ntotalpools = arenaobj->nfreepools;

return arenaobj;

}

因此我們可以看到,python首先會檢查當前"未使用"鏈表中是否還有"未使用"arena,檢查的結果將決定后續的動作。

如果在"未使用"鏈表中還存在未使用的arena,那么python會從"未使用"arena鏈表中抽取一個arena,接著調整"未使用"鏈表,讓它和抽取的arena斷絕一切聯系。然后python申請了一塊256KB大小的內存,將申請的內存地址賦給抽取出來的arena的address。我們已經知道,arena中維護的是pool集合,這塊256KB的內存就是pool的容身之處,這時候arena就已經和pool集合建立聯系了。這個arena已經具備了成為"可用"內存的條件,該arena和"未使用"arena鏈表脫離了關系,就等著被"可用"arena鏈表接收了,不過什么時候接收呢?先別急

隨后,python在代碼的[6]處設置了一些arena用戶維護pool集合的信息。需要注意的是,python將申請到的256KB內存進行了處理,主要是放棄了一些內存,并將可使用的內存邊界(pool_address)調整到了與系統頁對齊。然后通過arenaobj->freepools = NULL;將freepools設置為NULL,這不奇怪,基于對freeblock的了解,我們知道要等到釋放一個pool時,這個freepools才會有用。最后我們看到,pool集合占用的256KB內存在進行邊界對齊后,實際是交給pool_address來維護了。

回到new_arena中的[1]處,如果unused_arena_objects為NULL,則表明目前系統中已經沒有"未使用"arena了,那么python首先會擴大系統的arena集合(小塊內存內存池)。python在內部通過一個maxarenas的變量維護了存儲arena的數組的個數,然后在[2]處將待申請的arena的個數設置為當然arena個數(maxarenas)的2倍。當然首次初始化的時候maxarenas為0,此時為16。

在獲得了新的maxarenas后,python會檢查這個新得到的值是否溢出了。如果檢查順利通過,python就會在[3]處通過realloc擴大arenas指向的內存,并對新申請的arena_object進行設置,特別是那個不起眼的address,要將新申請的address一律設置為0。實際上,這是一個標識arena是出于"未使用"狀態還是"可用"狀態的重要標記。而一旦arena(arena_object)和pool集合建立了聯系,這個address就變成了非0,看代碼的[6]處。當然別忘記我們為什么會走到[3]這里,是因為unused_arena_objects == NULL了,而且最后還設置了unused_arena_objects,這樣系統中又有了"未使用"的arena了,接下來python就在[4]處對一個arena進行初始化了。

17.2.4 內存池

可用pool緩沖池--usedpools

通過#define SMALL_REQUEST_THRESHOLD 512我們知道python內部默認的小塊內存與大塊內存的分界點定在512個字節。也就是說,當申請的內存小于512個字節,pymalloc_alloc會在內存池中申請內存,而當申請的內存超過了512字節,那么pymalloc_alloc將退化為malloc,通過操作系統來申請內存。當然,通過修改python源代碼我們可以改變這個值,從而改變python的默認內存管理行為。

當申請的內存小于512字節時,python會使用area所維護的內存空間。那么python內部對于area的個數是否有限制呢?換句話說,python對于這個小塊空間內存池的大小是否有限制?其實這個決策取決于用戶,python提供了一個編譯符號,用于控制是否限制內存池的大小,不過這里不是重點,只需要知道就行。

盡管我們在前面花了不少篇幅介紹arena,同時也看到arena是python的小塊內存池的最上層結構,所有arena的集合實際就是小塊內存池。然而在實際的使用中,python并不直接與arenas和arena數組打交道。當python申請內存時,最基本的操作單元并不是arena,而是pool。估計到這里懵了,別急,慢慢來。

舉個例子,當我們申請一個28字節的內存時,python內部會在內存池尋找一塊能夠滿足需求的pool,從中取出一個block返回,而不會去尋找arena。這實際上是由pool和arena的屬性決定的,在python中,pool是一個有size概念的內存管理抽象體,一個pool中的block總是有確定的大小,這個pool總是和某個size class index對應,還記得pool_header中的那個szidx么?而arena是沒有size概念的內存管理抽象體。這就意味著,同一個arena在某個時刻,其內部的pool集合可能都是32字節的block;而到了另一個時刻,由于系統需要,這個arena可能被重新劃分,其中的pool集合可能改為64字節的block了,甚至pool集合中一般的pool管理32字節,另一半管理64字節。這就決定了在進行內存分配和銷毀時,所有的動作都是在pool上完成的。

當然內存池中的pool不僅僅是一個有size概念的內存管理抽象體,更進一步的,它還是一個有狀態的內存管理抽象體。一個pool在python運行的任何一個時刻,總是處于一下三種狀態中的一種:

used狀態:pool中至少有一個block已經被使用,并且至少有一個block未被使用。這種狀態的pool受控于python內部維護的usedpools數組。

full狀態:pool中所有的block都已經被使用,這種狀態的pool在arena中,但是不再arena的freepools鏈表中。

empty狀態:pool中所有的block都未被使用,處于這個狀態的pool的集合通過其pool_header中的nextpool構成一個鏈表,這個鏈表的表頭就是arena中的freepools。

請注意:arena中處于full狀態的pool是各自獨立,沒有像其他狀態的pool一樣,連接成一個鏈表。

我們從圖中看到所有的處于used狀態的pool都被置于usedpools的控制之下。python內部維護的usedpools數組是一個非常巧妙的實現,維護著所有的處于used狀態的pool。當申請內存時,python就會通過usedpools尋找到一個可用的pool(處于used狀態),從中分配一個block。因此我們想,一定有一個usedpools相關聯的機制,完成從申請的內存的大小到size class index之間的轉換,否則python就無法找到最合適的pool了。這種機制和usedpools的結構有著密切的關系,我們看一下它的結構。

//obmalloc.c

typedef uint8_t block;

#define PTA(x) ((poolp )((uint8_t *)&(usedpools[2*(x)]) - 2*sizeof(block *)))

#define PT(x) PTA(x), PTA(x)

//NB_SMALL_SIZE_CLASSES之前好像出現過,但是不用說也知道這表示當前配置下有多少個不同size的塊

//在我當前的機器就是512/8=64個,對應的size class index就是從0到63

#define NB_SMALL_SIZE_CLASSES (SMALL_REQUEST_THRESHOLD / ALIGNMENT)

static poolp usedpools[2 * ((NB_SMALL_SIZE_CLASSES + 7) / 8) * 8] = {

PT(0), PT(1), PT(2), PT(3), PT(4), PT(5), PT(6), PT(7)

#if NB_SMALL_SIZE_CLASSES > 8

, PT(8), PT(9), PT(10), PT(11), PT(12), PT(13), PT(14), PT(15)

#if NB_SMALL_SIZE_CLASSES > 16

, PT(16), PT(17), PT(18), PT(19), PT(20), PT(21), PT(22), PT(23)

#if NB_SMALL_SIZE_CLASSES > 24

, PT(24), PT(25), PT(26), PT(27), PT(28), PT(29), PT(30), PT(31)

#if NB_SMALL_SIZE_CLASSES > 32

, PT(32), PT(33), PT(34), PT(35), PT(36), PT(37), PT(38), PT(39)

#if NB_SMALL_SIZE_CLASSES > 40

, PT(40), PT(41), PT(42), PT(43), PT(44), PT(45), PT(46), PT(47)

#if NB_SMALL_SIZE_CLASSES > 48

, PT(48), PT(49), PT(50), PT(51), PT(52), PT(53), PT(54), PT(55)

#if NB_SMALL_SIZE_CLASSES > 56

, PT(56), PT(57), PT(58), PT(59), PT(60), PT(61), PT(62), PT(63)

#if NB_SMALL_SIZE_CLASSES > 64

#error "NB_SMALL_SIZE_CLASSES should be less than 64"

#endif /* NB_SMALL_SIZE_CLASSES > 64 */

#endif /* NB_SMALL_SIZE_CLASSES > 56 */

#endif /* NB_SMALL_SIZE_CLASSES > 48 */

#endif /* NB_SMALL_SIZE_CLASSES > 40 */

#endif /* NB_SMALL_SIZE_CLASSES > 32 */

#endif /* NB_SMALL_SIZE_CLASSES > 24 */

#endif /* NB_SMALL_SIZE_CLASSES > 16 */

#endif /* NB_SMALL_SIZE_CLASSES > 8 */

};

感覺這個數組有點怪異,別急我們來畫圖看一看

考慮一下當申請28字節的情形,前面我們說到,python首先會獲得size class index,顯然這里是3。那么在usedpools中,尋找第3+3=6個元素,發現usedpools[6]的值是指向usedpools[4]的地址。好暈啊,好吧,現在對照pool_header的定義來看一看usedpools[6]?->?nextpool這個指針指向哪里了呢?

//obmalloc.c

/* Pool for small blocks. */

struct pool_header {

union { block *_padding;

uint count; } ref; /* 當然pool里面的block數量 */

block *freeblock; /* 一個鏈表,指向下一個可用的block */

struct pool_header *nextpool; /* 指向下一個pool */

struct pool_header *prevpool; /* 指向上一個pool "" */

uint arenaindex; /* 在area里面的索引 */

uint szidx; /* block的大小(固定值?后面說) */

uint nextoffset; /* 下一個可用block的內存偏移量 */

uint maxnextoffset; /* 最后一個block距離開始位置的距離 */

};

顯然是從usedpools[6](即usedpools+4)開始向后偏移8個字節(一個ref的大小加上一個freeblock的大小)后的內存,正好是usedpools[6]的地址(即usedpools+6),這是python內部的trick

想象一下,當我們手中有一個size class為32字節的pool,想要將其放入這個usedpools中時,要怎么做呢?從上面的描述我們知道,只需要進行usedpools[i+i]?->?nextpool?=?pool即可,其中i為size class index,對應于32字節,這個i為3.當下次需要訪問size class 為32字節(size class index為3)的pool時,只需要簡單地訪問usedpools[3+3]就可以得到了。python正是使用這個usedpools快速地從眾多的pool中快速地尋找到一個最適合當前內存需求的pool,從中分配一塊block。

//obmalloc.c

static int

pymalloc_alloc(void *ctx, void **ptr_p, size_t nbytes)

{

block *bp;

poolp pool;

poolp next;

uint size;

...

LOCK();

//獲得size class index

size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT;

//直接通過usedpools[size+size],這里的size不就是我們上面說的i嗎?

pool = usedpools[size + size];

//如果usedpools中有可用的pool

if (pool != pool->nextpool) {

... //有可用pool

}

... //無可用pool,嘗試獲取empty狀態的pool

}

Pool的初始化

當python啟動之后,在usedpools這個小塊空間的內存池中,并不存在任何可用的內存,準確的說,不存在任何可用的pool。在這里,python采用了延遲分配的策略,即當我們確實開始申請小塊內存的時候,python才建立這個內存池。正如之前提到的,當我們開始申請28字節的內存時,python實際將申請32字節的內存,然后會首先根據32字節對應的class size index(3)在usedpools中對應的位置查找,如果發現在對應的位置后面沒有連接任何可用的pool,python會從"可用"arena鏈表中的第一個可用的arena中獲取的一個pool。不過需要注意的是,當前獲得的arena中包含的這些pools中可能會具有不同的class size index。

想象一下,當申請32字節的內存時,從"可用"arena中取出一個pool用作32字節的pool。當下一次內存分配請求分配64字節的內存時,python可以直接使用當前"可用"的arena的另一個pool即可,正如我們之前說的arena沒有size class的屬性,而pool才有。

//obmalloc.c

static int

pymalloc_alloc(void *ctx, void **ptr_p, size_t nbytes)

{

block *bp;

poolp pool;

poolp next;

uint size;

...

LOCK();

size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT;

pool = usedpools[size + size];

//如果usedpools中有可用的pool

if (pool != pool->nextpool) {

... //有可用pool

}

//無可用pool,嘗試獲取empty狀態的pool

if (usable_arenas == NULL) {

//嘗試申請新的arena,并放入"可用"arena鏈表

usable_arenas = new_arena();

if (usable_arenas == NULL) {

goto failed;

}

usable_arenas->nextarena =

usable_arenas->prevarena = NULL;

}

assert(usable_arenas->address != 0);

//從可用arena鏈表中第一個arena的freepools中抽取一個可用的pool

pool = usable_arenas->freepools;

if (pool != NULL) {

/* Unlink from cached pools. */

usable_arenas->freepools = pool->nextpool;

//調整可用arena鏈表中第一個arena中的可用pool的數量

--usable_arenas->nfreepools;

//如果調整之后變為0,則將該arena從可用arena鏈表中移除

if (usable_arenas->nfreepools == 0) {

/* Wholly allocated: remove. */

assert(usable_arenas->freepools == NULL);

assert(usable_arenas->nextarena == NULL ||

usable_arenas->nextarena->prevarena ==

usable_arenas);

usable_arenas = usable_arenas->nextarena;

if (usable_arenas != NULL) {

usable_arenas->prevarena = NULL;

assert(usable_arenas->address != 0);

}

}

else {

/* nfreepools > 0: it must be that freepools

* isn't NULL, or that we haven't yet carved

* off all the arena's pools for the first

* time.

*/

assert(usable_arenas->freepools != NULL ||

usable_arenas->pool_address <=

(block*)usable_arenas->address +

ARENA_SIZE - POOL_SIZE);

}

init_pool:

...

}

可以看到,如果開始時"可用"arena鏈表為空,那么python會通過new_arena申請一個arena,開始構建"可用"arena鏈表。還記得我們之前遺留了一個問題嗎?答案就在這里。在這里,一個脫離了"未使用"arena鏈表并轉變為"可用"的arena被納入了"可用"arena鏈表的控制。所以python會嘗試從"可用"arena鏈表中的第一個arena所維護的pool集合中取出一個可用的pool。如果成功地取出了這個pool,那么python就會進行一些維護信息的更新工作,甚至在當前arena中可用的pool已經用完了之后,將該arena從"可用"arena鏈表中移除

好了,現在我們手里有了一塊用于32字節內存分配的pool,為了提高以后內存分配的效率,我們需要將這個pool放入到usedpools中。這一步就是我們上面代碼中沒貼的init

//obmalloc.c

static int

pymalloc_alloc(void *ctx, void **ptr_p, size_t nbytes)

{

init_pool:

//將pool放入usedpools中

next = usedpools[size + size]; /* == prev */

pool->nextpool = next;

pool->prevpool = next;

next->nextpool = pool;

next->prevpool = pool;

pool->ref.count = 1;

//pool在之前就具有正確的size結構,直接返回pool中的一個block

if (pool->szidx == size) {

bp = pool->freeblock;

assert(bp != NULL);

pool->freeblock = *(block **)bp;

goto success;

}

//pool之前就具有正確的size結果,直接返回pool中的一個block

pool->szidx = size;

size = INDEX2SIZE(size);

bp = (block *)pool + POOL_OVERHEAD;

pool->nextoffset = POOL_OVERHEAD + (size << 1);

pool->maxnextoffset = POOL_SIZE - size;

pool->freeblock = bp + size;

*(block **)(pool->freeblock) = NULL;

goto success;

}

}

具體的細節可以自己觀察源代碼去研究,這里不再寫了,有點累

block的釋放

考察完了對block的分配,是時候來看看對block的釋放了。對block的釋放實際上就是將一塊block歸還給pool,我們已經知道pool可能存在3種狀態,在分別處于三種狀態,它們的位置是各不相同的。

當我們釋放一個block之后,可能會引起pool狀態的轉變,這種轉變可分為兩種情況

used狀態轉變為empty狀態

full狀態轉變為used狀態

//obmalloc.c

static int

pymalloc_free(void *ctx, void *p)

{

poolp pool;

block *lastfree;

poolp next, prev;

uint size;

pool = POOL_ADDR(p);

if (!address_in_range(p, pool)) {

return 0;

}

LOCK();

assert(pool->ref.count > 0); /* else it was empty */

//設置離散自由的block鏈表

*(block **)p = lastfree = pool->freeblock;

pool->freeblock = (block *)p;

//如果!lastfree成立,那么意味著不存在lastfree,說明這個pool在釋放block之前是滿的

if (!lastfree) {

/* Pool was full, so doesn't currently live in any list:

* link it to the front of the appropriate usedpools[] list.

* This mimics LRU pool usage for new allocations and

* targets optimal filling when several pools contain

* blocks of the same size class.

*/

//當前pool處于full狀態,在釋放一塊block之后,需要將其轉換為used狀態

//并重新鏈入到usedpools的頭部

--pool->ref.count;

assert(pool->ref.count > 0); /* else the pool is empty */

size = pool->szidx;

next = usedpools[size + size];

prev = next->prevpool;

pool->nextpool = next;

pool->prevpool = prev;

next->prevpool = pool;

prev->nextpool = pool;

goto success;

}

struct arena_object* ao;

uint nf; /* ao->nfreepools */

//否則到這一步表示lastfree有效

//pool回收了一個block之后,不需要從used狀態轉換為empty狀態

if (--pool->ref.count != 0) {

/* pool isn't empty: leave it in usedpools */

goto success;

}

/* Pool is now empty: unlink from usedpools, and

* link to the front of freepools. This ensures that

* previously freed pools will be allocated later

* (being not referenced, they are perhaps paged out).

*/

//否則說明pool為空

next = pool->nextpool;

prev = pool->prevpool;

next->prevpool = prev;

prev->nextpool = next;

//將pool放入freepools維護的鏈表中

ao = &arenas[pool->arenaindex];

pool->nextpool = ao->freepools;

ao->freepools = pool;

nf = ++ao->nfreepools;

if (nf == ao->ntotalpools) {

//調整usable_arenas鏈表

if (ao->prevarena == NULL) {

usable_arenas = ao->nextarena;

assert(usable_arenas == NULL ||

usable_arenas->address != 0);

}

else {

assert(ao->prevarena->nextarena == ao);

ao->prevarena->nextarena =

ao->nextarena;

}

/* Fix the pointer in the nextarena. */

if (ao->nextarena != NULL) {

assert(ao->nextarena->prevarena == ao);

ao->nextarena->prevarena =

ao->prevarena;

}

//調整"未使用"arena鏈表

ao->nextarena = unused_arena_objects;

unused_arena_objects = ao;

//程序走到這一步,表示是pool原先是used,釋放block之后依舊是used

//那么會將內存歸還給操作系統

_PyObject_Arena.free(_PyObject_Arena.ctx,

(void *)ao->address, ARENA_SIZE);

//設置address,將arena的狀態轉為"未使用"

ao->address = 0; /* mark unassociated */

--narenas_currently_allocated;

goto success;

}

}

實際上在python2.4之前,python的arena是不會釋放pool的。這樣的話就會引起內存泄漏,比如我們申請10?*?1024?*?1024個16字節的小內存,這就意味著必須使用160MB的內存,由于python會默認全部使用arena(這一點我們沒有提)來滿足你的需求。但是當我們將所有16字節的內存全部釋放了,這些內存也會回到arena的控制之中,這都沒有問題。但是問題來了,這些內存是被arena控制的,并沒有交給操作系統啊,,所以這160MB的內存始終會被python占用,如果后面程序再也不需要160MB如此巨大的內存,那么不就浪費了嗎?

由于這種情況必須在大量持續申請小內存對象時才會出現,因為大的話會自動交給操作系統了,小的才會由arena控制,而持續申請大量小內存的情況幾乎不會碰到,所以這個問題也就留在了 Python中。但是因為有些人發現了這個問題,所以這個問題在python2.5的時候就得到了解決。

因為早期的python,arena是沒有區分"未使用"和"可用"兩種狀態的,到了python2.5中,arena已經可以將自己維護的pool集合釋放,交給操作系統了,從而將"可用"狀態轉化為"未使用"狀態。而當python處理完pool,就開始處理arena了。

而對arena的處理實際上分為了4中情況

1.如果arena中所有的pool都是empty的,釋放pool集合所占用的內存

2.如果之前arena中沒有了empty的pool,那么在"可用"鏈表中就找不到該arena,由于現在arena中有了一個pool,所以需要將這個arena鏈入到"可用"鏈表的表頭

3.如果arena中的empty的pool的個數為n,那么會從"可用"arena鏈表中開始尋找arena可以插入的位置,將arena插入到"可用"鏈表。這樣操作的原因就在于"可用"arena鏈表實際上是一個有序的鏈表,從表頭開始往后,每一個arena中empty的pool的個數,即nfreepools,都不能大于前面的arena,也不能小于后面的arena。保持這樣有序性的原則是分配block時,是從"可用"鏈表的表頭開始尋找可用arena的,這樣就能保證如果一個arena的empty pool數量越多,它被使用的機會就越少。因此它最終釋放其維護的pool集合的內存的機會就越大,這樣就能保證多余的內存會被歸還給操作系統

4.其他情況,則不對arena進行任何處理。

內存池全景

前面我們已經提到了,對于一個用c開發的龐大的軟件(python是一門高級語言,但是執行對應代碼的解釋器則可以看成是c的一個軟件),其中的內存管理可謂是最復雜、最繁瑣的地方了。不同尺度的內存會有不同的抽象,這些抽象在各種情況下會組成各式各樣的鏈表,非常復雜。但是我們還是有可能從一個整體的尺度上把握整個內存池,盡管不同的鏈表變幻無常,但我們只需記住,所有的內存都在arenas(或者說那個存放多個arena的數組)的掌握之中 。

17.3 循環引用之垃圾回收

17.3.1 引用計數之垃圾回收

現在絕大部分語言都實現了垃圾回收機制,也包括python。然而python的垃圾回收和java,c#等語言有一個很大的不同,那就是python中大多數對象的生命周期是通過對象的引用計數來管理的,這一點在開始的章節我們就說了,對于python中最基礎的對象PyObject,有兩個屬性,一個是該對象的類型,還有一個就是引用計數(ob_refcnt)。不過從廣義上將,引用計數也算是一種垃圾回收機制,而且它是一中最簡單最直觀的垃圾回收計數。盡管需要一個值來維護引用計數,但是引用計數有一個最大的優點:實時性。任何內存,一旦沒有指向它的引用,那么就會被回收。而其他的垃圾回收技術必須在某種特定條件下(比如內存分配失敗)才能進行無效內存的回收。

引用計數機制所帶來的維護引用計數的額外操作,與python運行中所進行的內存分配、釋放、引用賦值的次數是成正比的。這一點,相對于主流的垃圾回收技術,比如標記--清除(mark--sweep)、停止--復制(stop--copy)等方法相比是一個弱點,因為它們帶來額外操作只和內存數量有關,至于多少人引用了這塊內存則不關心。因此為了與引用計數搭配、在內存的分配和釋放上獲得最高的效率,python設計了大量的內存池機制,比如小整數對象池、字符串的intern機制,列表的freelist緩沖池等等,這些大量使用的面向特定對象的內存池機制正是為了彌補引用計數的軟肋。

其實對于現在的cpu和內存來說,上面的問題都不是什么問題。但是引用計數還存在一個致命的缺陷,這一缺陷幾乎將引用計數機制在垃圾回收技術中判處了"死刑",這一技術就是"循環引用"。而且也正是因為"循環引用"這個致命傷,導致在狹義上并不把引用計數機制看成是垃圾回收技術

在介紹循環引用之前,先來看看python引用計數什么時候會增加,什么時候會減少。

引用計數加一

對象被創建:a=1

對象被引用:b=a

對象被作為參數傳到一個函數中,func(a)

對象作為列表、元組等其他容器里面的元素

引用計數減一

對象別名被顯式的銷毀:del a

對象的引用指向了其他的對象:a=2

對象離開了它的作用域,比如函數的局部變量,在函數執行完畢的時候,也會被銷毀(如果沒有獲取棧幀的話),而全局變量則不會

對象所在的容器被銷毀,或者從容器中刪除等等

查看引用計數

查看一個對象的引用計數,可以通過sys.getrefcount(obj),但是由于作為getrefcount這個函數的參數,所以引用計數會多1。

我們之前說,a =?"mashiro",相當于把a和a對應的值組合起來放在了命名空間里面,那么你認為這個a對應的值是什么呢?難道是"mashiro"這個字符串嗎?其實從python的層面上來看的話確實是這樣,但是在python的底層,其實存儲的是字符數組"mashiro"對應地址,我總覺得前面章節好像說錯了。

b=a在底層中則表示把a的指針拷貝給了b,是的你沒有看錯,都說python傳遞的是符號,但是在底層就是傳遞了一個指針,無論什么傳遞的都是指針,在python的層面上傳遞就是符號、或者就是引用。所以我們看到, 每當多了一個引用,那么"mashiro"(在c的層面上是一個結構體,PyUnicodeObject)的引用計數就會加1.

而每當減少一個引用,引用計數就會減少1。盡管我們用sys.getrefcount得到的結果是2,但是當這個函數執行完,由于局部變量的銷毀,其實結果已經變成了1。因此引用計數很方便,就是當一片空間沒有人引用了,那么就直接銷毀。盡管維護這個引用計數需要消耗資源,可還是那句話,對于如今的硬件資源來說,是完全可以接受的,畢竟引用計數真的很方便。但是,是的我要說但是了,就是我們之前的那個循環引用的問題。

l1 = []

l2 = []

l1.append(l2)

l2.append(l1)

del l1, l2

初始的時候,l1和l2指向的內存的引用計數都為1,但是l1.append(l2),那么l2指向內存的引用計數變成了2,同理l2.append(l1)導致l1指向內存的引用計數也變成了2。因此當我們del l1,?l2的時候,引用計數會從2變成1,因此l1和l2都不會被回收,因為我們是希望回收l1和l2的,但是如果只有引用計數的話,那么顯然這兩者是回收不了的。因此這算是引用計數的最大的缺陷,因為會導致內存泄漏。因此python為了解決這個問題,就必須在引用計數機制之上又引入了新的主流垃圾回收計數:標記--清除和分代收集計數來彌補這個最致命的漏洞。

17.3.2 三色標記模型

無論何種垃圾回收機制,一般都分為兩個階段:垃圾檢測和垃圾回收。垃圾檢測是從所有的已經分配的內存中區別出"可回收"和"不可回收"的內存,而垃圾回收則是使操作系統重新掌握垃圾檢測階段所標識出來的"可回收"內存塊。所以垃圾回收,并不是說直接把這塊內存的數據清空了,而是說將使用權從新交給了操作系統,不會自己霸占了。下面我們來看看標記--清除(mark--sweep)方法是如何實現的,并為這個過程建立一個三色標記模型,python中的垃圾回收正是基于這個模型完成的。

從具體的實現上來講,標記--清除方法同樣遵循垃圾回收的兩個階段,其簡要過程如下:

尋找根對象(root object)的集合,所謂的root object就是一些全局引用和函數棧的引用。這些引用所用的對象是不可被刪除的,而這個root object集合也是垃圾檢測動作的起點

從root object集合出發,沿著root object集合中的每一個引用,如果能到達某個對象A,則稱A是可達的(reachable),可達的對象也不可被刪除。這個階段就是垃圾檢測階段

當垃圾檢測階段結束后,所有的對象分為了可達的(reachable)和不可達的(unreachable)。而所有可達對象都必須予以保留,而不可達對象所占用的內存將被回收。

在垃圾回收動作被激活之前,系統中所分配的所有對象和對象之間的引用組成了一張有向圖,其中對象是圖中的節點,而對象間的引用則是圖的邊。我們在這個有向圖的基礎之上建立一個三個標注模型,更形象的展示垃圾回收的整個動作。當垃圾回收開始時,我們假設系統中的所有對象都是不可達的,對應在有向圖上就是白色 。隨后從垃圾回收的動作開始,沿著始于root object集合中的某個object的引用鏈,在某個時刻到達了對象A,那我們把A標記為灰色,灰色表示一個對象是可達的,但是其包含的引用還沒有被檢查。當我們檢查了對象A所包含的所有引用之后,A將被標記為黑色,表示其包含的所有引用已經被檢查過了。顯然,此時A中引用的對象則被標記成了灰色。假如我們從root object集合開始,按照廣度優先的策略進行搜索的話,那么不難想象,灰色節點對象集合就如同波紋一樣,不斷向外擴散,隨著所有的灰色節點都變成了黑色節點,也就意味著垃圾檢測階段結束了。

17.4 python中的垃圾回收

如之前所說,python中主要的內存管理手段是引用計數機制,而標記--清除和分代收集只是為了打破循環引用而引入的補充技術。這一事實意味著python中的垃圾回收只關注可能會產生循環引用的對象,而像PyLongObject、PyUnicodeObject這些對象是絕對不可能產生循環引用的,因為它們內部不可能持有對其他對象的引用,所以這些直接通過引用計數機制就可以實現,而且后面我們說的垃圾回收也專指那些可能產生循環引用的對象。python中的循環引用只會總是發生在container對象之間,所謂container對象就是內部可持有對其他對象的引用的對象,比如list、dict、class、instance等等。當python開始垃圾回收機制開始運行時,只需要檢查這些container對象,而對于PyLongObject、PyUnicodeObject則不需要理會,這使得垃圾回收帶來的開銷只依賴于container對象的數量,而非所有對象的數量。為了達到這一點,python就必須跟蹤所創建的每一個container對象,并將這些對象組織到一個集合中,只有這樣,才能將垃圾回收的動作限制在這些對象上。而python采用了一個雙向鏈表,所有的container對象在創建之后,都會被插入到這個鏈表當中。

17.4.1 可收集對象鏈表

在對python對象機制的分析當中我們已經看到,任何一個python對象都可以分為兩部分,一部分是PyObject_HEAD,另一部分是對象自身的數據。然而對于一個需要被垃圾回收機制跟蹤的container來說,還不夠,因為這個對象還必須鏈入到python內部的可收集對象鏈表中。而一個container對象要想成為一個可收集的對象,則必須加入額外的信息,這個信息位于PyObject_HEAD之前,稱為PyGC_Head

//objimpl.h

typedef union _gc_head {

struct {

union _gc_head *gc_next;

union _gc_head *gc_prev;

Py_ssize_t gc_refs;

} gc;

long double dummy; /* force worst-case alignment */

// malloc returns memory block aligned for any built-in types and

// long double is the largest standard C type.

// On amd64 linux, long double requires 16 byte alignment.

// See bpo-27987 for more discussion.

} PyGC_Head;

所以,對于python所創建的可收集container對象,其內存分布與我們之前所了解的內存布局是不同的,我們可以從可收集container對象的創建過程中窺見其內存分布。

//Modules/gcmodule.c

PyObject *

_PyObject_GC_New(PyTypeObject *tp)

{

PyObject *op = _PyObject_GC_Malloc(_PyObject_SIZE(tp));

if (op != NULL)

op = PyObject_INIT(op, tp);

return op;

}

PyObject *

_PyObject_GC_Malloc(size_t basicsize)

{

return _PyObject_GC_Alloc(0, basicsize);

}

#define GC_UNTRACKED _PyGC_REFS_UNTRACKED

#define _PyGC_REFS_UNTRACKED (-2) //該行位于objimpl.h中

static PyObject *

_PyObject_GC_Alloc(int use_calloc, size_t basicsize)

{

PyObject *op;

PyGC_Head *g;

size_t size;

//將對象和PyGC_Head所需內存加起來

if (basicsize > PY_SSIZE_T_MAX - sizeof(PyGC_Head))

return PyErr_NoMemory();

size = sizeof(PyGC_Head) + basicsize;

//為對象本身和PyGC_Head申請內存

if (use_calloc)

g = (PyGC_Head *)PyObject_Calloc(1, size);

else

g = (PyGC_Head *)PyObject_Malloc(size);

if (g == NULL)

return PyErr_NoMemory();

g->gc.gc_refs = 0;

_PyGCHead_SET_REFS(g, GC_UNTRACKED);

_PyRuntime.gc.generations[0].count++; /* number of allocated GC objects */

if (_PyRuntime.gc.generations[0].count > _PyRuntime.gc.generations[0].threshold &&

_PyRuntime.gc.enabled &&

_PyRuntime.gc.generations[0].threshold &&

!_PyRuntime.gc.collecting &&

!PyErr_Occurred()) {

_PyRuntime.gc.collecting = 1;

collect_generations();

_PyRuntime.gc.collecting = 0;

}

op = FROM_GC(g);

return op;

}

因此我們可以很清晰的看到,當python為可收集的container對象申請內存空間時,為PyGC_Head也申請了空間,并且其位置位于container對象之前。所以對于PyListObject、PyDictObject等container對象的內存分布的推測就應該變成這樣。

在可收集container對象的內存分布中,內存分為三個部分,首先第一塊用于垃圾回收機制,然后緊跟著的是python中所有對象都會有的PyObject_HEAD,最后才是container自身的數據。這里的container對象,既可以是PyDictObject、也可以是PyListObject等等。

//objimpl.h

typedef union _gc_head {

struct {

union _gc_head *gc_next;

union _gc_head *gc_prev;

Py_ssize_t gc_refs;

} gc;

long double dummy; /* force worst-case alignment */

// malloc returns memory block aligned for any built-in types and

// long double is the largest standard C type.

// On amd64 linux, long double requires 16 byte alignment.

// See bpo-27987 for more discussion.

} PyGC_Head;

再來看看PyGC_Head的模樣,里面除了兩個建立鏈表結構的前向和后向指針外,還有一個gc_ref,而這個值被初始化為GC_UNTRACKED,在上面的代碼中可以看到。這個變量對于垃圾回收的運行至關重要,但是在分析它之前我們還需要了解一些其他的東西。

當垃圾回收機制運行期間,我們需要在一個可收集的container對象的PyGC_Head部分和PyObject_HEAD部分之間來回切換。更清楚的說,某些時候,我們持有一個對象A的PyObject_HEAD的地址,但是我們需要根據這個地址來獲得PyGC_Head的地址;而且某些時候,我們又需要反過來進行逆運算。而python提供了兩個地址之間的轉換算法

//gcmodule.c

//AS_GC,根據PyObject_HEAD得到PyGC_Head

#define AS_GC(o) ((PyGC_Head *)(o)-1)

//FROM_GC,從PyGC_Head那里得到PyObject_HEAD

#define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1))

//objimpl.h

#define _Py_AS_GC(o) ((PyGC_Head *)(o)-1)

在PyGC_Head中,出現了用于建立鏈表的兩個指針,只有將創建的可收集container對象鏈接到python內部維護的可收集對象鏈表中,python的垃圾回收機制才能跟蹤和處理這個container對象。但是我們發現,在創建可收集container對象之時,并沒有立刻將這個對象鏈入到鏈表中。實際上,這個動作是發生在創建某個container對象最后一步,以PyListObject的創建舉例。

//listobject.c

PyObject *

PyList_New(Py_ssize_t size)

{

PyListObject *op;

...

Py_SIZE(op) = size;

op->allocated = size;

//創建PyListObject對象、并設置完屬性之后,返回之前,通過這一步_PyObject_GC_TRACK將所創建的container對象鏈接到了python中的可收集對象鏈表中。

_PyObject_GC_TRACK(op);

return (PyObject *) op;

}

//objimpl.h

#define _PyObject_GC_TRACK(o) do { \

PyGC_Head *g = _Py_AS_GC(o); \

if (_PyGCHead_REFS(g) != _PyGC_REFS_UNTRACKED) \

Py_FatalError("GC object already tracked"); \

_PyGCHead_SET_REFS(g, _PyGC_REFS_REACHABLE); \

g->gc.gc_next = _PyGC_generation0; \

g->gc.gc_prev = _PyGC_generation0->gc.gc_prev; \

g->gc.gc_prev->gc.gc_next = g; \

_PyGC_generation0->gc.gc_prev = g; \

} while (0);

前面我們說過,python會將自己的垃圾回收機制限制在其維護的可收集對象鏈表上,因為所有的循環引用一定是發生這個鏈表的一群對象之間。在_PyObject_GC_TRACK之后,我們創建的container對象也就置身于python垃圾回收機制的掌控機制當中了。

同樣的,python還提供將一個container對象從鏈表中摘除的方法,顯然這個方法應該會在對象被銷毀的時候調用。

//objimpl.h

#define _PyObject_GC_UNTRACK(o) do { \

PyGC_Head *g = _Py_AS_GC(o); \

assert(_PyGCHead_REFS(g) != _PyGC_REFS_UNTRACKED); \

_PyGCHead_SET_REFS(g, _PyGC_REFS_UNTRACKED); \

g->gc.gc_prev->gc.gc_next = g->gc.gc_next; \

g->gc.gc_next->gc.gc_prev = g->gc.gc_prev; \

g->gc.gc_next = NULL; \

} while (0);

很明顯,_PyObject_GC_UNTRACK只是_PyObject_GC_TRACK的逆運算而已

17.4.2 分代的垃圾收集

無論什么語言,寫出來的程序都有共同之處。那就是不同對象的聲明周期會存在不同,有的對象所占的內存塊的生命周期很短,而有的內存塊的生命周期則很長,甚至可能從程序的開始持續到程序結束。這兩者的比例大概在80~90%

這對于垃圾回收機制有著重要的意義,因為我們已經知道,像標記--清除這樣的垃圾回收機制所帶來的額外操作實際上是和系統中內存塊的數量是相關的,當需要回收的內存塊越多的時候,垃圾檢測帶來的額外操作就越多,相反則越少。因此我們可以采用一種空間換時間的策略,因為目前所有對象都在一個鏈子上,每當進行垃圾回收機制的時候,都要把所有對象都檢查一遍。而其實也有不少比較穩定的對象(在多次垃圾回收的洗禮下能活下來),我們完全沒有必要每次都檢查,或者說檢查的頻率可以降低一些。于是聰明如你已經猜到了,我們再來一根鏈子不就可以了,把那些認為比較穩定的對象移到另外一條鏈子上,而新的鏈子進行垃圾回收的頻率會低一些,總之頻率不會像初始的鏈子那么高。

所以這種思想就是:將系統中的所有內存塊根據其存活時間劃分為不同的集合,每一個集合就成為一個"代",垃圾回收的頻率隨著"代"的存活時間的增大而減小,也就是說,存活的越長的對象就越可能不是垃圾,就越可能是程序中需要一直存在的對象,就應該少去檢測它。反正不是垃圾,你檢了也白檢。那么關鍵的問題來了,這個存活時間是如何被衡量的呢?或者我們說當對象比較穩定的時候的這個穩定是如何衡量的呢?沒錯,我們上面已經暴露了,就是通過經歷了幾次垃圾回收動作來評判,如果一個對象經歷的垃圾回收次數越多,那么顯然其存活時間就越長。因為python的垃圾回收器,每當條件滿足時(至于什么條件我們后面會說),就會進行一次垃圾回收(注意:不同的代的垃圾回收的頻率是不同的),而每次掃黃的時候你都不在,吭,每次垃圾回收的時候你都能活下來,這就說明你存活的時間更長,或者像我們上面說的更穩定,那么就不應該再把你放在這個鏈子上了,而是會移動到新的鏈子上。而在新的鏈子上,進行垃圾回收的頻率會降低,因為既然穩定了,檢測就不必那么頻繁了,或者說新的鏈子上觸發垃圾回收所需要的時間更長了。

"代"似乎是一個比較抽象的概念,但在python中,你就把"代"想象成多個對象組成集合,或者你把"代"想象成鏈表(或者鏈子)也可以,因為這些對象都串在鏈表上面。而屬于同一"代"的內存塊都被鏈接在同一個鏈表中。而在python中總共存在三條鏈表,說明python中所有的對象總共可以分為三代,分別零代、一代、二代。一個"代"就是一條我們上面提到的可收集對象鏈表。而在前面所介紹的鏈表的基礎之上,為了支持分代機制,我們需要的僅僅是一個額外的表頭而已。

//Include/internal/mem.h

struct gc_generation {

PyGC_Head head;

int threshold; /* collection threshold */

int count; /* count of allocations or collections of younger

generations */

};

#define NUM_GENERATIONS 3

//gcmodule.c

#define GEN_HEAD(n) (&_PyRuntime.gc.generations[n].head)

struct gc_generation generations[NUM_GENERATIONS] = {

/* PyGC_Head, threshold, count */

{{{_GEN_HEAD(0), _GEN_HEAD(0), 0}}, 700, 0},

{{{_GEN_HEAD(1), _GEN_HEAD(1), 0}}, 10, 0},

{{{_GEN_HEAD(2), _GEN_HEAD(2), 0}}, 10, 0},

};

state->generation0 = GEN_HEAD(0);

上面這個維護了三個gc_generation結構的數組,通過這個數組控制了三條可收集對象鏈表,這就是python中用于分代垃圾收集的三個"代"。

而我們在之前上面說的_PyObject_GC_TRACK中會看到_PyGC_generation0,它不偏不斜,指向的正是第0代鏈表。

對于每一個gc_generation,其中的count記錄了當前這條可收集對象鏈表中一共有多少個對象。而在_PyObject_GC_Alloc中我們可以看到每當分配了內存,就會進行_PyRuntime.gc.generations[0].count++動作,將第0代鏈表中所維護的內存塊數量加1,這預示著所有新創建的對象實際上都會被加入到0代鏈表當中,而這一點也確實如此,已經被_PyObject_GC_TRACK證明了。而且我們發現這里是先將數量加1,然后再將新的container對象(內存塊)才會被鏈接到第0代鏈表當中,當然這個無所謂啦。

而gc_generation中的threshold則記錄該條可收集對象鏈表中最多可以容納多少個可收集對象,從python的實現代碼中,我們知道第0代鏈表中最多可以容納700個對象(只可能是container對象)。而一旦第0代鏈表中的container對象超過了700個這個閾值,那么會立刻除法垃圾回收機制。

static Py_ssize_t

collect_generations(void)

{

int i;

Py_ssize_t n = 0;

for (i = NUM_GENERATIONS-1; i >= 0; i--) {

//當count大于threshold的時候,但是這個僅僅針對于0代鏈表

if (_PyRuntime.gc.generations[i].count > _PyRuntime.gc.generations[i].threshold) {

if (i == NUM_GENERATIONS - 1

&& _PyRuntime.gc.long_lived_pending < _PyRuntime.gc.long_lived_total / 4)

continue;

n = collect_with_callback(i);

break;

}

}

return n;

}

這里面雖然寫了一個for循環,但是只有當第0代鏈表的count超過了threshold的時候才會觸發垃圾回收,那么1代鏈表和2代鏈表觸發垃圾回收的條件又是什么呢?當0代鏈表觸發了10次垃圾回收的時候,會觸發一次1代鏈表的垃圾回收。當1代鏈表觸發了10次垃圾回收的時候,會觸發一次2代鏈表的垃圾回收。另外:

在清理1代鏈表的時候,會順帶清理0代鏈表

在清理2代鏈表的時候,會順帶清理0代鏈表和1代鏈表

17.4.3 python中的標記--清除

我們上面說到,當清理1代鏈表會順帶清理0代鏈表,總是就是把比自己"代"要小的鏈子也清理了。那么這是怎么做到的呢?其實答案就在gc_list_merge函數中,如果清理的是1代鏈表,那么在開始垃圾回收之前,python會將0代鏈表(比它年輕的),整個地鏈接到1代鏈表之后。

//gcmodule.c

static void

gc_list_merge(PyGC_Head *from, PyGC_Head *to)

{

PyGC_Head *tail;

assert(from != to);

if (!gc_list_is_empty(from)) {

tail = to->gc.gc_prev;

tail->gc.gc_next = from->gc.gc_next;

tail->gc.gc_next->gc.gc_prev = tail;

to->gc.gc_prev = from->gc.gc_prev;

to->gc.gc_prev->gc.gc_next = to;

}

gc_list_init(from);

}

以我們舉的例子來說的話,那么這里的from就是0代鏈表,to就是1代鏈表,所以此后的標記--清除算法就將在merge之后的那一條鏈表上進行。

在介紹python中的標記--清除垃圾回收方法之前,我們需要建立一個循環引用的最簡單例子

list1 = []

list2 = []

list1.append(list2)

list2.append(list1)

# 注意這里多了一個外部引用

a = list1

list3 = []

list4 = []

list3.append(list4)

list4.append(list3)

上面的數字指的是當前對象的引用計數ob_refcnt的值

17.4.3.1 尋找root object集合

為了使用標記--清除算法,按照我們之前對垃圾收集算法的一般性描述,首先我們需要找到root object,那么在我們上面的那幅圖中,哪些是屬于root object呢?

讓我們換個角度來思考,前面提到,root object是不能被刪除的對象。也就是說,在可收集對象鏈表的外部存在著某個引用在引用這個對象,刪除這個對象會導致錯誤的行為,那么在我們當前這個例子中只有list1是屬于root object的。但這僅僅是觀察的結果,那么如何設計一種算法來得到這個結果呢?

我們注意到這樣一個事實,如果兩個對象的引用計數都為1,但是僅僅它們之間存在著循環引用,那么這兩個對象是需要被回收的,也就是說,盡管它們的引用計數表現為非0,但是實際上有效的引用計數為0。這里,我們提出了有效引用計數的概念,為了從引用計數中獲得優秀的引用計數,必須將循環引用的影響取出,也就是說,這個閉環從引用中摘除,而具體的實現就是兩個對象各自的引用值都減去1。這樣一來,兩個對象的引用計數都成為了0,這樣我們便揮去了循環引用的迷霧,是有效引用計數出現了真身。那么如何使兩個對象的引用計數都減1呢,很簡單,假設這兩個對象為A和B,那么從A出發,由于它有一個對B的引用,則將B的引用計數減1;然后順著引用達到B,發現它有一個對A的引用,那么同樣會將A的引用減1,這樣就完成了循環引用對象間環的刪除。

總結一下就是,python會尋找那些具有循環引用的、但是沒有被外部引用的對象,并嘗試把它們的引用計數都減去1

但是這樣就引出了一個問題,假設可收集對象鏈表中的container對象A有一個對對象C的引用,而C并不在這個鏈表中,如果將C的引用計數減去1,而最后A并沒有被回收,那么顯然,C的引用計數被錯誤地減少1,這將導致未來的某個時刻對C的引用會出現懸空。這就要求我們必須在A沒有被刪除的情況下回復C的引用計數,可是如果采用這樣的方案的話,那么維護引用計數的復雜度將成倍增長。換一個角度,其實我們有更好的做法,我們不改動真實的引用計數,而是改動引用計數的副本。對于副本,我們無論做什么樣的改動,都不會影響對象生命周期的維護,因為這個副本的唯一作用就是尋找root? object集合,而這個副本就是PyGC_Head中的gc.gc_ref。在垃圾回收的第一步,就是遍歷可收集對象鏈表,將每個對象的gc.gc_ref的值設置為其ob_refcnt的值。

//gcmodule.c

static void

update_refs(PyGC_Head *containers)

{

PyGC_Head *gc = containers->gc.gc_next;

for (; gc != containers; gc = gc->gc.gc_next) {

assert(_PyGCHead_REFS(gc) == GC_REACHABLE);

_PyGCHead_SET_REFS(gc, Py_REFCNT(FROM_GC(gc)));

assert(_PyGCHead_REFS(gc) != 0);

}

}

//而接下來的動作就是要將環引用從引用中摘除

static void

subtract_refs(PyGC_Head *containers)

{

traverseproc traverse;

PyGC_Head *gc = containers->gc.gc_next;

for (; gc != containers; gc=gc->gc.gc_next) {

traverse = Py_TYPE(FROM_GC(gc))->tp_traverse;

(void) traverse(FROM_GC(gc),

(visitproc)visit_decref,

NULL);

}

}

我們注意到里面有一個traverse,這個是和特定的container 對象有關的,在container對象的類型對象中定義。一般來說,traverse的動作就是遍歷container對象中的每一個引用,然后對引用進行某種動作,而這個動作在subtract_refs中就是visit_decref,它以一個回調函數的形式傳遞到traverse操作中。比如:我們來看看PyListObject對象所定義traverse操作。

//object.h

typedef int (*visitproc)(PyObject *, void *);

typedef int (*traverseproc)(PyObject *, visitproc, void *);

//listobject.c

PyTypeObject PyList_Type = {

...

(traverseproc)list_traverse, /* tp_traverse */

...

};

static int

list_traverse(PyListObject *o, visitproc visit, void *arg)

{

Py_ssize_t i;

for (i = Py_SIZE(o); --i >= 0; )

//對列表中的每一個元素都進行回調的操作

Py_VISIT(o->ob_item[i]);

return 0;

}

//gcmodule.c

/* A traversal callback for subtract_refs. */

static int

visit_decref(PyObject *op, void *data)

{

assert(op != NULL);

//PyObject_IS_GC判斷op指向的對象是不是被垃圾收集監控的

//標識container對象是被垃圾收集監控的

if (PyObject_IS_GC(op)) {

PyGC_Head *gc = AS_GC(op);

assert(_PyGCHead_REFS(gc) != 0); /* else refcount was too small */

if (_PyGCHead_REFS(gc) > 0)

_PyGCHead_DECREF(gc);

}

return 0;

}

在完成了subtract_refs之后,可收集對象鏈表中所有container對象之間的環引用就被摘除了。這時有一些container對象的PyGC_Head.gc_ref還不為0,這就意味著存在對這些對象的外部引用,這些對象就是開始標記--清除算法的root object。

估計有人不明白引用計數是加在什么地方,其實變量=值在python中,變量得到的都是值的指針,a =?1,表示是在命名空間里面會有"a": 1這個鍵值對,但看似是這樣,其實存儲的并不是1,而是1這個結構體(python對象在底層是一個結構體)的指針,這個結構體存儲在堆區。我們獲取a的引用計數,其實是獲取a指向的這個對象的引用計數,此時為1,如果b=a,在底層就等價于把a存儲的內容(指針)拷貝給了b,那么此時a和b存儲的指針指的都是同一個對象,那么這個對象的引用計數就變成了2。如果再來個b=2,那么表示再創建一個結構體存儲的值為2,然后讓b存儲新的結構體的指針。那么原來的結構體的引用計數就從2又變成了1。

所以為什么初始的時候,list1的引用計數是3就很明顯了,list1的引用計數指的其實是list1這個變量對應的值(或者說在底層,list1存儲的指針指向的值)的引用計數,所以一旦創建一個變量那么引用計數會自動增加為1,然后a也指向了list1所指向的內存,并且list1又作為list2的一個元素(這個位置的元素存儲了指向list1的指針),所以引用計數總共是3。

由于sys.getrefcount函數本身會多一個引用,所以減去1的話,那么都是3。表示它們指向的內存存儲的值的引用計數為3。sys.getrefcount(a) -> 4,這個時候a就想到了,除了我,還有兩位老鐵指向了我指向的內存。

17.4.3.2 垃圾標記

假設我們現在執行了刪除操作del?list1, list2,?list3, list4,那么成功地尋找到root object集合之后,我們就可以從root object觸發,沿著引用鏈,一個接一個地標記不能回收的內存,由于root object集合中的對象是不能回收的,因此,被這些對象直接或間接引用的對象也是不能回收的,比如這里的list2,即便del list2,但是因為list1不能回收,而又append了list2,所以list2指向的內存也是不可以釋放的。下面在從root object出發前,我們首先需要將現在的內存鏈表一分為二,一條鏈表維護root object集合,成為root鏈表,而另一條鏈表中維護剩下的對象,成為unreachable鏈表。之所以要分解成兩個鏈表,是出于這樣一種考慮:顯然,現在的unreachable鏈表是名不副實的,因為里面可能存在被root鏈表中的對象直接或者間接引用的對象,這些對象也是不可以回收的,因此一旦在標記中發現了這樣的對象,那么就應該將其從unreachable中移到root鏈表中;當完成標記之后,unreachable鏈表中剩下的對象就是名副其實的垃圾對象了,那么接下來的垃圾回收只需要限制在unreachable鏈表中即可。

為此python專門準備了一條名為unreachable的鏈表,通過move_unreachable函數完成了對原始鏈表的切分。

//gcmodule.c

static void

move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)

{

PyGC_Head *gc = young->gc.gc_next;

while (gc != young) {

PyGC_Head *next;

//[1]:如果是root object

if (_PyGCHead_REFS(gc)) {

PyObject *op = FROM_GC(gc);

traverseproc traverse = Py_TYPE(op)->tp_traverse;

assert(_PyGCHead_REFS(gc) > 0);

//設置其gc_refs為GC_REACHABLE

_PyGCHead_SET_REFS(gc, GC_REACHABLE);

(void) traverse(op,

(visitproc)visit_reachable,

(void *)young);

next = gc->gc.gc_next;

if (PyTuple_CheckExact(op)) {

_PyTuple_MaybeUntrack(op);

}

}

else {

//[2]:對于非root object,移到unreachable鏈表中

next = gc->gc.gc_next;

gc_list_move(gc, unreachable);

_PyGCHead_SET_REFS(gc, GC_TENTATIVELY_UNREACHABLE);

}

gc = next;

}

}

static int

visit_reachable(PyObject *op, PyGC_Head *reachable)

{

if (PyObject_IS_GC(op)) {

PyGC_Head *gc = AS_GC(op);

const Py_ssize_t gc_refs = _PyGCHead_REFS(gc);

//[3]:對于還沒有處理的對象,恢復其gc_refs

if (gc_refs == 0) {

_PyGCHead_SET_REFS(gc, 1);

}

//[4]:對于已經被挪到unreachable鏈表中的對象,將其再次挪動到原來的鏈表

else if (gc_refs == GC_TENTATIVELY_UNREACHABLE) {

gc_list_move(gc, reachable);

_PyGCHead_SET_REFS(gc, 1);

}

else {

assert(gc_refs > 0

|| gc_refs == GC_REACHABLE

|| gc_refs == GC_UNTRACKED);

}

}

return 0;

}

在move_unreachable中,沿著可收集對象鏈表依次向前,并檢查其PyGC_Head.gc.gc_ref值,我們發現這里的動作是遍歷鏈表,而并非從root object集合出發,遍歷引用鏈。這會導致一個微妙的結果,即當檢查到一個gc_ref為0的對象時,我們并不能立即斷定這個對象就是垃圾對象。因為在這個對象之后的對象鏈表上,也許還會遇到一個root object,而這個root object引用該對象。所以這個對象只是一個可能的垃圾對象,因此我們才要將其標志為GC_TENTATIVELY_UNREACHABLE,但是還是通過gc_list_move將其搬到了unreachable鏈表中,咦,難道不會出問題嗎?別急,我們馬上就會看到, python還留了后手。

當在move_unreachable中遇到一個gc_refs不為0的對象A時,顯然,A是root object或者是從某個root object開始可以引用到的對象,而A所引用的所有對象也都是不可回收的對象。因此在代碼的[1]處下面,我們看到會再次調用與特定對象相關的transverse操作,依次對A所引用的對象調用visit_reachable。在visit_reachable的[4]處我們發現,如果A所引用的對象之前曾被標注為GC_TENTATIVELY_UNREACHABLE,那么現在A可以訪問到它,意味著它也是一個不可回收的對象,所以python會再次從unreachable鏈表中將其搬回到原來的鏈表。注意:這里的reachable,就是move_unreachable中的young,也就是我們所謂的root object鏈表。python還會將其gc_refs設置為1,表示該對象是一個不可回收對象。同樣在[1]處,我們看到對A所引用的gc_refs為0的對象,其gc_refs也被設置成了1。想一想這是什么對象呢?顯然它就是在鏈表move_unreachable操作中還沒有訪問到的對象,這樣python就直接掐斷了之后move_unreachable訪問它時將其移動到unreachable鏈表的誘因。

當move_unreachable完成之后,最初的一條鏈表就被切分成了兩條鏈表,在unreachable鏈表中,就是我們發現的垃圾對象,是垃圾回收的目標。但是等一等,在unreachable鏈表中,所有的對象都可以安全回收嗎?其實,垃圾回收在清理對象的時候,默認是會清理的,但是一旦當我們定義了函數__del__,那么在清理對象的時候就會調用這個__del__方法,因此也叫析構函數,這是python為開發人員提供的在對象被銷毀時進行某些資源釋放的Hook機制。在python3中,即使我們重寫了也沒事,因為python會把含有__del__函數的PyInstanceObject對象都統統移動到一個名為garbage的PyListObject對象中。

17.4.4.4 垃圾回收

要回收unreachable鏈表中的垃圾對象,就必須先打破對象間的循環引用,前面我們已經闡述了如何打破循環引用的辦法,下面來看看具體的銷毀過程

//gcmodule.c

static int

gc_list_is_empty(PyGC_Head *list)

{

return (list->gc.gc_next == list);

}

static void

delete_garbage(PyGC_Head *collectable, PyGC_Head *old)

{

inquiry clear;

while (!gc_list_is_empty(collectable)) {

PyGC_Head *gc = collectable->gc.gc_next;

PyObject *op = FROM_GC(gc);

if (_PyRuntime.gc.debug & DEBUG_SAVEALL) {

PyList_Append(_PyRuntime.gc.garbage, op);

}

else {

if ((clear = Py_TYPE(op)->tp_clear) != NULL) {

Py_INCREF(op);

clear(op);

Py_DECREF(op);

}

}

if (collectable->gc.gc_next == gc) {

/* object is still alive, move it, it may die later */

gc_list_move(gc, old);

_PyGCHead_SET_REFS(gc, GC_REACHABLE);

}

}

}

其中會調用container對象的類型對象中的tp_clear操作,這個操作會調整container對象中引用的對象的引用計數值,從而打破完成循環的最終目標。還是以PyListObject為例:

//listobject.c

static int

_list_clear(PyListObject *a)

{

Py_ssize_t i;

PyObject **item = a->ob_item;

if (item != NULL) {

i = Py_SIZE(a);

//將ob_size調整為0

Py_SIZE(a) = 0;

//ob_item是一個二級指針,本來指向一個數組的指針

//現在指向為NULL

a->ob_item = NULL;

//容量也設置為0

a->allocated = 0;

while (--i >= 0) {

//數組里面元素也全部減少引用計數

Py_XDECREF(item[i]);

}

//釋放數組

PyMem_FREE(item);

}

return 0;

}

我們注意到,在delete_garbage中,有一些unreachable鏈表中的對象會被重新送回到reachable鏈表(即delete_garbage的old參數)中,這是由于進行clear動作時,如果成功進行,則通常一個對象會把自己從垃圾回收機制維護的鏈表中摘除(也就是這里的collectable鏈表)。由于某些原因,對象可能在clear動作時,沒有成功完成必要的動作,從而沒有將自己從collectable鏈表摘除,這表示對象認為自己還不能被銷毀,所以python需要講這種對象放回到reachable鏈表中。

我們在上面看到了list_clear,假設是調用了list3的list_clear,那么不好意思,這個是對list4做的處理。因為list3和list4存在循環引用,如果調用了list3的list_clear會減少list4的引用計數,由于這兩位老鐵都被刪除了,還惺惺相惜賴在內存里面不走,所以將list4的引用計數減少1之后,只能歸于湮滅了,然后會調用其list_dealloc,注意:這時候調用的是list4的list_dealloc。

//listobjct.c

static void

list_dealloc(PyListObject *op)

{

Py_ssize_t i;

//從可收集鏈表中移除

PyObject_GC_UnTrack(op);

Py_TRASHCAN_SAFE_BEGIN(op)

if (op->ob_item != NULL) {

//依次遍歷,減少內部元素的引用計數

i = Py_SIZE(op);

while (--i >= 0) {

Py_XDECREF(op->ob_item[i]);

}

//釋放內存

PyMem_FREE(op->ob_item);

}

//緩沖池機制

if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op))

free_list[numfree++] = op;

else

Py_TYPE(op)->tp_free((PyObject *)op);

Py_TRASHCAN_SAFE_END(op)

}

我們知道調用list3的list_clear,減少內部元素引用計數的時候,導致list4引用計數為0。而一旦list4的引用計數為0,那么是不是也要執行和list3一樣的list_clear動作呢?然后會發現list3的引用計數也為0了,因此list3也會被銷毀。循環引用,彼此共生,銷毀之路,怎能獨自前行?最終list3和list4都會執行內部的list_dealloc,釋放內部元素,調整參數,當然還有所謂的緩沖池機制等等。總之如此一來,list3和list4就都被安全地回收了。

17.4.4.5 總結

雖然有很多對象掛在垃圾收集機制監控的鏈表上,但是很多時候是引用計數機制在維護這些對象,只有引用計數無能為力的循環引用,垃圾收集機制才會起到作用(這里沒有把引用計數機制看成垃圾回收,當然如果別人問你python的垃圾回收機制的時候,你也可以把引用計數機制加上)。事實上,如果不是循環引用的話,那么垃圾回收是無能為力的,因為掛在垃圾回收機制上的對象都是引用計數不為0的,如果為0早被引用計數機制干掉了。而引用計數不為0的情況只有兩種:一種是被程序使用的對象,二是循環引用中的對象。被程序使用的對象是不能被回收的,所以垃圾回收只能處理那些循環引用的對象。

所以python的垃圾回收就是:引用計數為主,分代回收為輔,兩者結合使用,后者主要是為了彌補前者的缺點而存在的。

17.5 python中的gc模塊

這個gc模塊,底層就是gcmodule,我們說這些模塊底層是用c寫的,當python編譯好時,就內嵌在解釋器里面了。我們可以導入它,但是在python安裝目錄上看不到。

gc.enable():開啟垃圾回收

這個函數表示開啟垃圾回收機制,默認是自動開啟的。

gc.disable():關閉垃圾回收

import gc

class A:

pass

# 關掉gc

gc.disable()

while True:

a1 = A()

a2 = A()

# 此時內部出現了循環引用

a1.__dict__["attr"] = a2

a2.__dict__["attr"] = a1

# 由于循環引用,此時是del a1, a2,光靠引用計數是刪不掉的

# 需要垃圾回收,但是我們給關閉了

del a1, a2

無限循環,并且每次循環都會創建新的對象,最終導致內存無限增大。

import gc

class A:

pass

# 關掉gc

gc.disable()

while True:

a1 = A()

a2 = A()

這里即使我們關閉了gc,但是每一次循環都會指向一個新的對象,而之前的對象由于沒有人指向了,那么引用計數為0,直接就被引用計數機制干掉了,內存會一直穩定,不會出現增長。所以我們看到,即使關閉了gc,但是對于那些引用計數為0的,該刪除還是會刪除的。所以引用計數很簡單,就是按照對應的規則該加1加1,該減1減1,一旦為0直接銷毀。而當出現循環引用的時候,才需要gc閃亮登場。這里關閉了gc,但是沒有循環引用所以沒事,而上一個例子,關閉了gc,但是出現了循環引用,而引用計數機制只會根據引用計數來判斷,而發現引用計數不為0,所以就一直傻傻地不回收,程序又一直創建新的對象,最終導致內存越用越多。而上一個例子若是開啟了gc,那么分代回收計數,就會通過標記--清除的方式將產生循環引用的對象的引用計數減1,而引用計數機制發現引用計數為0了,那么就會將對象回收掉。所以這個引用計數機制到底算不算垃圾回收機制的一種呢?你要說算吧,我把gc關閉了,引用計數機制還可以發揮作用,你要說不算吧,它確實是負責判定對象是否應該被回收的唯一標準,所以該怎么說就具體看情況吧。

gc.isenabled():判斷gc是否開啟

import gc

print(gc.isenabled()) # True

gc.disable()

print(gc.isenabled()) # False

gc.collect():立刻觸發垃圾回收

我們說,垃圾回收觸發是需要條件的,比如0代鏈表,清理零代鏈表的時候,需要對象的個數count大于閾值threshold(默認是700),但是這個函數可以強制觸發垃圾回收。

gc.get_threshold():返回每一代的閾值

import gc

print(gc.get_threshold()) # (700, 10, 10)

# 700:零代鏈表的對象超過700個,觸發垃圾回收

# 10:零代鏈表,垃圾回收10次,會清理一代鏈表

# 10:一代鏈表,垃圾回收10次,會清理二代鏈表

gc.set_threshold():設置每一代的閾值

import gc

gc.set_threshold(1000, 100, 100)

print(gc.get_threshold()) # (1000, 100, 100)

gc.get_count():查看每一代的值達到了多少

import gc

print(gc.get_count()) # (44, 7, 5)

gc.get_stats():返回每一代的具體信息

from pprint import pprint

import gc

pprint(gc.get_stats())

"""

[{'collected': 316, 'collections': 62, 'uncollectable': 0},

{'collected': 538, 'collections': 5, 'uncollectable': 0},

{'collected': 0, 'collections': 0, 'uncollectable': 0}]

"""

gc.get_objects():返回被垃圾回收器追蹤的所有對象,一個列表

gc.is_tracked(obj):查看對象obj是否被垃圾收集器追蹤

import gc

a = 1

b = []

print(gc.is_tracked(a)) # False

print(gc.is_tracked(b)) # True

# 我們說只有那些可能會產生循環引用的對象才會被垃圾回收器跟蹤

gc.get_referrers(obj):返回所有引用了obj的對象

gc.get_referents(obj):返回所有被obj引用了的對象

gc.freeze():凍結所有被垃圾回收器跟蹤的對象并在以后的垃圾回收中不被處理

gc.unfreeze():取消所有凍結的對象,讓它們繼續參數垃圾回收

gc.get_freeze_count():獲取凍結的對象個數

import gc

# 不需要參數,會自動找到被垃圾回收器跟蹤的對象

gc.freeze()

# 說明有很多內置對象在被跟蹤,被我們凍結了

print(gc.get_freeze_count()) # 24397

b = []

gc.freeze()

# 只要這里比上面多1個就行

print(gc.get_freeze_count()) # 24398

# 取消凍結

gc.unfreeze()

print(gc.get_freeze_count()) # 0

gc.get_debug():獲取debug級別

import gc

print(gc.get_debug()) # 0

gc.set_debug():設置debug級別

import gc

"""

DEBUG_STATS - 在垃圾收集過程中打印所有統計信息

DEBUG_COLLECTABLE - 打印發現的可收集對象

DEBUG_UNCOLLECTABLE - 打印unreachable對象(除了uncollectable對象)

DEBUG_SAVEALL - 將對象保存到gc.garbage(一個列表)里面,而不是釋放它

DEBUG_LEAK - 對內存泄漏的程序進行debug (everything but STATS).

"""

class A:

pass

class B:

pass

a = A()

b = B()

gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_SAVEALL)

print(gc.garbage) # []

a.b = b

b.a = a

del a, b

gc.collect() # 強制觸發垃圾回收

# 下面都是自動打印的

"""

gc: collecting generation 2...

gc: objects in each generation: 123 3732 20563

gc: objects in permanent generation: 0

gc: done, 4 unreachable, 0 uncollectable, 0.0000s elapsed

gc: collecting generation 2...

gc: objects in each generation: 0 0 24249

gc: objects in permanent generation: 0

gc: done, 0 unreachable, 0 uncollectable, 0.0150s elapsed

gc: collecting generation 2...

gc: objects in each generation: 525 0 23752

gc: objects in permanent generation: 0

gc: done, 7062 unreachable, 0 uncollectable, 0.0000s elapsed

gc: collecting generation 2...

gc: objects in each generation: 0 0 21941

gc: objects in permanent generation: 0

gc: done, 4572 unreachable, 0 uncollectable, 0.0000s elapsed

"""

print(gc.garbage)

# [<__main__.a object at>, <__main__.b object at>, {'b': <__main__.b object at>}, {'a': <__main__.a object at>}]

17.6 總結

盡管python采用了最經典的(最土的)的引用計數來作為自動內存管理的方案,但是python采用了多種方式來彌補引用計數的不足,內存池的大量使用,標記--清除(分代技術采用的去除循環引用的引用計數的方式)垃圾收集技術都極大地完善了python的內存管理(包括申請、回收)機制。盡管引用計數機制需要花費額外的開銷來維護引用計數,但是現在這個年代,這點內存算個啥。而且引用計數也有好處,不然早就隨著時代的前進而被掃進歷史的垃圾堆里面了。首先引用計數真的很方便,很直觀,對于很多對象引用計數能夠直接解決,不需要什么復雜的操作;另外引用計數將垃圾回收的開銷分攤在了整個運行時,這對于python的響應是有好處的。

當然內存管理和垃圾回收是一門給常精細和繁瑣的技術,有興趣的話各位可以自己大刀闊斧的沖進python的源碼中自由翱翔。

總結

以上是生活随笔為你收集整理的python内存管理和释放_《python解释器源码剖析》第17章--python的内存管理与垃圾回收...的全部內容,希望文章能夠幫你解決所遇到的問題。

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

日韩欧美中文字幕公布 | 男人扒开女人内裤强吻桶进去 | 欧美日韩综合一区二区三区 | 国产手机在线αⅴ片无码观看 | 精品少妇爆乳无码av无码专区 | 欧美怡红院免费全部视频 | 九九综合va免费看 | 夜夜夜高潮夜夜爽夜夜爰爰 | 日本免费一区二区三区最新 | 熟妇人妻无乱码中文字幕 | 亚洲色大成网站www国产 | 国产成人综合色在线观看网站 | 国产莉萝无码av在线播放 | 婷婷色婷婷开心五月四房播播 | a在线亚洲男人的天堂 | 亚洲小说图区综合在线 | 人妻中文无码久热丝袜 | 日本爽爽爽爽爽爽在线观看免 | 99精品久久毛片a片 | 国产精品国产三级国产专播 | 国产午夜福利100集发布 | 免费看少妇作爱视频 | 正在播放老肥熟妇露脸 | 东京热一精品无码av | 一二三四社区在线中文视频 | 国产精品内射视频免费 | 国产欧美精品一区二区三区 | 国产成人av免费观看 | 伊人久久婷婷五月综合97色 | 国产热a欧美热a在线视频 | 日日躁夜夜躁狠狠躁 | 曰韩少妇内射免费播放 | 国产成人精品优优av | av无码电影一区二区三区 | 色综合视频一区二区三区 | 超碰97人人做人人爱少妇 | 日韩精品久久久肉伦网站 | 亚洲欧美综合区丁香五月小说 | 无码国产激情在线观看 | 精品国产一区av天美传媒 | 欧美野外疯狂做受xxxx高潮 | 国产精品久久久久久亚洲影视内衣 | аⅴ资源天堂资源库在线 | 精品人妻人人做人人爽 | 无码国内精品人妻少妇 | 麻豆国产人妻欲求不满谁演的 | 亚洲精品一区三区三区在线观看 | 成人性做爰aaa片免费看不忠 | 骚片av蜜桃精品一区 | 国产成人无码专区 | 欧美性生交活xxxxxdddd | 欧美丰满老熟妇xxxxx性 | 亚洲人交乣女bbw | 超碰97人人做人人爱少妇 | 色婷婷久久一区二区三区麻豆 | 亚洲高清偷拍一区二区三区 | 免费视频欧美无人区码 | 久久久www成人免费毛片 | 蜜桃视频韩日免费播放 | 色 综合 欧美 亚洲 国产 | 日韩欧美群交p片內射中文 | 久在线观看福利视频 | 亚洲人成网站免费播放 | 奇米影视7777久久精品 | 妺妺窝人体色www在线小说 | 丰满少妇高潮惨叫视频 | 久久国产劲爆∧v内射 | 欧美变态另类xxxx | 欧美一区二区三区视频在线观看 | 一本久久伊人热热精品中文字幕 | 99久久99久久免费精品蜜桃 | 中文字幕乱码亚洲无线三区 | 装睡被陌生人摸出水好爽 | 丰满岳乱妇在线观看中字无码 | aⅴ在线视频男人的天堂 | 97精品国产97久久久久久免费 | 亚洲国产精品无码一区二区三区 | 免费无码午夜福利片69 | 四虎永久在线精品免费网址 | 在线观看欧美一区二区三区 | 真人与拘做受免费视频一 | 欧美人与禽猛交狂配 | 国产亚洲精品久久久久久久 | 女人被男人躁得好爽免费视频 | 一二三四社区在线中文视频 | 在线亚洲高清揄拍自拍一品区 | 亚洲春色在线视频 | 日本www一道久久久免费榴莲 | 国产精品a成v人在线播放 | 国产精品高潮呻吟av久久4虎 | а√天堂www在线天堂小说 | 51国偷自产一区二区三区 | 黑人巨大精品欧美一区二区 | 人人妻人人澡人人爽人人精品浪潮 | 一区二区三区高清视频一 | 麻豆国产丝袜白领秘书在线观看 | 呦交小u女精品视频 | 国产成人无码专区 | 免费观看又污又黄的网站 | 中文字幕乱码中文乱码51精品 | 久久精品国产99精品亚洲 | 大地资源网第二页免费观看 | 日日躁夜夜躁狠狠躁 | 老太婆性杂交欧美肥老太 | 麻豆国产人妻欲求不满谁演的 | 精品人妻中文字幕有码在线 | 亚洲啪av永久无码精品放毛片 | 成熟女人特级毛片www免费 | 国产内射爽爽大片视频社区在线 | 亚洲aⅴ无码成人网站国产app | 久久久久久亚洲精品a片成人 | 色婷婷久久一区二区三区麻豆 | 老太婆性杂交欧美肥老太 | 东京无码熟妇人妻av在线网址 | 午夜精品久久久久久久久 | 国产乱子伦视频在线播放 | 亚洲国产欧美国产综合一区 | 午夜嘿嘿嘿影院 | 亚洲欧美精品伊人久久 | 国产综合色产在线精品 | 99精品国产综合久久久久五月天 | 小泽玛莉亚一区二区视频在线 | 日本精品高清一区二区 | 激情综合激情五月俺也去 | 日韩成人一区二区三区在线观看 | 日韩欧美成人免费观看 | 国产在线一区二区三区四区五区 | 无码av中文字幕免费放 | 高中生自慰www网站 | 日本一卡2卡3卡四卡精品网站 | 国产精品久久久久9999小说 | 精品国产av色一区二区深夜久久 | 国产午夜手机精彩视频 | 又大又黄又粗又爽的免费视频 | 天堂无码人妻精品一区二区三区 | 狠狠噜狠狠狠狠丁香五月 | 少妇性俱乐部纵欲狂欢电影 | 色婷婷综合激情综在线播放 | 亚洲午夜无码久久 | 国产高清不卡无码视频 | 狂野欧美激情性xxxx | 男人扒开女人内裤强吻桶进去 | 婷婷综合久久中文字幕蜜桃三电影 | 天堂亚洲免费视频 | 熟女体下毛毛黑森林 | 在线亚洲高清揄拍自拍一品区 | 国产香蕉尹人视频在线 | 亚洲国产高清在线观看视频 | 精品国产成人一区二区三区 | 国产乱人伦偷精品视频 | 精品国产一区二区三区四区 | 高潮毛片无遮挡高清免费视频 | 人妻少妇精品无码专区二区 | 久久精品无码一区二区三区 | 国色天香社区在线视频 | 国产免费无码一区二区视频 | 大乳丰满人妻中文字幕日本 | 九九久久精品国产免费看小说 | 少妇人妻大乳在线视频 | 免费无码肉片在线观看 | 人人妻人人澡人人爽欧美一区 | 国产精品99爱免费视频 | 一本久久伊人热热精品中文字幕 | 精品人妻中文字幕有码在线 | 色综合久久中文娱乐网 | 日韩 欧美 动漫 国产 制服 | 天堂亚洲2017在线观看 | 免费无码av一区二区 | 国产精品人妻一区二区三区四 | 亚洲热妇无码av在线播放 | 四虎国产精品免费久久 | 久久久中文久久久无码 | 1000部啪啪未满十八勿入下载 | 国产黄在线观看免费观看不卡 | 国产人妻人伦精品1国产丝袜 | 国产三级精品三级男人的天堂 | 亚洲а∨天堂久久精品2021 | 88国产精品欧美一区二区三区 | 欧美zoozzooz性欧美 | 欧美兽交xxxx×视频 | 领导边摸边吃奶边做爽在线观看 | 欧美人与动性行为视频 | 欧美熟妇另类久久久久久多毛 | av人摸人人人澡人人超碰下载 | 97久久国产亚洲精品超碰热 | 性色欲网站人妻丰满中文久久不卡 | 兔费看少妇性l交大片免费 | 久久精品99久久香蕉国产色戒 | 久久国产精品萌白酱免费 | 窝窝午夜理论片影院 | 高清不卡一区二区三区 | 四虎国产精品免费久久 | 精品 日韩 国产 欧美 视频 | 欧美一区二区三区视频在线观看 | 欧美精品国产综合久久 | 精品 日韩 国产 欧美 视频 | 国模大胆一区二区三区 | 久9re热视频这里只有精品 | 少妇无套内谢久久久久 | 内射老妇bbwx0c0ck | 日本一区二区三区免费高清 | 国产深夜福利视频在线 | 熟妇人妻激情偷爽文 | 国语精品一区二区三区 | 扒开双腿吃奶呻吟做受视频 | 欧美一区二区三区视频在线观看 | 波多野结衣高清一区二区三区 | 国产av久久久久精东av | 国产人妻精品一区二区三区不卡 | 色婷婷香蕉在线一区二区 | 中国女人内谢69xxxxxa片 | 久久久久久av无码免费看大片 | 婷婷丁香六月激情综合啪 | 国产亲子乱弄免费视频 | 大肉大捧一进一出好爽视频 | 国产人妻精品一区二区三区不卡 | 美女扒开屁股让男人桶 | 55夜色66夜色国产精品视频 | 国产精品视频免费播放 | 一本久久a久久精品亚洲 | 超碰97人人做人人爱少妇 | 窝窝午夜理论片影院 | 中文字幕人妻无码一夲道 | 四虎影视成人永久免费观看视频 | 色婷婷综合激情综在线播放 | 亚洲另类伦春色综合小说 | 日本大香伊一区二区三区 | 色综合久久88色综合天天 | 精品一区二区三区无码免费视频 | 国产精品亚洲一区二区三区喷水 | 啦啦啦www在线观看免费视频 | 国产亲子乱弄免费视频 | 精品欧洲av无码一区二区三区 | 国产偷自视频区视频 | 一二三四社区在线中文视频 | 人妻天天爽夜夜爽一区二区 | 亚洲色大成网站www国产 | 亚洲 a v无 码免 费 成 人 a v | 天堂无码人妻精品一区二区三区 | 亚洲精品国产精品乱码不卡 | 国产内射老熟女aaaa | 国产精品99爱免费视频 | a片免费视频在线观看 | 亚洲 激情 小说 另类 欧美 | 亚洲中文字幕在线观看 | 国产乱子伦视频在线播放 | 无码人中文字幕 | 熟女少妇在线视频播放 | 在线a亚洲视频播放在线观看 | 欧美人与物videos另类 | 国产精品无码一区二区桃花视频 | 成人aaa片一区国产精品 | 亚洲成av人影院在线观看 | 国产真人无遮挡作爱免费视频 | 色婷婷久久一区二区三区麻豆 | 奇米影视888欧美在线观看 | 一区二区三区高清视频一 | 国产精品无码一区二区三区不卡 | 奇米影视888欧美在线观看 | 久久久久免费看成人影片 | 国产精品无码永久免费888 | 国产又爽又黄又刺激的视频 | 一二三四在线观看免费视频 | 日本爽爽爽爽爽爽在线观看免 | 久久99精品久久久久婷婷 | 亚洲综合无码久久精品综合 | 国产成人无码a区在线观看视频app | 免费无码的av片在线观看 | 国产深夜福利视频在线 | 日本精品人妻无码免费大全 | 国产精品福利视频导航 | 日本肉体xxxx裸交 | 嫩b人妻精品一区二区三区 | 乌克兰少妇性做爰 | 亚洲天堂2017无码 | 久久99精品久久久久久动态图 | 四虎4hu永久免费 | 丰满少妇熟乱xxxxx视频 | 鲁鲁鲁爽爽爽在线视频观看 | 麻豆精品国产精华精华液好用吗 | 精品国产乱码久久久久乱码 | 免费看少妇作爱视频 | 国产精品无套呻吟在线 | 国产乱人伦av在线无码 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 国产成人久久精品流白浆 | 国产两女互慰高潮视频在线观看 | 在教室伦流澡到高潮hnp视频 | 少妇邻居内射在线 | 久久亚洲精品中文字幕无男同 | 精品欧美一区二区三区久久久 | 国产精品久久久久久亚洲影视内衣 | 无码人妻少妇伦在线电影 | 亚洲另类伦春色综合小说 | 亚洲一区二区三区四区 | 老太婆性杂交欧美肥老太 | 一本色道久久综合亚洲精品不卡 | 亚洲欧美日韩国产精品一区二区 | 国産精品久久久久久久 | 欧美日本精品一区二区三区 | 噜噜噜亚洲色成人网站 | 国产亚洲视频中文字幕97精品 | 免费国产黄网站在线观看 | 一本久道久久综合婷婷五月 | 国产精品亚洲а∨无码播放麻豆 | 在线播放无码字幕亚洲 | 国产香蕉97碰碰久久人人 | 99久久精品午夜一区二区 | 久久国产36精品色熟妇 | 狠狠色噜噜狠狠狠7777奇米 | 午夜精品久久久久久久 | 亚洲国产一区二区三区在线观看 | 中文久久乱码一区二区 | 国产两女互慰高潮视频在线观看 | 亚洲啪av永久无码精品放毛片 | 国产办公室秘书无码精品99 | 福利一区二区三区视频在线观看 | 黑人巨大精品欧美一区二区 | 日日摸日日碰夜夜爽av | 国内丰满熟女出轨videos | 夜先锋av资源网站 | 精品久久久无码人妻字幂 | 亚洲国产精华液网站w | 欧美一区二区三区 | 狂野欧美性猛交免费视频 | 精品熟女少妇av免费观看 | 色 综合 欧美 亚洲 国产 | 77777熟女视频在线观看 а天堂中文在线官网 | 少妇性l交大片 | 久久亚洲国产成人精品性色 | 日本精品少妇一区二区三区 | 亚洲中文字幕无码一久久区 | 久久久久se色偷偷亚洲精品av | 国产在线一区二区三区四区五区 | 久久久中文字幕日本无吗 | 亚洲国产综合无码一区 | 国产真人无遮挡作爱免费视频 | 国产办公室秘书无码精品99 | 亚洲爆乳精品无码一区二区三区 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 俺去俺来也在线www色官网 | 一区二区三区乱码在线 | 欧洲 | 一本加勒比波多野结衣 | 亚洲熟熟妇xxxx | 国产免费无码一区二区视频 | 日韩视频 中文字幕 视频一区 | 日日摸日日碰夜夜爽av | 成人影院yy111111在线观看 | 欧美freesex黑人又粗又大 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 久久久久久a亚洲欧洲av冫 | 在线欧美精品一区二区三区 | 高清无码午夜福利视频 | 无码精品国产va在线观看dvd | 亚洲人成影院在线无码按摩店 | 大屁股大乳丰满人妻 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 人人妻人人澡人人爽欧美一区 | 婷婷五月综合缴情在线视频 | 国产精品a成v人在线播放 | 人妻中文无码久热丝袜 | 欧美性猛交内射兽交老熟妇 | a国产一区二区免费入口 | 国产免费久久精品国产传媒 | 精品国产av色一区二区深夜久久 | 亚洲国产欧美在线成人 | 乱码午夜-极国产极内射 | 久久国内精品自在自线 | 无码成人精品区在线观看 | 国产成人精品三级麻豆 | 丝袜人妻一区二区三区 | 国产精品亚洲综合色区韩国 | 老熟女重囗味hdxx69 | 精品无码一区二区三区爱欲 | 性做久久久久久久久 | 国产激情无码一区二区app | 麻花豆传媒剧国产免费mv在线 | 日日噜噜噜噜夜夜爽亚洲精品 | 欧美35页视频在线观看 | 亚洲成av人综合在线观看 | 久久久久久久人妻无码中文字幕爆 | 亚洲欧美国产精品久久 | 俺去俺来也在线www色官网 | 又粗又大又硬毛片免费看 | 性生交大片免费看女人按摩摩 | 亚洲爆乳精品无码一区二区三区 | 亚洲精品久久久久久久久久久 | 欧洲精品码一区二区三区免费看 | 夜夜夜高潮夜夜爽夜夜爰爰 | 成人动漫在线观看 | 无码人妻丰满熟妇区五十路百度 | 一本久久a久久精品vr综合 | 欧美日本免费一区二区三区 | 欧美阿v高清资源不卡在线播放 | 免费无码av一区二区 | 色偷偷av老熟女 久久精品人妻少妇一区二区三区 | 国产黄在线观看免费观看不卡 | 色妞www精品免费视频 | 亚洲一区二区三区四区 | 无码人妻久久一区二区三区不卡 | 久久国语露脸国产精品电影 | а天堂中文在线官网 | 国产乱人偷精品人妻a片 | 亚洲国产精品久久久久久 | 天干天干啦夜天干天2017 | 我要看www免费看插插视频 | 精品国产乱码久久久久乱码 | 欧美精品免费观看二区 | 无码人妻久久一区二区三区不卡 | 日韩欧美中文字幕在线三区 | 爱做久久久久久 | 人人妻人人澡人人爽欧美精品 | 日本www一道久久久免费榴莲 | 欧美性黑人极品hd | 女人被男人爽到呻吟的视频 | 亚洲 欧美 激情 小说 另类 | 国产麻豆精品精东影业av网站 | 欧美 日韩 人妻 高清 中文 | 国产无遮挡吃胸膜奶免费看 | 亚洲成av人片天堂网无码】 | 男人和女人高潮免费网站 | 成 人影片 免费观看 | 国产精品18久久久久久麻辣 | 午夜福利试看120秒体验区 | 精品国产成人一区二区三区 | 成在人线av无码免观看麻豆 | 麻豆国产97在线 | 欧洲 | 久久无码人妻影院 | 国产亚洲欧美日韩亚洲中文色 | 人妻体内射精一区二区三四 | 一本大道伊人av久久综合 | 亚洲s色大片在线观看 | 国产绳艺sm调教室论坛 | 中文无码精品a∨在线观看不卡 | aⅴ在线视频男人的天堂 | 性欧美牲交xxxxx视频 | 天天爽夜夜爽夜夜爽 | 亚洲一区av无码专区在线观看 | 蜜臀av无码人妻精品 | 国产超碰人人爽人人做人人添 | 国产做国产爱免费视频 | 久久久久久久人妻无码中文字幕爆 | 久久精品国产一区二区三区 | 人妻插b视频一区二区三区 | 亚洲高清偷拍一区二区三区 | 天下第一社区视频www日本 | 久久99精品国产麻豆蜜芽 | 麻豆国产97在线 | 欧洲 | 高清无码午夜福利视频 | 又大又黄又粗又爽的免费视频 | 欧美国产日韩亚洲中文 | 国产明星裸体无码xxxx视频 | 国产成人综合色在线观看网站 | 日韩人妻系列无码专区 | 天天躁夜夜躁狠狠是什么心态 | 天天av天天av天天透 | 亚洲人成无码网www | 鲁大师影院在线观看 | 性生交大片免费看女人按摩摩 | 国产免费久久精品国产传媒 | 无码国模国产在线观看 | ass日本丰满熟妇pics | 亚洲色成人中文字幕网站 | 亚洲爆乳精品无码一区二区三区 | 人妻aⅴ无码一区二区三区 | 久久无码人妻影院 | 精品偷自拍另类在线观看 | 国产精品99久久精品爆乳 | 国产成人一区二区三区在线观看 | 日本成熟视频免费视频 | 丰满人妻一区二区三区免费视频 | 国产精品久久久久无码av色戒 | 中文字幕av无码一区二区三区电影 | 天天躁夜夜躁狠狠是什么心态 | 波多野结衣av一区二区全免费观看 | 久久久亚洲欧洲日产国码αv | 香港三级日本三级妇三级 | 性色av无码免费一区二区三区 | 老司机亚洲精品影院无码 | 久久人人爽人人人人片 | 中文字幕人妻丝袜二区 | 亚洲国产精品久久久天堂 | 黑森林福利视频导航 | 熟妇激情内射com | 在线精品国产一区二区三区 | 欧美放荡的少妇 | 撕开奶罩揉吮奶头视频 | 久热国产vs视频在线观看 | 精品国精品国产自在久国产87 | 久久精品国产大片免费观看 | 久久久亚洲欧洲日产国码αv | 任你躁国产自任一区二区三区 | 国产做国产爱免费视频 | 亚洲精品综合一区二区三区在线 | 久久天天躁狠狠躁夜夜免费观看 | 97久久精品无码一区二区 | 欧美日本精品一区二区三区 | 熟女少妇在线视频播放 | 波多野结衣av在线观看 | 国产亚洲日韩欧美另类第八页 | 日韩av激情在线观看 | 亚洲中文字幕无码一久久区 | 国产精品成人av在线观看 | 丰满妇女强制高潮18xxxx | av无码电影一区二区三区 | 国产精品国产三级国产专播 | 日韩av无码一区二区三区 | 青草青草久热国产精品 | 丰满少妇高潮惨叫视频 | 久久精品女人的天堂av | 国产精品亚洲综合色区韩国 | 丁香啪啪综合成人亚洲 | 网友自拍区视频精品 | 色欲av亚洲一区无码少妇 | 亚洲精品综合一区二区三区在线 | 亚洲午夜福利在线观看 | 亚洲精品一区二区三区大桥未久 | 东京无码熟妇人妻av在线网址 | 日韩欧美成人免费观看 | 俄罗斯老熟妇色xxxx | 亚洲男人av香蕉爽爽爽爽 | 国产精品久久久av久久久 | 亚洲精品综合一区二区三区在线 | 精品无人区无码乱码毛片国产 | 亚洲大尺度无码无码专区 | 亚洲色欲色欲欲www在线 | 人妻体内射精一区二区三四 | 18禁黄网站男男禁片免费观看 | 亚洲国产午夜精品理论片 | 在线观看免费人成视频 | 国产sm调教视频在线观看 | 国产欧美亚洲精品a | 青草青草久热国产精品 | 伊人久久大香线蕉亚洲 | 国产亚洲tv在线观看 | 天干天干啦夜天干天2017 | 精品国产一区av天美传媒 | 中文字幕乱妇无码av在线 | 日日摸夜夜摸狠狠摸婷婷 | 日韩精品久久久肉伦网站 | 九月婷婷人人澡人人添人人爽 | 动漫av一区二区在线观看 | 国产热a欧美热a在线视频 | 精品国产成人一区二区三区 | 又紧又大又爽精品一区二区 | 日韩精品一区二区av在线 | 四十如虎的丰满熟妇啪啪 | 97se亚洲精品一区 | 久久婷婷五月综合色国产香蕉 | 波多野结衣 黑人 | 亚洲综合另类小说色区 | 中文字幕乱码中文乱码51精品 | 一本久道高清无码视频 | 色一情一乱一伦一视频免费看 | 一个人看的www免费视频在线观看 | 在教室伦流澡到高潮hnp视频 | 国产精品多人p群无码 | 亚洲の无码国产の无码步美 | 曰本女人与公拘交酡免费视频 | 国产人妻精品一区二区三区不卡 | 在教室伦流澡到高潮hnp视频 | 色老头在线一区二区三区 | 狠狠cao日日穞夜夜穞av | 久在线观看福利视频 | 日本大乳高潮视频在线观看 | 欧美黑人乱大交 | 欧美一区二区三区视频在线观看 | 国产色xx群视频射精 | 国模大胆一区二区三区 | 搡女人真爽免费视频大全 | 丰满岳乱妇在线观看中字无码 | 欧美精品在线观看 | 午夜男女很黄的视频 | 免费观看的无遮挡av | 亚洲一区二区三区国产精华液 | 乱人伦人妻中文字幕无码 | 秋霞成人午夜鲁丝一区二区三区 | 国产在线精品一区二区三区直播 | 久久久中文久久久无码 | 国产精品视频免费播放 | 伦伦影院午夜理论片 | 真人与拘做受免费视频 | 天天av天天av天天透 | 久久久精品人妻久久影视 | 天天摸天天透天天添 | 在线看片无码永久免费视频 | 国产av无码专区亚洲a∨毛片 | 99精品无人区乱码1区2区3区 | 永久免费观看国产裸体美女 | 亚洲a无码综合a国产av中文 | 久久久久亚洲精品中文字幕 | 少妇无套内谢久久久久 | 亚洲 a v无 码免 费 成 人 a v | 久久这里只有精品视频9 | 水蜜桃亚洲一二三四在线 | 影音先锋中文字幕无码 | 一本大道伊人av久久综合 | 国产av无码专区亚洲awww | 丰满少妇弄高潮了www | 澳门永久av免费网站 | 人妻尝试又大又粗久久 | 蜜桃av抽搐高潮一区二区 | 老熟妇仑乱视频一区二区 | 日本www一道久久久免费榴莲 | 国产猛烈高潮尖叫视频免费 | 人人超人人超碰超国产 | 久久亚洲精品中文字幕无男同 | 色偷偷人人澡人人爽人人模 | 亚洲啪av永久无码精品放毛片 | 久久精品国产一区二区三区 | 国产在线无码精品电影网 | 亚洲国产成人a精品不卡在线 | 久久久久亚洲精品中文字幕 | 久久99久久99精品中文字幕 | 国产成人无码专区 | 国产高清av在线播放 | 国产乡下妇女做爰 | 国产精品高潮呻吟av久久4虎 | 中文字幕精品av一区二区五区 | 日本一区二区更新不卡 | 伊人久久大香线焦av综合影院 | 成 人影片 免费观看 | 亚洲欧洲无卡二区视頻 | 久久99精品国产.久久久久 | 国产电影无码午夜在线播放 | 一本久久a久久精品亚洲 | 熟妇激情内射com | 少妇久久久久久人妻无码 | 3d动漫精品啪啪一区二区中 | 少妇人妻av毛片在线看 | 免费乱码人妻系列无码专区 | 无码中文字幕色专区 | 欧美日韩色另类综合 | 51国偷自产一区二区三区 | 成在人线av无码免观看麻豆 | 久久久久亚洲精品男人的天堂 | 色妞www精品免费视频 | 国产97人人超碰caoprom | 日本大乳高潮视频在线观看 | 亚洲男人av香蕉爽爽爽爽 | 无码国产色欲xxxxx视频 | 色婷婷香蕉在线一区二区 | 国产精品人妻一区二区三区四 | 在线播放免费人成毛片乱码 | 精品无人区无码乱码毛片国产 | 国产人妻精品午夜福利免费 | 国产sm调教视频在线观看 | 欧美zoozzooz性欧美 | 国产麻豆精品一区二区三区v视界 | 日韩人妻无码中文字幕视频 | 影音先锋中文字幕无码 | 亚洲の无码国产の无码步美 | 成熟妇人a片免费看网站 | 精品成人av一区二区三区 | 国产精品亚洲五月天高清 | 国产特级毛片aaaaaa高潮流水 | 天干天干啦夜天干天2017 | 精品国产一区二区三区四区 | 久久天天躁夜夜躁狠狠 | 欧美兽交xxxx×视频 | 国产熟妇高潮叫床视频播放 | 亚洲成色www久久网站 | 无遮无挡爽爽免费视频 | 国产精品视频免费播放 | 任你躁国产自任一区二区三区 | 国产精品久久久一区二区三区 | 亚洲精品午夜国产va久久成人 | 无码精品国产va在线观看dvd | 少妇无码吹潮 | 国产av一区二区精品久久凹凸 | 日本免费一区二区三区最新 | 亚洲欧洲中文日韩av乱码 | 国产成人精品视频ⅴa片软件竹菊 | 久久精品中文字幕大胸 | 乱人伦人妻中文字幕无码 | a在线观看免费网站大全 | 国产一区二区三区精品视频 | 中文字幕人成乱码熟女app | 免费看男女做好爽好硬视频 | 日韩精品a片一区二区三区妖精 | a在线观看免费网站大全 | www国产亚洲精品久久网站 | 欧美亚洲日韩国产人成在线播放 | 欧美人与动性行为视频 | 午夜丰满少妇性开放视频 | 日本精品少妇一区二区三区 | 精品水蜜桃久久久久久久 | 国产午夜亚洲精品不卡下载 | 在线播放亚洲第一字幕 | 无码精品人妻一区二区三区av | 亚洲爆乳大丰满无码专区 | 中文字幕av日韩精品一区二区 | 成人免费无码大片a毛片 | 成人av无码一区二区三区 | 真人与拘做受免费视频一 | 亚洲无人区午夜福利码高清完整版 | 超碰97人人射妻 | 日韩无套无码精品 | 久久精品国产99精品亚洲 | 国产在线精品一区二区三区直播 | 纯爱无遮挡h肉动漫在线播放 | 亚洲性无码av中文字幕 | 国产精品第一区揄拍无码 | 无套内射视频囯产 | 最新国产乱人伦偷精品免费网站 | 久久这里只有精品视频9 | 亚洲狠狠色丁香婷婷综合 | 国产特级毛片aaaaaaa高清 | 性色av无码免费一区二区三区 | 欧美三级a做爰在线观看 | 一本色道久久综合亚洲精品不卡 | 色诱久久久久综合网ywww | 色欲久久久天天天综合网精品 | 性色av无码免费一区二区三区 | 亚洲小说图区综合在线 | 一个人看的www免费视频在线观看 | 日韩人妻少妇一区二区三区 | 国产亚洲欧美日韩亚洲中文色 | 中文字幕无线码 | 老头边吃奶边弄进去呻吟 | 一个人看的www免费视频在线观看 | 中文字幕无码免费久久9一区9 | 亚洲欧美中文字幕5发布 | 久久久久久久人妻无码中文字幕爆 | 精品人妻av区 | 亚洲欧美国产精品专区久久 | 精品乱子伦一区二区三区 | 亚洲中文字幕无码中文字在线 | 真人与拘做受免费视频一 | 大乳丰满人妻中文字幕日本 | 国产成人无码区免费内射一片色欲 | 日韩av无码中文无码电影 | 7777奇米四色成人眼影 | 成人免费视频一区二区 | 久久久久人妻一区精品色欧美 | 欧美熟妇另类久久久久久不卡 | 国产香蕉尹人综合在线观看 | 天堂а√在线地址中文在线 | 欧美freesex黑人又粗又大 | 亚洲日本在线电影 | 日本一卡2卡3卡四卡精品网站 | 久久www免费人成人片 | 国产成人综合色在线观看网站 | 久久亚洲中文字幕无码 | 亚洲欧美日韩综合久久久 | 国产99久久精品一区二区 | 国产欧美亚洲精品a | 国产精华av午夜在线观看 | 狠狠色噜噜狠狠狠狠7777米奇 | 日本护士毛茸茸高潮 | 狠狠cao日日穞夜夜穞av | 亚洲成av人片天堂网无码】 | 人人澡人人妻人人爽人人蜜桃 | 久久久久久亚洲精品a片成人 | 亚欧洲精品在线视频免费观看 | 欧美日韩人成综合在线播放 | 国产在线无码精品电影网 | 97资源共享在线视频 | 欧美三级不卡在线观看 | 国产成人无码午夜视频在线观看 | 中国女人内谢69xxxxxa片 | 国产成人人人97超碰超爽8 | 熟妇人妻中文av无码 | 久久综合狠狠综合久久综合88 | 少妇性荡欲午夜性开放视频剧场 | 天天拍夜夜添久久精品大 | 无码人妻少妇伦在线电影 | 久久99精品国产麻豆 | 久久人人爽人人人人片 | 久久人人爽人人爽人人片av高清 | 蜜桃无码一区二区三区 | 人人妻人人澡人人爽精品欧美 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 亚洲精品一区二区三区四区五区 | 国产成人精品优优av | 人人澡人人妻人人爽人人蜜桃 | 久久国语露脸国产精品电影 | 熟妇女人妻丰满少妇中文字幕 | 正在播放老肥熟妇露脸 | 国内老熟妇对白xxxxhd | 黑人巨大精品欧美一区二区 | 无码中文字幕色专区 | 在线看片无码永久免费视频 | 亚洲成色www久久网站 | 高潮毛片无遮挡高清免费 | 少妇太爽了在线观看 | 亚洲一区二区三区国产精华液 | 午夜福利不卡在线视频 | 国色天香社区在线视频 | 九九久久精品国产免费看小说 | 色一情一乱一伦一区二区三欧美 | 国产成人久久精品流白浆 | 亚洲国产高清在线观看视频 | 中文精品无码中文字幕无码专区 | 欧美人妻一区二区三区 | 久久亚洲精品成人无码 | 国内精品人妻无码久久久影院 | 粗大的内捧猛烈进出视频 | 网友自拍区视频精品 | 国产在线精品一区二区高清不卡 | 四虎影视成人永久免费观看视频 | 99久久久无码国产精品免费 | 无码精品国产va在线观看dvd | 伊人久久大香线焦av综合影院 | 久久综合给久久狠狠97色 | 日本爽爽爽爽爽爽在线观看免 | 国内精品人妻无码久久久影院 | 麻花豆传媒剧国产免费mv在线 | 性啪啪chinese东北女人 | 国产三级精品三级男人的天堂 | 日韩亚洲欧美精品综合 | 麻豆国产丝袜白领秘书在线观看 | 无码成人精品区在线观看 | 波多野结衣一区二区三区av免费 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 性色欲情网站iwww九文堂 | 精品无码一区二区三区的天堂 | 国产乱码精品一品二品 | 久久99精品国产麻豆蜜芽 | 大地资源中文第3页 | 久久久久国色av免费观看性色 | 国产办公室秘书无码精品99 | 特黄特色大片免费播放器图片 | 乱码av麻豆丝袜熟女系列 | 日日碰狠狠丁香久燥 | 色婷婷久久一区二区三区麻豆 | 18无码粉嫩小泬无套在线观看 | 久久人人爽人人爽人人片av高清 | 中文字幕精品av一区二区五区 | 国产精品久久久午夜夜伦鲁鲁 | 最近中文2019字幕第二页 | 国产凸凹视频一区二区 | 天堂一区人妻无码 | 亚洲日韩av一区二区三区四区 | 伦伦影院午夜理论片 | 日本高清一区免费中文视频 | 久久zyz资源站无码中文动漫 | 蜜桃视频韩日免费播放 | 国产片av国语在线观看 | 免费看少妇作爱视频 | 在线成人www免费观看视频 | 久久无码中文字幕免费影院蜜桃 | 极品尤物被啪到呻吟喷水 | 精品国产福利一区二区 | 色爱情人网站 | 亚洲欧美中文字幕5发布 | 99精品久久毛片a片 | 狠狠色色综合网站 | 巨爆乳无码视频在线观看 | 亚洲欧洲日本无在线码 | 99久久精品午夜一区二区 | 成在人线av无码免费 | 亚洲欧洲中文日韩av乱码 | a片免费视频在线观看 | 久久久久国色av免费观看性色 | 欧洲精品码一区二区三区免费看 | 国精品人妻无码一区二区三区蜜柚 | 精品久久久中文字幕人妻 | 波多野结衣高清一区二区三区 | 精品人人妻人人澡人人爽人人 | av香港经典三级级 在线 | 99久久久无码国产精品免费 | 人人妻人人澡人人爽欧美一区 | 天下第一社区视频www日本 | 荫蒂添的好舒服视频囗交 | 18无码粉嫩小泬无套在线观看 | 97久久超碰中文字幕 | 曰韩无码二三区中文字幕 | 婷婷六月久久综合丁香 | 亚洲精品一区二区三区大桥未久 | 性做久久久久久久久 | 日本精品人妻无码77777 天堂一区人妻无码 | 日日干夜夜干 | 国产精品爱久久久久久久 | 久青草影院在线观看国产 | 中文字幕乱码人妻无码久久 | 国产偷自视频区视频 | 日韩人妻无码一区二区三区久久99 | 无码国产激情在线观看 | 人妻少妇精品久久 | 国产婷婷色一区二区三区在线 | 永久免费观看国产裸体美女 | 国产香蕉97碰碰久久人人 | 中文字幕乱码人妻无码久久 | 在线欧美精品一区二区三区 | 蜜桃av抽搐高潮一区二区 | 在线观看免费人成视频 | 久久视频在线观看精品 | 性欧美牲交xxxxx视频 | 欧美黑人乱大交 | 少妇被粗大的猛进出69影院 | 牲交欧美兽交欧美 | 日本www一道久久久免费榴莲 | 成人片黄网站色大片免费观看 | 日韩人妻无码一区二区三区久久99 | 西西人体www44rt大胆高清 | 日欧一片内射va在线影院 | 老司机亚洲精品影院 | 麻花豆传媒剧国产免费mv在线 | 日本护士毛茸茸高潮 | 国产色在线 | 国产 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 国产精品永久免费视频 | 娇妻被黑人粗大高潮白浆 | 国产成人综合色在线观看网站 | 久久综合狠狠综合久久综合88 | 好屌草这里只有精品 | 亚洲综合伊人久久大杳蕉 | 国产熟女一区二区三区四区五区 | 99麻豆久久久国产精品免费 | 亚洲精品鲁一鲁一区二区三区 | 人人爽人人爽人人片av亚洲 | 亚洲日本一区二区三区在线 | 亚洲国产精品久久人人爱 | 久久久久久国产精品无码下载 | 丝袜人妻一区二区三区 | 无码福利日韩神码福利片 | 国产舌乚八伦偷品w中 | 人妻人人添人妻人人爱 | 国产美女极度色诱视频www | 国产舌乚八伦偷品w中 | 男人和女人高潮免费网站 | 国产成人午夜福利在线播放 | 亚洲午夜福利在线观看 | 在线观看国产一区二区三区 | 精品国产av色一区二区深夜久久 | 亚洲性无码av中文字幕 | 巨爆乳无码视频在线观看 | 在线 国产 欧美 亚洲 天堂 | 99久久精品无码一区二区毛片 | 欧美兽交xxxx×视频 | 最新国产麻豆aⅴ精品无码 | 日日夜夜撸啊撸 | 欧美熟妇另类久久久久久不卡 | 黑森林福利视频导航 | 香蕉久久久久久av成人 | 亚洲熟妇色xxxxx亚洲 | 无码精品国产va在线观看dvd | av无码不卡在线观看免费 | 噜噜噜亚洲色成人网站 | 久久午夜夜伦鲁鲁片无码免费 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 亚洲人成影院在线观看 | 国产精品久久国产三级国 | 亚洲综合久久一区二区 | 夜夜影院未满十八勿进 | 亚洲国产精品一区二区美利坚 | 狠狠躁日日躁夜夜躁2020 | 亚洲成a人片在线观看无码 | 欧洲美熟女乱又伦 | 国产特级毛片aaaaaaa高清 | 天干天干啦夜天干天2017 | 国产福利视频一区二区 | 欧美丰满熟妇xxxx性ppx人交 | 成人欧美一区二区三区黑人 | 亚洲日韩一区二区 | 亚洲精品久久久久久久久久久 | 免费观看的无遮挡av | 亚洲国产综合无码一区 | 无码人妻精品一区二区三区不卡 | 久久久精品欧美一区二区免费 | 一本无码人妻在中文字幕免费 | 免费乱码人妻系列无码专区 | 久久精品国产大片免费观看 | 精品国产一区二区三区四区在线看 | 色综合天天综合狠狠爱 | 牲欲强的熟妇农村老妇女 | 成人片黄网站色大片免费观看 | 国产一区二区三区四区五区加勒比 | 亚洲 另类 在线 欧美 制服 | 国产情侣作爱视频免费观看 | 99久久婷婷国产综合精品青草免费 | 51国偷自产一区二区三区 | 日日天日日夜日日摸 | 大肉大捧一进一出好爽视频 | 国内精品久久毛片一区二区 | 成人无码影片精品久久久 | 成在人线av无码免观看麻豆 | 桃花色综合影院 | 精品久久久久久人妻无码中文字幕 | 久久综合九色综合欧美狠狠 | 免费网站看v片在线18禁无码 | 色 综合 欧美 亚洲 国产 | 国产黑色丝袜在线播放 | 精品亚洲成av人在线观看 | 亚洲の无码国产の无码影院 | 99国产精品白浆在线观看免费 | 精品国产av色一区二区深夜久久 | 国产无遮挡又黄又爽免费视频 | 性色欲情网站iwww九文堂 | 久久久久免费看成人影片 | 日本饥渴人妻欲求不满 | 亚洲国产精华液网站w | 精品一区二区三区波多野结衣 | 国产亚洲日韩欧美另类第八页 | 国产香蕉97碰碰久久人人 | 伊人久久大香线蕉亚洲 | 亚洲爆乳无码专区 | 日韩欧美群交p片內射中文 | 好爽又高潮了毛片免费下载 | 噜噜噜亚洲色成人网站 | 成熟妇人a片免费看网站 | 久在线观看福利视频 | 乱人伦人妻中文字幕无码 | 国语精品一区二区三区 | 精品人妻人人做人人爽 | 国产激情综合五月久久 | 波多野结衣高清一区二区三区 | 欧美三级a做爰在线观看 | 任你躁国产自任一区二区三区 | 国产绳艺sm调教室论坛 | 日日躁夜夜躁狠狠躁 | 婷婷色婷婷开心五月四房播播 | 特大黑人娇小亚洲女 | 亚洲国产精品无码一区二区三区 | 高潮毛片无遮挡高清免费视频 | 国产手机在线αⅴ片无码观看 | 中文字幕无码人妻少妇免费 | 伊人色综合久久天天小片 | 亚洲色欲色欲天天天www | 午夜精品一区二区三区的区别 | 精品欧美一区二区三区久久久 | 久久久成人毛片无码 | 内射后入在线观看一区 | 麻豆果冻传媒2021精品传媒一区下载 | 国产成人精品久久亚洲高清不卡 | 亚洲人成无码网www | 国内精品久久毛片一区二区 | 日韩av无码一区二区三区不卡 | 欧洲熟妇精品视频 | 国产午夜无码视频在线观看 | 97夜夜澡人人爽人人喊中国片 | 国产精品高潮呻吟av久久 | 久久久www成人免费毛片 | 青春草在线视频免费观看 | 午夜免费福利小电影 | 少妇高潮一区二区三区99 | 午夜不卡av免费 一本久久a久久精品vr综合 | 免费无码av一区二区 | 国产亚洲精品久久久久久国模美 | 亚洲春色在线视频 | 人人妻在人人 | 黑人玩弄人妻中文在线 | 性色av无码免费一区二区三区 | 国产精品99久久精品爆乳 | 午夜精品久久久久久久 | 国产精品成人av在线观看 | 日本在线高清不卡免费播放 | 国产亚洲欧美在线专区 | 国产午夜视频在线观看 | 亚洲a无码综合a国产av中文 | 精品偷拍一区二区三区在线看 | 无码毛片视频一区二区本码 | ass日本丰满熟妇pics | 激情内射亚州一区二区三区爱妻 | a在线观看免费网站大全 | 正在播放东北夫妻内射 | 国产在线精品一区二区高清不卡 | 十八禁真人啪啪免费网站 | 欧美一区二区三区视频在线观看 | 久久久久久av无码免费看大片 | 俄罗斯老熟妇色xxxx | 少妇性l交大片 | 成人试看120秒体验区 | 日日麻批免费40分钟无码 | 免费播放一区二区三区 | 亚洲成在人网站无码天堂 | 国产精品亚洲а∨无码播放麻豆 | 宝宝好涨水快流出来免费视频 | 中文毛片无遮挡高清免费 | 狠狠色欧美亚洲狠狠色www | 18无码粉嫩小泬无套在线观看 | 性做久久久久久久免费看 | 国产精品沙发午睡系列 | 丰满少妇熟乱xxxxx视频 | 狠狠色噜噜狠狠狠7777奇米 | 内射白嫩少妇超碰 | 最新版天堂资源中文官网 | 巨爆乳无码视频在线观看 | 久久亚洲中文字幕精品一区 | 东北女人啪啪对白 | 黑森林福利视频导航 | 无码av免费一区二区三区试看 | 香蕉久久久久久av成人 | 日韩亚洲欧美精品综合 | 日本精品高清一区二区 | 国产成人av免费观看 | 亚拍精品一区二区三区探花 | 久久亚洲精品中文字幕无男同 | 日韩人妻无码中文字幕视频 | 欧美35页视频在线观看 | 伊人久久大香线蕉av一区二区 | 亚洲国产精品无码久久久久高潮 | 久久国产精品偷任你爽任你 | 亚洲综合久久一区二区 | 亚洲另类伦春色综合小说 | 国产人妻久久精品二区三区老狼 | 国产深夜福利视频在线 | 欧美三级不卡在线观看 | 永久黄网站色视频免费直播 | 亚洲国产欧美日韩精品一区二区三区 | 亚洲gv猛男gv无码男同 | 少妇久久久久久人妻无码 | 黄网在线观看免费网站 | 51国偷自产一区二区三区 | 天天摸天天碰天天添 | 欧美熟妇另类久久久久久多毛 | 少妇性l交大片 | 国产成人午夜福利在线播放 | 国产黄在线观看免费观看不卡 | 无码人妻出轨黑人中文字幕 | 亚洲精品综合一区二区三区在线 | 国产精品久久久午夜夜伦鲁鲁 | 国产精品va在线播放 | a在线亚洲男人的天堂 | 亚洲国精产品一二二线 | 偷窥日本少妇撒尿chinese | 熟妇女人妻丰满少妇中文字幕 | 国产一区二区三区四区五区加勒比 | 日本va欧美va欧美va精品 | 性史性农村dvd毛片 | 亚洲va欧美va天堂v国产综合 | 婷婷五月综合缴情在线视频 | 四虎影视成人永久免费观看视频 | 国内少妇偷人精品视频 | 午夜精品久久久久久久久 | 天天躁日日躁狠狠躁免费麻豆 | 国产明星裸体无码xxxx视频 | 久久国产精品偷任你爽任你 | 欧美老妇交乱视频在线观看 | 激情内射日本一区二区三区 | 亚洲国产精品一区二区美利坚 | 国产精品久久久久7777 | 亚洲成熟女人毛毛耸耸多 | 日本乱偷人妻中文字幕 | 久久99久久99精品中文字幕 | 丰满少妇熟乱xxxxx视频 | 亚洲人成网站色7799 | 男人的天堂2018无码 | 一个人免费观看的www视频 | 领导边摸边吃奶边做爽在线观看 | 超碰97人人射妻 | 99久久婷婷国产综合精品青草免费 | 国语自产偷拍精品视频偷 | 欧美激情一区二区三区成人 | 在线观看免费人成视频 | 国产三级久久久精品麻豆三级 | 国内精品人妻无码久久久影院 | 中文字幕乱码人妻二区三区 | 欧美日本日韩 | 搡女人真爽免费视频大全 | 中文字幕乱妇无码av在线 | 老司机亚洲精品影院无码 | 精品午夜福利在线观看 | 夜夜影院未满十八勿进 | 日韩av无码一区二区三区不卡 | 久久久久久久久蜜桃 | 国产精品鲁鲁鲁 | 99久久99久久免费精品蜜桃 | 亚洲欧洲中文日韩av乱码 | 久久精品视频在线看15 | 日韩av无码一区二区三区 | 露脸叫床粗话东北少妇 | 高清不卡一区二区三区 | 日韩精品无码免费一区二区三区 | 亚洲中文字幕无码一久久区 | 国产色视频一区二区三区 | 99久久久无码国产精品免费 | 亚洲 另类 在线 欧美 制服 | 亚洲а∨天堂久久精品2021 | 亚洲 日韩 欧美 成人 在线观看 | 无码毛片视频一区二区本码 | 国产无av码在线观看 | 国产精品欧美成人 | 免费无码一区二区三区蜜桃大 | 亚洲欧美综合区丁香五月小说 | 国产成人综合色在线观看网站 | 国产精品无码永久免费888 | 亚洲精品一区国产 | 国产美女极度色诱视频www | 日本乱偷人妻中文字幕 | 少妇性l交大片欧洲热妇乱xxx | 最新国产乱人伦偷精品免费网站 | 国产精品无码一区二区桃花视频 | 久久精品丝袜高跟鞋 | 男人的天堂av网站 | 真人与拘做受免费视频一 | 色婷婷久久一区二区三区麻豆 | 久久亚洲中文字幕精品一区 | 亚洲精品无码国产 | 成在人线av无码免观看麻豆 | 无码人妻精品一区二区三区不卡 | 激情亚洲一区国产精品 | 免费乱码人妻系列无码专区 | 2019nv天堂香蕉在线观看 | 在线视频网站www色 | 欧美亚洲日韩国产人成在线播放 | 国精品人妻无码一区二区三区蜜柚 | 免费观看激色视频网站 | 日本丰满熟妇videos | 啦啦啦www在线观看免费视频 | 久久久久久a亚洲欧洲av冫 | 国产人妻人伦精品1国产丝袜 | 国内少妇偷人精品视频 | 国内揄拍国内精品少妇国语 | 人妻少妇被猛烈进入中文字幕 | 免费看男女做好爽好硬视频 | 国产成人无码av一区二区 | 精品少妇爆乳无码av无码专区 | 国产人成高清在线视频99最全资源 | 色五月丁香五月综合五月 | 老熟妇仑乱视频一区二区 | 欧美午夜特黄aaaaaa片 | 丝袜人妻一区二区三区 | 国产精品久久国产精品99 | 国内精品人妻无码久久久影院蜜桃 | 狂野欧美性猛交免费视频 | 亚洲精品午夜国产va久久成人 | 国内丰满熟女出轨videos | 亚洲狠狠色丁香婷婷综合 | 欧美激情内射喷水高潮 | 天天做天天爱天天爽综合网 | 在线精品国产一区二区三区 | 性欧美大战久久久久久久 | 亚洲中文字幕无码中文字在线 | 精品一区二区三区波多野结衣 | 97精品人妻一区二区三区香蕉 | 免费无码肉片在线观看 | 精品久久久久香蕉网 | 国产综合色产在线精品 | 日韩在线不卡免费视频一区 | 99国产欧美久久久精品 | 天堂а√在线地址中文在线 | 51国偷自产一区二区三区 | 久久99精品久久久久久 | 在教室伦流澡到高潮hnp视频 | 日本精品人妻无码免费大全 | 综合激情五月综合激情五月激情1 | 精品国产乱码久久久久乱码 | 亚洲の无码国产の无码影院 | 99久久人妻精品免费一区 | 麻花豆传媒剧国产免费mv在线 | 国产免费久久精品国产传媒 | 少妇性l交大片欧洲热妇乱xxx | 欧美三级a做爰在线观看 | 国产极品美女高潮无套在线观看 | 99精品国产综合久久久久五月天 | 又紧又大又爽精品一区二区 | 亚洲精品成人av在线 | 国产午夜手机精彩视频 | 久久久av男人的天堂 | 亚洲色偷偷男人的天堂 | 欧美老妇交乱视频在线观看 | 欧美午夜特黄aaaaaa片 | 人人超人人超碰超国产 | 老司机亚洲精品影院 | 婷婷综合久久中文字幕蜜桃三电影 | av无码久久久久不卡免费网站 | 少妇性荡欲午夜性开放视频剧场 | 高潮毛片无遮挡高清免费 | 精品久久综合1区2区3区激情 | 成人动漫在线观看 | 亚洲国产成人av在线观看 | 久久99精品久久久久婷婷 | 日产国产精品亚洲系列 | 久久综合香蕉国产蜜臀av | 国产口爆吞精在线视频 | av在线亚洲欧洲日产一区二区 | 对白脏话肉麻粗话av | 99久久精品午夜一区二区 | 欧美真人作爱免费视频 | 人人澡人人妻人人爽人人蜜桃 | 亚洲国产精品无码一区二区三区 | 久久久久久亚洲精品a片成人 | 无码纯肉视频在线观看 | 国产精品手机免费 | 亚洲成a人一区二区三区 | 白嫩日本少妇做爰 | 国产亚洲精品久久久久久久久动漫 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 精品成在人线av无码免费看 | 久久国产36精品色熟妇 | 九一九色国产 | 亚洲 欧美 激情 小说 另类 | 中文字幕无码热在线视频 | 国产sm调教视频在线观看 | 日日夜夜撸啊撸 | 久久久久久久人妻无码中文字幕爆 | 无码av免费一区二区三区试看 | 亚洲另类伦春色综合小说 | 人人妻人人澡人人爽人人精品 | 人妻有码中文字幕在线 | 97夜夜澡人人双人人人喊 | 成年美女黄网站色大免费全看 | 少妇人妻偷人精品无码视频 | 欧美大屁股xxxxhd黑色 | 免费网站看v片在线18禁无码 | 久久精品人妻少妇一区二区三区 | 曰韩无码二三区中文字幕 | 成熟女人特级毛片www免费 | 精品人妻中文字幕有码在线 | 人妻无码αv中文字幕久久琪琪布 | 亚洲gv猛男gv无码男同 | 青草青草久热国产精品 | 欧美国产亚洲日韩在线二区 | 55夜色66夜色国产精品视频 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 国产精品第一区揄拍无码 | 国产舌乚八伦偷品w中 | 中文字幕av伊人av无码av | 精品少妇爆乳无码av无码专区 | 亚洲а∨天堂久久精品2021 | 亚洲精品中文字幕 | 国产精品国产自线拍免费软件 | 亚洲中文字幕无码一久久区 | 国产成人无码av一区二区 | 国产成人综合色在线观看网站 | 日本高清一区免费中文视频 | av无码久久久久不卡免费网站 | 欧美人妻一区二区三区 | 欧美日韩色另类综合 | 国产精品久久久久无码av色戒 | 国产深夜福利视频在线 | 精品乱码久久久久久久 | 性欧美熟妇videofreesex | 亚洲日本一区二区三区在线 | 亚洲国产精华液网站w | 在线精品亚洲一区二区 | 精品厕所偷拍各类美女tp嘘嘘 | 亚洲综合另类小说色区 | 日本熟妇浓毛 | 国产精品内射视频免费 | 一本久久伊人热热精品中文字幕 | 久久亚洲中文字幕精品一区 | 亚洲自偷自拍另类第1页 | 久久久精品人妻久久影视 | 午夜精品一区二区三区的区别 | 欧美大屁股xxxxhd黑色 | 亚洲国产日韩a在线播放 | 4hu四虎永久在线观看 | 水蜜桃av无码 | 无套内谢的新婚少妇国语播放 | 九九久久精品国产免费看小说 | 男人扒开女人内裤强吻桶进去 | 东京热无码av男人的天堂 | 中文字幕无码视频专区 | 国产超碰人人爽人人做人人添 | 亚洲熟女一区二区三区 | 在线播放无码字幕亚洲 | 综合网日日天干夜夜久久 | 日日躁夜夜躁狠狠躁 | 国产97色在线 | 免 | 欧美性生交xxxxx久久久 | 中文字幕无线码 | 精品厕所偷拍各类美女tp嘘嘘 | 内射巨臀欧美在线视频 | 中文字幕无码av波多野吉衣 | 成人精品视频一区二区三区尤物 | 国产成人无码区免费内射一片色欲 | 六月丁香婷婷色狠狠久久 | 日日鲁鲁鲁夜夜爽爽狠狠 | 久久人人爽人人爽人人片ⅴ | 久久精品中文闷骚内射 | 丁香花在线影院观看在线播放 | 蜜桃臀无码内射一区二区三区 | 又色又爽又黄的美女裸体网站 | 少妇人妻偷人精品无码视频 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 成熟女人特级毛片www免费 | 麻豆精产国品 | 国产精品美女久久久久av爽李琼 | 亚洲国产精品一区二区美利坚 | 巨爆乳无码视频在线观看 | 亚洲日本va中文字幕 | 欧美日韩一区二区三区自拍 | 久久无码专区国产精品s | 国产亚洲精品久久久久久久久动漫 | 欧美一区二区三区 | 国产免费久久久久久无码 | 亚洲色欲色欲天天天www | 国内精品一区二区三区不卡 | 自拍偷自拍亚洲精品10p | 久久久久亚洲精品中文字幕 | 欧美日韩精品 | 蜜桃视频韩日免费播放 | 六月丁香婷婷色狠狠久久 | 国产精品va在线观看无码 | 久久久无码中文字幕久... | 纯爱无遮挡h肉动漫在线播放 | 欧美人与牲动交xxxx | 野狼第一精品社区 | 成人免费视频一区二区 | 一本无码人妻在中文字幕免费 | 国产激情综合五月久久 | 欧美丰满少妇xxxx性 | 久久精品国产99久久6动漫 | 国产成人久久精品流白浆 | 熟妇人妻无乱码中文字幕 | 亚洲最大成人网站 | 亚洲日韩精品欧美一区二区 | 超碰97人人射妻 | 狂野欧美性猛xxxx乱大交 | 国产三级精品三级男人的天堂 | 99精品国产综合久久久久五月天 | 黑森林福利视频导航 | 久久精品中文闷骚内射 | 樱花草在线社区www | www国产精品内射老师 | 久久精品女人的天堂av | 欧美35页视频在线观看 | 国产午夜无码精品免费看 | 国产色精品久久人妻 | 露脸叫床粗话东北少妇 | 免费乱码人妻系列无码专区 | 图片小说视频一区二区 | 中文字幕色婷婷在线视频 | 日本xxxx色视频在线观看免费 | 18禁黄网站男男禁片免费观看 | 四虎永久在线精品免费网址 | 美女张开腿让人桶 | 欧洲熟妇精品视频 | 人人超人人超碰超国产 | 中文字幕av无码一区二区三区电影 | 久久精品人人做人人综合试看 | 无码一区二区三区在线观看 | 国産精品久久久久久久 | 蜜臀av在线播放 久久综合激激的五月天 | 国产综合久久久久鬼色 | 欧美精品一区二区精品久久 | 国产精品国产三级国产专播 | 狠狠噜狠狠狠狠丁香五月 | 在线精品亚洲一区二区 | 在线观看国产一区二区三区 | 国产激情一区二区三区 | 国产在线精品一区二区高清不卡 | 中文字幕乱妇无码av在线 | 国产又爽又猛又粗的视频a片 | 高潮毛片无遮挡高清免费 | 亚洲欧洲日本综合aⅴ在线 | 少妇人妻大乳在线视频 | 亚洲 高清 成人 动漫 | 国产精品久久精品三级 | 娇妻被黑人粗大高潮白浆 | 日韩少妇内射免费播放 | 大色综合色综合网站 | 国产办公室秘书无码精品99 | 亚洲自偷自偷在线制服 | 国产精品无码成人午夜电影 | 国产成人无码av片在线观看不卡 | 精品aⅴ一区二区三区 | 网友自拍区视频精品 | 熟女俱乐部五十路六十路av | 97久久超碰中文字幕 | 好屌草这里只有精品 | www成人国产高清内射 | 波多野结衣av一区二区全免费观看 | v一区无码内射国产 | 国产熟妇高潮叫床视频播放 | www国产亚洲精品久久久日本 | 天天av天天av天天透 | 国产精品无码一区二区桃花视频 | 亚洲综合在线一区二区三区 | 成人三级无码视频在线观看 | 亚洲一区二区三区四区 | 国产黄在线观看免费观看不卡 | 影音先锋中文字幕无码 | 九九久久精品国产免费看小说 | 学生妹亚洲一区二区 | 骚片av蜜桃精品一区 | 中文毛片无遮挡高清免费 | 成人毛片一区二区 | 国产成人一区二区三区在线观看 | 熟女俱乐部五十路六十路av | 欧美成人免费全部网站 | 亚洲国产综合无码一区 | 久久久精品国产sm最大网站 | 国产精品香蕉在线观看 | 亚洲高清偷拍一区二区三区 | 一二三四在线观看免费视频 | 国产农村乱对白刺激视频 | 国产精品嫩草久久久久 | 欧美丰满少妇xxxx性 | 久久精品国产精品国产精品污 | 3d动漫精品啪啪一区二区中 | 日韩视频 中文字幕 视频一区 | 欧美亚洲日韩国产人成在线播放 | 亚洲色大成网站www国产 | 窝窝午夜理论片影院 | 日日鲁鲁鲁夜夜爽爽狠狠 | av在线亚洲欧洲日产一区二区 | 国模大胆一区二区三区 | 国产精品久久久久7777 | 日韩精品a片一区二区三区妖精 | 丰满少妇熟乱xxxxx视频 | 亚洲国产精品成人久久蜜臀 | 欧美三级不卡在线观看 | 中文字幕无码热在线视频 | 天堂а√在线地址中文在线 | 亚洲无人区午夜福利码高清完整版 | 97夜夜澡人人双人人人喊 | 超碰97人人做人人爱少妇 | 亚洲男人av香蕉爽爽爽爽 | 亚洲无人区午夜福利码高清完整版 | 色综合久久中文娱乐网 | 日本护士毛茸茸高潮 | 欧美日本日韩 | 国精品人妻无码一区二区三区蜜柚 | 亚洲男人av香蕉爽爽爽爽 | 国内精品人妻无码久久久影院 | 国产乱人偷精品人妻a片 | 76少妇精品导航 | 台湾无码一区二区 | 男女爱爱好爽视频免费看 | av在线亚洲欧洲日产一区二区 | 国产精品高潮呻吟av久久 | 欧美怡红院免费全部视频 | 激情五月综合色婷婷一区二区 | 99久久亚洲精品无码毛片 | 精品一区二区不卡无码av | 全黄性性激高免费视频 | 精品无码av一区二区三区 | 久久久久se色偷偷亚洲精品av | 中文精品无码中文字幕无码专区 | 久久综合色之久久综合 | 300部国产真实乱 | 亚洲日韩一区二区三区 | 国产乱人无码伦av在线a | 国产成人无码专区 | 久久久久久国产精品无码下载 | 一本大道伊人av久久综合 | 国产性生交xxxxx无码 | 欧美性生交xxxxx久久久 | 日日碰狠狠躁久久躁蜜桃 | 久久 国产 尿 小便 嘘嘘 | 国内少妇偷人精品视频免费 | 亚洲国产一区二区三区在线观看 | 日本一本二本三区免费 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 日本精品人妻无码免费大全 | 77777熟女视频在线观看 а天堂中文在线官网 | 亚洲一区二区观看播放 | 亚洲色成人中文字幕网站 | 欧美怡红院免费全部视频 | 天天爽夜夜爽夜夜爽 | 久久99热只有频精品8 | 精品欧美一区二区三区久久久 | 一本色道婷婷久久欧美 | 一本大道久久东京热无码av | 精品久久久中文字幕人妻 | 日韩亚洲欧美中文高清在线 | 国产深夜福利视频在线 | 中国女人内谢69xxxxxa片 | 特级做a爰片毛片免费69 | 国产9 9在线 | 中文 | 亚洲人成影院在线无码按摩店 | 精品国偷自产在线 | 动漫av网站免费观看 | 精品国产精品久久一区免费式 | 国精产品一品二品国精品69xx | 男女爱爱好爽视频免费看 | 中文字幕乱码人妻二区三区 | 麻豆国产丝袜白领秘书在线观看 | 欧美黑人巨大xxxxx | 国产人妻精品一区二区三区 | 67194成是人免费无码 | 国产莉萝无码av在线播放 | 大肉大捧一进一出好爽视频 | 国产av无码专区亚洲a∨毛片 | 久久综合给久久狠狠97色 | 久在线观看福利视频 | 少妇无码吹潮 | 久久国产自偷自偷免费一区调 | 九九久久精品国产免费看小说 | 夜夜夜高潮夜夜爽夜夜爰爰 | 国内少妇偷人精品视频 | 狂野欧美性猛交免费视频 | 高清国产亚洲精品自在久久 | 亚洲熟妇色xxxxx欧美老妇y | 国产在线精品一区二区三区直播 | 日韩av无码一区二区三区 | 对白脏话肉麻粗话av | 亚洲成a人片在线观看无码 | 日韩精品a片一区二区三区妖精 | 2020最新国产自产精品 | 日本在线高清不卡免费播放 | 久久久久成人片免费观看蜜芽 | 欧美性黑人极品hd | 免费视频欧美无人区码 | 装睡被陌生人摸出水好爽 | 永久免费观看国产裸体美女 | 色 综合 欧美 亚洲 国产 | 丰腴饱满的极品熟妇 | 午夜无码人妻av大片色欲 | 无码毛片视频一区二区本码 | 岛国片人妻三上悠亚 | 亚洲精品国产第一综合99久久 | 亚洲国产av精品一区二区蜜芽 | 国产亚洲欧美在线专区 | 丝袜美腿亚洲一区二区 | 中国女人内谢69xxxxxa片 | 日韩av无码中文无码电影 | 少妇人妻av毛片在线看 | 亚洲国产精品成人久久蜜臀 | 夜夜躁日日躁狠狠久久av | 日本熟妇浓毛 | 国产精品久久久午夜夜伦鲁鲁 | 日韩欧美中文字幕在线三区 | 国产精品内射视频免费 | 在线观看欧美一区二区三区 | 狠狠色噜噜狠狠狠狠7777米奇 | 人妻有码中文字幕在线 | 性生交大片免费看女人按摩摩 | 久久无码专区国产精品s | 成人无码精品一区二区三区 | 人妻与老人中文字幕 | 人妻互换免费中文字幕 | 亚洲一区二区三区国产精华液 | 亚洲日本va中文字幕 | 色欲人妻aaaaaaa无码 | 亚洲精品久久久久久一区二区 | 亚洲七七久久桃花影院 | 内射爽无广熟女亚洲 | 一本无码人妻在中文字幕免费 | 久久人人爽人人人人片 | 国产精品香蕉在线观看 | 思思久久99热只有频精品66 | 亚洲伊人久久精品影院 | 国产精品无码一区二区桃花视频 | 日本熟妇人妻xxxxx人hd | 狠狠综合久久久久综合网 | 最新国产乱人伦偷精品免费网站 | 日韩视频 中文字幕 视频一区 | 西西人体www44rt大胆高清 | 久久精品成人欧美大片 | 97夜夜澡人人爽人人喊中国片 | 奇米影视888欧美在线观看 | 国产成人精品一区二区在线小狼 | 欧美激情综合亚洲一二区 | 国产卡一卡二卡三 | 成人无码精品1区2区3区免费看 | 久久午夜无码鲁丝片 | 国产精品久久久一区二区三区 | 香港三级日本三级妇三级 | 日本va欧美va欧美va精品 | 中文字幕+乱码+中文字幕一区 | 国产成人精品久久亚洲高清不卡 | 国内丰满熟女出轨videos | 欧美怡红院免费全部视频 | 国产97在线 | 亚洲 | 亚洲男人av天堂午夜在 | 99久久99久久免费精品蜜桃 | 亚洲色大成网站www | 精品无人国产偷自产在线 | 高清无码午夜福利视频 | 未满成年国产在线观看 | 久久精品视频在线看15 | 亚洲一区二区三区含羞草 | 国产精品自产拍在线观看 | 激情五月综合色婷婷一区二区 | 99在线 | 亚洲 | 久久综合色之久久综合 | 99精品视频在线观看免费 | 无码人妻丰满熟妇区五十路百度 | 中文字幕无码热在线视频 | 久久综合久久自在自线精品自 | 小泽玛莉亚一区二区视频在线 | 久久99精品国产麻豆 | 天天拍夜夜添久久精品大 | 欧美人与动性行为视频 | 亚洲色成人中文字幕网站 | 人妻aⅴ无码一区二区三区 | 亚洲啪av永久无码精品放毛片 | 麻花豆传媒剧国产免费mv在线 | 亚洲精品国偷拍自产在线麻豆 | 国产成人一区二区三区在线观看 | 特黄特色大片免费播放器图片 |