Python 內存管理學習

      網友投稿 976 2022-05-30

      Python中的所有東西都是對象。這意味著動態內存分配是Python內存管理的基礎。當不再需要對象時,Python內存管理器將自動從它們中回收內存。

      Python是使用C編程語言實現的高級編程語言。Python內存管理器管理Python的內存分配。有一個私有heap,其中包含所有Python對象和數據結構。Python內存管理器按需管理Python堆。Python內存管理器具有特定于對象的分配器,可為int,string等特定對象分別分配內存。在此之下,原始內存分配器與操作系統的內存管理器進行交互,以確保私有堆上有空間。

      python?中的內存管理涉及包含所有?python?對象和數據結構的私有堆。?python?內存管理器在內部保證這個私有堆的管理。?python?內存管理器有不同的組件,這些組件處理各種動態存儲管理方面,比如共享,分割,預分配或者緩存。

      Python內存管理器管理稱為“塊”的內存塊。相同大小的塊的集合構成了“池”。池是在Arenas上創建的,在堆= 64池上分配了256kB的內存塊。如果對象被銷毀,則內存管理器將用相同大小的新對象填充此空間。

      方法和變量在堆棧存儲器中創建。每當創建方法和變量時,都會創建一個堆??蚣堋V灰祷胤椒ǎ@些框架就會自動銷毀。

      在堆內存中創建對象和實例變量。一旦返回變量和函數,將對垃圾對象進行垃圾回收。

      請務必注意,Python內存管理器不一定會將內存釋放回操作系統,而是將內存返回給python解釋器。Python有一個小的對象分配器,用于分配內存以供進一步使用。在長時間運行的進程中,您可能有未使用內存的增量保留。

      Python?的內存管理主要有三種機制:引用計數機制,垃圾回收機制和內存池機制。

      引用計數機制

      賦值語句是語言最常見的功能了。但即使是最簡單的賦值語句,也可以很有內涵。Python的賦值語句就很值得研究。

      a?=?1

      整數1為一個對象。而a是一個引用。利用賦值語句,引用a指向對象1。

      為了探索對象在內存的存儲,我們可以求助于Python的內置函數id()。它用于返回對象的身份(identity)。其實,這里所謂的身份,就是該對象的內存地址。

      a?=?1

      print(id(a))

      print(hex(id(a)))

      它們返回的是:

      1914562480

      0x721de7b0

      分別為內存地址的十進制和十六進制表示。

      在Python中,整數和短小的字符,Python都會緩存這些對象,以便重復使用。當我們創建多個等于1的引用時,實際上是讓所有這些引用指向同一個對象。

      a?=?1

      b?=?1

      print(id(a))

      print(id(b))

      1914562480

      1914562480

      可見a和b實際上是指向同一個對象的兩個引用。

      由于Python緩存了整數和短字符串,因此每個對象只存有一份。比如,所有整數1的引用都指向同一對象。即使使用賦值語句,也只是創造了新的引用,而不是對象本身。長的字符串和其它對象可以有多個相同的對象,可以使用賦值語句創建出新的對象。

      在Python中,每個對象都有存有指向該對象的引用總數,即引用計數(reference count)。

      我們可以使用sys包中的getrefcount(),來查看某個對象的引用計數。需要注意的是,當使用某個引用作為參數,傳遞給getrefcount()時,參數實際上創建了一個臨時的引用。因此,getrefcount()所得到的結果,會比期望的多1。

      from?sys?import?getrefcount

      a?=?[1,?2,?3]

      print(getrefcount(a))

      b?=?a

      print(getrefcount(b))

      由于上述原因,兩個getrefcount將返回2和3,而不是期望的1和2。

      Python的一個容器對象(container),比如表、詞典等,可以包含多個對象。實際上,容器對象中包含的并不是元素對象本身,是指向各個元素對象的引用。

      容器對象的引用可能構成很復雜的拓撲結構。我們可以用objgraph包來繪制其引用關系,比如:

      x?=?[1,?2,?3]

      y?=?[x,?dict(key1=x)]

      z?=?[y,?(x,?y)]

      import?objgraph

      objgraph.show_refs([z],?filename='show_refs.png')

      兩個對象可能相互引用,從而構成所謂的引用環(reference cycle)。

      a?=?[]

      b?=?[a]

      a.append(b)

      即使是一個對象,只需要自己引用自己,也能構成引用環。

      a?=?[]

      a.append(a)

      print(getrefcount(a))

      垃圾回收機制

      當Python中的對象越來越多,它們將占據越來越大的內存。不過不用太擔心Python的體形,它會乖巧的在適當的時候啟動垃圾回收(garbage collection),將沒用的對象清除。

      從基本原理上,當Python的某個對象的引用計數降為0時,說明沒有任何引用指向該對象,該對象就成為要被回收的垃圾了。比如某個新建對象,它被分配給某個引用,對象的引用計數變為1。如果引用被刪除,對象的引用計數為0,那么該對象就可以被垃圾回收。比如下面的表:

      a?=?[1,?2,?3]

      del?a

      del a后,已經沒有任何引用指向之前建立的[1, 2, 3]這個表。用戶不可能通過任何方式接觸或者動用這個對象。當垃圾回收啟動時,Python掃描到這個引用計數為0的對象,就將它所占據的內存清空。

      垃圾回收時,Python不能進行其它的任務。頻繁的垃圾回收將大大降低Python的工作效率。如果內存中的對象不多,就沒有必要總啟動垃圾回收。所以,Python只會在特定條件下,自動啟動垃圾回收。當Python運行時,會記錄其中分配對象(object allocation)和取消分配對象(object deallocation)的次數。當兩者的差值高于某個閾值時,垃圾回收才會啟動。

      我們可以通過gc模塊的get_threshold()方法,查看該閾值:

      import?gc

      print(gc.get_threshold())

      python3中返回(700, 10, 10),后面的兩個10是與分代回收相關的閾值,后面可以看到。700即是垃圾回收啟動的閾值??梢酝ㄟ^gc中的set_threshold()方法重新設置。

      上文已經舉例說明循環引用計數的問題,出現循環引用后,利用上述引用計數機制無法對循環引用中的對象進行釋放空間,這就是循環引用問題。?解決循環引用并釋放內存(標記清除):

      收集所有容器對象(循環引用只針對于容器對象,其他對象不會產生循環引用),使用雙向鏈表(可以看作一個集合)對這些對象進行引用;

      針對每一個容器對象,使用變量gc_refs來記錄當前對應的應用個數;

      對于每個容器對象,找到其正在引用的其他容器對象,并將這個被引用的容器對象引用計數減去1;

      檢查所有容器對象的引用計數,若為0,則證明該容器對象是由于循環引用存活下來的,并對其進行銷毀。

      如何提升查找循環引用過程的性能?

      由一可知,循環引用查找和銷毀過程非常繁瑣,要分別處理每一個容器對象,所以python考慮一種改善性能的做法,即分代回收。?首先是一個假設–如果一個對象被檢測了10次還沒有被銷毀,就減少對其的檢測頻率;基于這個假設,提出一套機制,即分代回收機制。?分代回收是建立在標記清除技術基礎之上的,是一種以空間換時間的操作方式。

      Python將內存根據對象的存活時間劃分為不同的集合,每個集合稱為一個代,Python將內存分為了3“代”,分別為年輕代(第0代)、中年代(第1代)、老年代(第2代),他們對應的是3個鏈表,它們的垃圾收集頻率與對象的存活時間的增大而減小。新創建的對象都會分配在年輕代,年輕代鏈表的總數達到上限時,Python垃圾收集機制就會被觸發,把那些可以被回收的對象回收掉,而那些不會回收的對象就會被移到中年代去,依此類推,老年代中的對象是存活時間最久的對象,甚至是存活于整個系統的生命周期內。

      內存分配(內存池機制)

      Python頻繁地創建和銷毀一些小的對象,為了減少小對象(小于512字節)的開銷,Python引入了內存池機制,?用于管理對小塊內存的申請和釋放,較大的對象被路由到標準C分配器。?我們可以在obmalloc.c源碼中可以看到關于小內存請求的描述

      分配的內存空間大于?SMALL_REQUEST_THRESHOLD?將直接使用layer 1的內存分配接口進行分配。?pymalloc?把“內存池”主要抽象成用3種數據結構表示:block、pool、arena

      塊指的是一個確定大小的內存塊,每個塊保留一個固定大小的Python對象,塊的大小可以從8到512字節,并且是8的倍數(即8字節對齊)。

      block由pool管理,所有512字節以下的內存申請會根據申請字節的大小分配不一樣的block,一個pool通常為4K(系統頁大小),一個pool管理著一堆固定大小的內存塊(block)?obmalloc.c源代碼注釋:

      *?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

      pool?由若干個大小相等的?block?組成,相同大小的塊集合稱為池。通常池的大小等于存儲器頁面的大小,即4Kb。?將池限制為固定大小的塊有助于碎片化管理,如果對象被破壞,則內存管理器可以使用相同大小的新對象填充此空間。

      /*?Pool?for?small?blocks.?*/

      struct?pool_header?{

      union?{?block?*_padding;

      uint?count;?}?ref;??????????/*?number?of?allocated?blocks????*/

      block?*freeblock;???????????????????/*?pool's?free?list?head?????????*/

      struct?pool_header?*nextpool;???????/*?next?pool?of?this?size?class??*/

      struct?pool_header?*prevpool;???????/*?previous?pool???????""????????*/

      uint?arenaindex;????????????????????/*?index?into?arenas?of?base?adr?*/

      uint?szidx;?????????????????????????/*?block?size?class?index????????*/

      uint?nextoffset;????????????????????/*?bytes?to?virgin?block?????????*/

      uint?maxnextoffset;?????????????????/*?largest?valid?nextoffset??????*/

      };

      每個池都使用雙向鏈表(nextpool和prevpool字段)將相同大小的塊的池鏈接在一起。通過這種方式,即使跨不同的池,算法也可以輕松找到給定塊大小的可用空間。?szidx字段保存size類索引,而ref.count保留已使用塊的數量。?arenaindex存儲創建Pool的arena的索引。

      每個Pool有三種狀態:?- used -?具有可用于存儲數據的塊,部分使用,既不空也不滿?- full -?所有池的塊目前都已分配并包含數據,當該?pool?上的一個?block?被回收時,重新將該?pool?鏈接在適當的?usedpools?鏈表中。?- empty -?沒有存儲數據,可以在需要時分配

      我們已經知道,所有相同塊大小的池都鏈接在一起。為了有效地管理池,Python使用了一個名為usedpools的列表存儲跟蹤可用池的指針。在分配內存時,Pymalloc先判斷是否存在要申請的大小的Pool,如果存在的話,直接從Pool中獲取一個Free Block返回給應用程序,這個過程是非常迅速的。如果分配完這個Block后此Pool變為一個空Pool,則將這個Pool從鏈表中移除。

      Arenas是最大的內存塊,并在內存中的頁面邊界上對齊,頁面邊界是OS使用的固定長度連續內存塊的邊緣。在Arena內的池是一個虛擬內存頁(4K),在堆上分配的256kB內存塊,為64個池提供內存。

      arena?結構定義如下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都使用雙鏈表(nextarena和prevarena字段)鏈接,跟上面的pool類似。?ntotalpools?總共分配的?pool?數量?nfreepools?可分配的?pool?數量?freepools?字段指向可用池的鏈接列表

      Arenas的實現沒有什么復雜的??梢詫⑵湟暈槿萜髁斜恚谛枰獣r自動為Pool分配新內存。

      Python的小對象管理器不一定會將內存釋放回操作系統,保留了一些已分配的內存塊,以備將來進一步使用。?arena?完全釋放當且僅當其中的所有池都為空時,如果長時間運行的Python進程隨著時間的推移需要更多內存,但并不一定意味著您有內存泄漏。

      內存優化

      內存分析工具

      resource?模塊用來查看項目當前得的固有的)內存消耗(固有內存是項目實際使用的RAM),注意resource庫只在linux系統下有效

      import?resource

      resource.getrusage(resource.RUSAGE_SELF).ru_maxrss

      443

      objgraph?是一個實用模塊,可以展示當前內存中存在的對象,上文中已經展示過其用法,不再過多說明。

      heapy?是一個實用的,用于調試內存消耗/泄漏的工具。通常,我將objgraph和heapy搭配使用:用?heapy?查看分配對象隨時間增長的差異,heapy能夠顯示對象持有的最大內存等;用Objgraph找backref鏈,嘗試獲取它們不能被釋放的原因。

      Heapy的典型用法是在不同地方的代碼中調用一個函數,試圖為內存使用量提供大量收集線索,找到可能會引發的問題:

      from?guppyimport?hpy

      def?dump_heap(h,?i):

      """

      @param?h:?Theheap?(from?hp?=?hpy(),?h?=?hp.heap())

      @param?i:?Identifierstr

      """

      print?"Dumpingstatsat:?{0}".format(i)

      print?'Memoryusage:?{0}(MB)'.format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024)

      print?"Mostcommontypes:"

      objgraph.show_most_common_types()

      print?"heapis:"

      print?"{0}".format(h)

      by_refs?=?h.byrcs

      print?"byreferences:?{0}".format(by_refs)

      print?"Morestatsfor?topelement.."

      print?"Byclodo?(class?or?dict?owner):?{0}".format(by_refs[0].byclodo)

      print?"Bysize:?{0}".format(by_refs[0].bysize)

      print?"Byid:?{0}".format(by_refs[0].byid)

      memory_profile包最好有psutil包的支持,二者皆可以通過pip安裝。

      pip?install?psutil?memory_profiler

      memory_profiler的對性能的影響很大,所以最好將測試局限在一個較小的問題上,或者在代碼成長的早期進行分析。

      使用的方法分兩步:

      第一步是在源代碼中用‘@profile’對需要分析的函數進行標記,

      第二步是調用memory_profiler模塊進行分析。

      python?-m?memory_profiler?test_memory.py

      在處理內存分配時,情況并不像CPU占有率那么直截了當,通常一個進程將內存超額分配給本地內存池并在空閑時使用會更有效率,因為一次內存分配的操作非常昂貴;

      另外,垃圾收集不會立即執行,所以對象被銷毀后依然會存在垃圾收集池中一段時間;

      這些技術的后或是很難真正了解一個Python程序內部的內存使用和釋放情況,因為當從進程外部觀察時,某一段代碼可能不會分配固定數量的內存,觀察多行代碼的內存占有趨勢可能比只觀察一行代碼更有參考價值。

      垃圾回收優化

      對Python的垃圾回收進行調優的一個最簡單的手段便是關閉自動回收,根據情況手動觸發。

      相比完全手動的垃圾回收,一個更溫和的方法是調高垃圾回收的閾值。?調高閾值的方法能在一定程度上避免內存溢出的問題(但不能完全避免),同時可能減少可觀的垃圾回收開銷。根據具體項目?的不同,甚至是程序輸入的不,合適的閾值也不同。因此需要反復測試找到一個合適的閾值,這也算調高閾值這種手段?的一個缺點。

      一個可能更好的方法是使用良好的編程習慣盡可能的避免循環引用。兩種常見的手段包括:?手動解循環引用和使用弱引用。

      查找循環引用,?Python的gc模塊提供了gc.set_debug接口來設置一些輔助的調試信息。如果我們調用gc.set_debug(gc.DEBUG_COLLECTABLE | gc.DEBUG_OBJECTS)則每當Python進行垃圾回收時都會將?其發現的無法到達需要回收的對象的信息打印出來。例如:

      gc.collect()

      gc.set_debug(gc.DEBUG_COLLECTABLE?|?gc.DEBUG_OBJECTS)

      test2()

      gc.collect()

      gc:?collectable?

      gc:?collectable?

      gc:?collectable?

      gc:?collectable?

      Slots

      默認情況下,自定義的對象都使用dict來存儲屬性(通過obj.dict__查看),而python中的dict大小一般比實際存儲的元素個數要大(以此降低hash沖突概率),因此會浪費一定的空間。在新式類中使用__slots,就是告訴Python虛擬機,這種類型的對象只會用到這些屬性,因此虛擬機預留足夠的空間就行了,如果聲明了__slots__,那么對象就不會再有__dict__屬性。

      class?Student(object):

      __slots__?=?('name',?'age')?#?用tuple定義允許綁定的屬性名稱

      使用slots到底能帶來多少內存優化呢,取決于類自身有多少屬性、屬性的類型,以及同時存在多少個類的實例。

      #?-*-?coding:?utf-8?-*-

      import?sys

      import?tracemalloc

      NUM_OF_ATTR?=??3?#3?#?10?#?30?#90

      NUM_OF_INSTANCE?=?10?#?10?#?100

      class?Slots(object):

      __slots__?=?['attr%s'%i?for?i?in?range(NUM_OF_ATTR)]

      def?__init__(self):

      value_lst?=?(1.0,?True,?[],?{},?())

      for?i?in?range(NUM_OF_ATTR):

      setattr(self,?'attr%s'%i,?value_lst[i?%?len(value_lst)])

      class?NoSlots(object):

      def?__init__(self):

      value_lst?=?(1.0,?True,?[],?{},?())

      for?i?in?range(NUM_OF_ATTR):

      setattr(self,?'attr%s'%i,?value_lst[i?%?len(value_lst)])

      if?__name__?==?'__main__':

      clz?=?Slots?if?len(sys.argv)?>?1?else?NoSlots

      tracemalloc.start()

      Python 內存管理學習

      objs?=?[clz()?for?i?in?range(NUM_OF_INSTANCE)]

      print(tracemalloc.get_traced_memory()[0])

      上面的代碼,主要是在每個實例的屬性數目、并發存在的實例數目兩個維度進行測試,并沒有測試不同的屬性類型。結果如下表:

      實例數\屬性數

      3

      10

      30

      90

      10

      28.7%

      25.7%

      43.9%

      64.2%

      100

      42.7%

      21.6%

      36.5%

      56.7%

      百分比為內存優化百分比,計算公式為(b - a) / b,?其中b為沒有使用__slots__時分配的內存,?a為使用了__slots__時分配的內存。

      注意事項

      ·?????????基類和子類都必須__slots__,即使基類或者子類沒有屬性

      ·?????????>>>?class?Base(object):

      ·

      ·?????????...?????pass

      ·?????????...

      ·?????????>>>?class?Derived(Base):

      ·?????????...?????__slots__?=?('a',?)

      ·?????????...

      ·?????????>>>?d.__slots__

      ·?????????('a',)

      ·?????????>>>?getattr(d,?'__dict__',?'No?Dict')

      ·?????????{}

      ·?????????子類會繼承基類的__slots__

      ·?????????>>>?class?Base(object):

      ·

      ·?????????...?????__slots__?=?('a',)

      ·?????????...

      ·?????????>>>?class?Derived(Base):

      ·?????????...?????__slots__?=?('b',?)

      ·?????????...

      ·?????????>>>?d?=?Derived()

      ·?????????>>>?d.__slots__

      ·?????????('b',)

      ·?????????>>>?getattr(d,?'__dict__',?'No?Dict')

      ·?????????'No?Dict'

      ·?????????>>>?d.a?=?1

      ·?????????>>>?d.c?=?0

      ·?????????Traceback?(most?recent?call?last):

      ·???????????File?"",?line?1,?in?

      ·?????????AttributeError:?'Derived'?object?has?no?attribute?'c'

      棧溢出

      在計算機中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由于棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。

      解決遞歸調用棧溢出的方法是通過尾遞歸優化,事實上尾遞歸和循環的效果是一樣的,所以,把循環看成是一種特殊的尾遞歸函數也是可以的。

      尾遞歸是指,在函數返回的時候,調用自身本身,并且,return語句不能包含表達式。這樣,編譯器或者解釋器就可以把尾遞歸做優化,使遞歸本身無論調用多少次,都只占用一個棧幀,不會出現棧溢出的情況。

      例如斐波那契數列,在最開始的時候所有的斐波那契代碼都是使用遞歸的方式來寫的,遞歸有很多的缺點,執行效率低下,浪費資源,還有可能會造成棧溢出,而遞歸的程序的優點也是很明顯的,就是結構層次很清晰,易于理解。

      可以使用循環的方式來取代遞歸,當然也可以使用尾遞歸的方式來實現。

      #encoding:utf-8

      import?time

      def?Fib_recursion(num):

      '''

      直接使用遞歸法求解斐波那契數量的第num個數字

      '''

      if?num<2:

      return?num

      return?Fib_recursion(num-1)+Fib_recursion(num-2)

      def?Fib_tail_recursion(num,res,temp):

      '''

      使用尾遞歸法求解斐波那契數量的第num個數字

      '''

      if?num==0:

      return?res

      else:

      return?Fib_tail_recursion(num-1,?temp,?res+temp)

      def?Fib_circle(num):

      '''

      直接使用循環來求解

      '''

      a=0

      b=1

      for?i?in?range(1,num):

      c=a+b

      a=b

      b=c

      return?c

      if?__name__?==?'__main__':

      num_list=[5,10,20,30,40,50]

      for?num?in?num_list:

      start_time=time.time()

      print?Fib_recursion(num)

      end_time=time.time()

      print?Fib_tail_recursion(num,0,1)

      end_time2=time.time()

      print?Fib_circle(num)

      end_time3=time.time()

      print?'正在求解的斐波那契數字下標為%s'?%num

      print?'直接遞歸耗時為?:',?end_time-start_time

      print?'尾遞歸調用耗時為:',?end_time2-end_time

      print?'直接使用循環耗時為:',?end_time3-end_time2

      copy模塊的deepcopy可以很好的完成對象的深拷貝,一個深層拷貝會構造一個新的復合對象,然后遞歸地將原始對象中所找到的對象的副本?插入。

      當對象節點內容過于龐大或者存在循環應用的話,會導致棧內存溢出或者遞歸循環導致進程崩潰;

      實際中,由于深層復制會復制所有內容,因此可能會過多復制(例如本應該在副本之間共享的數據)。

      數據庫 華為云數據庫 python

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:常見IoT安全威脅種類有哪些?
      下一篇:【大廠內參】第11期:WeLink的殺手锏和遠程辦公軟件的另一面
      相關文章
      相泽亚洲一区中文字幕| 亚洲精品无码一区二区| 亚洲成a人片在线观看天堂无码| 亚洲国产成人久久精品app| 无码欧精品亚洲日韩一区| 亚洲AV无码第一区二区三区| 国产成人综合亚洲亚洲国产第一页| 亚洲国模精品一区| 亚洲精品动漫人成3d在线| 亚洲精品国产va在线观看蜜芽| 亚洲va中文字幕无码| 亚洲AV无码一区二区乱子仑| 亚洲精品无码永久在线观看男男 | 在线播放亚洲精品| 国产成人综合亚洲| 亚洲国产成人精品无码久久久久久综合| 最新亚洲人成网站在线观看| 国产精品亚洲а∨天堂2021 | 亚洲AV成人一区二区三区AV| 国产亚洲欧洲精品| 国产亚洲综合成人91精品| 亚洲中文字幕无码不卡电影| 亚洲日韩精品无码专区网址| 亚洲av午夜成人片精品网站| 精品日韩亚洲AV无码| 亚洲妇女水蜜桃av网网站| 亚洲高清中文字幕免费| 亚洲国产成人AV在线播放| www.91亚洲| 亚洲人成人网站色www| 久久夜色精品国产嚕嚕亚洲av| 亚洲影院在线观看| 亚洲国产精品成人久久久| 男人天堂2018亚洲男人天堂 | 中文字幕精品亚洲无线码一区应用| 国产成人亚洲精品影院| 国产亚洲A∨片在线观看| 精品亚洲成a人片在线观看| 亚洲videos| 豆国产96在线|亚洲| 亚洲男人的天堂在线va拉文|