亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Java > 正文

Java內存分析(3)——String的Intern方法詳解

2019-11-06 06:18:06
字體:
來源:轉載
供稿:網友

原文地址:http://www.cnblogs.com/wxgblogs/p/5635099.html

引言

  在 java 語言中有8中基本類型和一種比較特殊的類型String。這些類型為了使他們在運行過程中速度更快,更節省內存,都提供了一種常量池的概念。常量池就類似一個JAVA系統級別提供的緩存。8種基本類型的常量池都是系統協調的,String類型的常量池比較特殊。它的主要使用方法有兩種:

直接使用雙引號聲明出來的String對象會直接存儲在常量池中。 如果不是用雙引號聲明的String對象,可以使用String提供的intern方法。intern 方法會從字符串常量池中查詢當前字符串是否存在,若不存在就會將當前字符串放入常量池中 一. intern 的實現原理

1.JAVA 代碼

/** * Returns a canonical rePResentation for the string object. * <p> * A pool of strings, initially empty, is maintained privately by the * class <code>String</code>. * <p> * When the intern method is invoked, if the pool already contains a * string equal to this <code>String</code> object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this <code>String</code> object is added to the * pool and a reference to this <code>String</code> object is returned. * <p> * It follows that for any two strings <code>s</code> and <code>t</code>, * <code>s.intern() == t.intern()</code> is <code>true</code> * if and only if <code>s.equals(t)</code> is <code>true</code>. * <p> * All literal strings and string-valued constant expressions are * interned. String literals are defined in section 3.10.5 of the * <cite>The Java? Language Specification</cite>. * * @return a string that has the same contents as this string, but is * guaranteed to be from a pool of unique strings. */ public native String intern();

  String.intern方法中看到,這個方法是一個 native 的方法,但注釋寫的非常明了?!叭绻A砍刂写嬖诋斍白址? 就會直接返回當前字符串. 如果常量池中沒有此字符串, 會將此字符串放入常量池中后, 再返回”。

2,native 代碼

  在 jdk7后,Oracle 接管了 JAVA 的源碼后就不對外開放了,根據 jdk 的主要開發人員聲明 openJdk7 和 jdk7 使用的是同一分主代碼,只是分支代碼會有些許的變動。所以可以直接跟蹤 openJdk7 的源碼來探究 intern 的實現。

native實現代碼:

/openjdk7/jdk/src/share/native/java/lang/String.c

Java_java_lang_String_intern(JNIEnv *env, jobject this){ return JVM_InternString(env, this);}/openjdk7/hotspot/src/share/vm/prims/jvm.h/** java.lang.String*/JNIEXPORT jstring JNICALLJVM_InternString(JNIEnv *env, jstring str);

/openjdk7/hotspot/src/share/vm/prims/jvm.cpp

// String support ///////////////////////////////////////////////////////////////////////////JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str)) JVMWrapper("JVM_InternString"); JvmtiVMObjectAllocEventCollector oam; if (str == NULL) return NULL; oop string = JNIHandles::resolve_non_null(str); oop result = StringTable::intern(string, CHECK_NULL); return (jstring) JNIHandles::make_local(env, result);JVM_END

/openjdk7/hotspot/src/share/vm/classfile/symbolTable.cpp

oop StringTable::intern(Handle string_or_null, jchar* name, int len, TRAPS) { unsigned int hashValue = java_lang_String::hash_string(name, len); int index = the_table()->hash_to_index(hashValue); oop string = the_table()->lookup(index, name, len, hashValue); // Found if (string != NULL) return string; // Otherwise, add to symbol to table return the_table()->basic_add(index, string_or_null, name, len, hashValue, CHECK_NULL);}

/openjdk7/hotspot/src/share/vm/classfile/symbolTable.cpp

oop StringTable::lookup(int index, jchar* name, int len, unsigned int hash) { for (HashtableEntry<oop>* l = bucket(index); l != NULL; l = l->next()) { if (l->hash() == hash) { if (java_lang_String::equals(l->literal(), name, len)) { return l->literal(); } } } return NULL;}

  它的大體實現結構就是:JAVA 使用 jni 調用c++實現的StringTable的intern方法, StringTable的intern方法跟Java中的HashMap的實現是差不多的, 只是不能自動擴容。默認大小是1009。要注意的是,String的String Pool是一個固定大小的Hashtable,默認值大小長度是1009,如果放進String Pool的String非常多,就會造成Hash沖突嚴重,從而導致鏈表會很長,而鏈表長了后直接會造成的影響就是當調用String.intern時性能會大幅下降。在 jdk6中StringTable是固定的,就是1009的長度,所以如果常量池中的字符串過多就會導致效率下降很快。在jdk7中,StringTable的長度可以通過一個參數指定:

-XX:StringTableSize=99991

二.jdk6 和 jdk7 下 intern 的區別

  相信很多 JAVA 程序員都做做類似 String s = new String(“abc”)這個語句創建了幾個對象的題目。 這種題目主要就是為了考察程序員對字符串對象的常量池掌握與否。上述的語句中是創建了2個對象,第一個對象是”abc”字符串存儲在常量池中,第二個對象在JAVA Heap中的 String 對象。

public static void main(String[] args) { String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4);}

打印結果是

jdk6 下false false jdk7 下false true      具體為什么稍后再解釋,然后將s3.intern();語句下調一行,放到String s4 = “11”;后面。將s.intern(); 放到String s2 = “1”;后面。是什么結果呢

public static void main(String[] args) { String s = new String("1"); String s2 = "1"; s.intern(); System.out.println(s == s2); String s3 = new String("1") + new String("1"); String s4 = "11"; s3.intern(); System.out.println(s3 == s4);}

打印結果為:

jdk6 下false false jdk7 下false false

1.jdk6中的解釋

這里寫圖片描述   注:圖中綠色線條代表 string 對象的內容指向。 黑色線條代表地址指向。

  如上圖所示。首先說一下 jdk6中的情況,在 jdk6中上述的所有打印都是 false 的,因為 jdk6中的常量池是放在 Perm 區中的,Perm區和正常的 JAVA Heap 區域是完全分開的。上面說過如果是使用引號聲明的字符串都是會直接在字符串常量池中生成,而 new 出來的 String 對象是放在 JAVA Heap 區域。所以拿一個 JAVA Heap 區域的對象地址和字符串常量池的對象地址進行比較肯定是不相同的,即使調用String.intern方法也是沒有任何關系的。

2.jdk7中的解釋

  在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的Perm區的,Perm區是一個類靜態的區域,主要存儲一些加載類的信息,常量池,方法片段等內容,默認大小只有4m,一旦常量池中大量使用 intern 是會直接產生java.lang.OutOfMemoryError:PermGen space錯誤的。在 jdk7 的版本中,字符串常量池已經從Perm區移到正常的Java Heap區域了。為什么要移動,Perm 區域太小是一個主要原因,當然據消息稱jdk8已經直接取消了Perm區域,而新建立了一個元區域。應該是jdk開發者認為Perm區域已經不適合現在 JAVA 的發展了。正式因為字符串常量池移動到JAVA Heap區域后,再來解釋為什么會有上述的打印結果。 這里寫圖片描述

在第一段代碼中,先看 s3和s4字符串。String s3 = new String(“1”) + new String(“1”);,這句代碼中現在生成了2最終個對象,是字符串常量池中的“1” 和 JAVA Heap中的 s3引用指向的對象。中間還有2個匿名的new String(“1”)我們不去討論它們。此時s3引用對象內容是”11″,但此時常量池中是沒有 “11”對象的。

接下來s3.intern();這一句代碼,是將 s3中的”11”字符串放入String 常量池中,因為此時常量池中不存在”11”字符串,因此常規做法是跟 jdk6 圖中表示的那樣,在常量池中生成一個”11”的對象,關鍵點是 jdk7 中常量池不在Perm區域了,這塊做了調整。常量池中不需要再存儲一份對象了,可以直接存儲堆中的引用。這份引用指向s3引用的對象。 也就是說引用地址是相同的。

最后String s4 = “11”; 這句代碼中”11″是顯示聲明的,因此會直接去常量池中創建,創建的時候發現已經有這個對象了,此時也就是指向s3引用對象的一個引用。所以s4引用就指向和s3一樣了。因此最后的比較 s3 == s4 是 true。

再看s和 s2 對象。String s = new String(“1”); 第一句代碼,生成了2個對象。常量池中的“1” 和 JAVA Heap 中的字符串對象。s.intern(); 這一句是 s 對象去常量池中尋找后發現 “1” 已經在常量池里了。 接下來String s2 = “1”; 這句代碼是生成一個 s2的引用指向常量池中的“1”對象。 結果就是 s 和 s2 的引用地址明顯不同。圖中畫的很清晰。 這里寫圖片描述

來看第二段代碼,從上邊第二幅圖中觀察。第一段代碼和第二段代碼的改變就是 s3.intern(); 的順序是放在String s4 = “11”;后了。這樣,首先執行String s4 = “11”;聲明 s4 的時候常量池中是不存在“11”對象的,執行完畢后,“11“對象是 s4 聲明產生的新對象。然后再執行s3.intern();時,常量池中“11”對象已經存在了,因此 s3 和 s4 的引用是不同的。 第二段代碼中的 s 和 s2 代碼中,s.intern();,這一句往后放也不會有什么影響了,因為對象池中在執行第一句代碼String s = new String(“1”);的時候已經生成“1”對象了。下邊的s2聲明都是直接從常量池中取地址引用的。 s 和 s2 的引用地址是不會相等的。 小結

  從上述的例子代碼可以看出 jdk7 版本對 intern 操作和常量池都做了一定的修改。主要包括2點:

將String常量池從Perm區移動到了Java Heap區 String#intern 方法時,如果存在堆中的對象,會直接保存對象的引用,而不會重新創建對象。

三.使用 intern

1.intern 正確使用例子

  接下來我們來看一下一個比較常見的使用String#intern方法的例子。

static final int MAX = 1000 * 10000;static final String[] arr = new String[MAX];public static void main(String[] args) throws Exception { Integer[] DB_DATA = new Integer[10]; Random random = new Random(10 * 10000); for (int i = 0; i < DB_DATA.length; i++) { DB_DATA[i] = random.nextInt(); } long t = System.currentTimeMillis(); for (int i = 0; i < MAX; i++) { //arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])); arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern(); } System.out.println((System.currentTimeMillis() - t) + "ms"); System.gc();}

  運行的參數是:-Xmx2g -Xms2g -Xmn1500M 上述代碼是一個演示代碼,其中有兩條語句不一樣,一條是使用 intern,一條是未使用 intern。結果如下圖

2160ms

這里寫圖片描述 826ms 這里寫圖片描述

  通過上述結果,我們發現不使用 intern 的代碼生成了1000w 個字符串,占用了大約640m 空間。 使用了 intern 的代碼生成了1345個字符串,占用總空間 133k 左右。其實通過觀察程序中只是用到了10個字符串,所以準確計算后應該是正好相差100w 倍。雖然例子有些極端,但確實能準確反應出 intern 使用后產生的巨大空間節省。

細心的同學會發現使用了 intern 方法后時間上有了一些增長。這是因為程序中每次都是用了 new String 后, 然后又進行 intern 操作的耗時時間,這一點如果在內存空間充足的情況下確實是無法避免的,但我們平時使用時,內存空間肯定不是無限大的,不使用 intern 占用空間導致 jvm 垃圾回收的時間是要遠遠大于這點時間的。 畢竟這里使用了1000w次intern 才多出來1秒鐘多的時間。

2,intern 不當使用

  看過了 intern 的使用和 intern 的原理等,我們來看一個不當使用 intern 操作導致的問題。

  在使用 fastjson 進行接口讀取的時候,我們發現在讀取了近70w條數據后,我們的日志打印變的非常緩慢,每打印一次日志用時30ms左右,如果在一個請求中打印2到3條日 志以上會發現請求有一倍以上的耗時。在重新啟動 jvm 后問題消失。繼續讀取接口后,問題又重現。接下來我們看一下出現問題的過程。

1,根據 log4j 打印日志查找問題原因

在使用log4j#info打印日志的時候時間非常長。所以使用 housemd 軟件跟蹤 info 方法的耗時堆棧。

trace SLF4JLogger. trace AbstractLoggerWrapper: trace AsyncLogger

org/apache/logging/log4j/core/async/AsyncLogger.actualAsyncLog(RingBufferLogEvent) sun.misc.Launcher$AppClassLoader@109aca82 1 1ms org.apache.logging.log4j.core.async.AsyncLogger@19de86bb org/apache/logging/log4j/core/async/AsyncLogger.location(String) sun.misc.Launcher$AppClassLoader@109aca82 1 30ms org.apache.logging.log4j.core.async.AsyncLogger@19de86bb org/apache/logging/log4j/core/async/AsyncLogger.log(Marker, String, Level, Message, Throwable) sun.misc.Launcher$AppClassLoader@109aca82 1 61ms org.apache.logging.log4j.core.async.AsyncLogger@19de86bb代碼出在 AsyncLogger.location 這個方法上. 里邊主要是調用了 return Log4jLogEvent.calcLocation(fqcnOfLogger);和Log4jLogEvent.calcLocation()

Log4jLogEvent.calcLocation()的代碼如下:

public static StackTraceElement calcLocation(final String fqcnOfLogger) { if (fqcnOfLogger == null) { return null; } final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); boolean next = false; for (final StackTraceElement element : stackTrace) { final String className = element.getClassName(); if (next) { if (fqcnOfLogger.equals(className)) { continue; } return element; } if (fqcnOfLogger.equals(className)) { next = true; } else if (NOT_AVAIL.equals(className)) { break; } } return null; }

  經過跟蹤發現是 Thread.currentThread().getStackTrace(); 的問題。

2, 跟蹤Thread.currentThread().getStackTrace()的 native 代碼,驗證String#intern

  Thread.currentThread().getStackTrace();native的方法:

public StackTraceElement[] getStackTrace() { if (this != Thread.currentThread()) { // check for getStackTrace permission SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkPermission( SecurityConstants.GET_STACK_TRACE_PERMISSION); } // optimization so we do not call into the vm for threads that // have not yet started or have terminated if (!isAlive()) { return EMPTY_STACK_TRACE; } StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this}); StackTraceElement[] stackTrace = stackTraceArray[0]; // a thread that was alive during the previous isAlive call may have // since terminated, therefore not having a stacktrace. if (stackTrace == null) { stackTrace = EMPTY_STACK_TRACE; } return stackTrace; } else { // Don't need JVM help for current thread return (new Exception()).getStackTrace(); } }

下載 openJdk7的源碼查詢 jdk 的 native 實現代碼,列表如下【這里因為篇幅問題,不詳細羅列涉及到的代碼,有興趣的可以根據文件名稱和行號查找相關代碼】:

/openjdk7/jdk/src/share/native/java/lang/Thread.c /openjdk7/hotspot/src/share/vm/prims/jvm.h line:294: /openjdk7/hotspot/src/share/vm/prims/jvm.cpp line:4382-4414: /openjdk7/hotspot/src/share/vm/services/threadService.cpp line:235-267: /openjdk7/hotspot/src/share/vm/services/threadService.cpp line:566-577: /openjdk7/hotspot/src/share/vm/classfile/javaClasses.cpp line:1635-[1651,1654,1658]:

完成跟蹤了底層的 jvm 源碼后發現,是下邊的三條代碼引發了整個程序的變慢問題。

oop classname = StringTable::intern((char*) str, CHECK_0); oop methodname = StringTable::intern(method->name(), CHECK_0); oop filename = StringTable::intern(source, CHECK_0);

這三段代碼是獲取類名、方法名、和文件名。因為類名、方法名、文件名都是存儲在字符串常量池中的,所以每次獲取它們都是通過String#intern方法。但沒有考慮到的是默認的 StringPool 的長度是1009且不可變的。因此一旦常量池中的字符串達到的一定的規模后,性能會急劇下降。

3,fastjson 不當使用 String#intern

導致這個 intern 變慢的原因是因為 fastjson 對String#intern方法的使用不當造成的。跟蹤 fastjson 中的實現代碼發現,

com.alibaba.fastjson.parser.JSONScanner#scanFieldSymbol():

if (ch == '/"') { bp = index; this.ch = ch = buf[bp]; strVal = symbolTable.addSymbol(buf, start, index - start - 1, hash); break;}

com.alibaba.fastjson.parser.SymbolTable#addSymbol():

/** * Constructs a new entry from the specified symbol information and next entry reference. */public Entry(char[] ch, int offset, int length, int hash, Entry next){ characters = new char[length]; System.arraycopy(ch, offset, characters, 0, length); symbol = new String(characters).intern(); this.next = next; this.hashCode = hash; this.bytes = null;}

fastjson 中對所有的 json 的 key 使用了 intern 方法,緩存到了字符串常量池中,這樣每次讀取的時候就會非常快,大大減少時間和空間。而且 json 的 key 通常都是不變的。這個地方沒有考慮到大量的 json key 如果是變化的,那就會給字符串常量池帶來很大的負擔。

這個問題 fastjson 在1.1.24版本中已經將這個漏洞修復了。程序加入了一個最大的緩存大小,超過這個大小后就不會再往字符串常量池中放了。

[1.1.24版本的com.alibaba.fastjson.parser.SymbolTable#addSymbol() Line:113]代碼

public static final int MAX_SIZE = 1024;if (size >= MAX_SIZE) { return new String(buffer, offset, len);}

這個問題是70w 數據量時候的引發的,如果是幾百萬的數據量的話可能就不只是30ms 的問題了。因此在使用系統級提供的String#intern方式一定要慎重!

五.總結

  本文大體的描述了 String#intern和字符串常量池的日常使用,jdk 版本的變化和String#intern方法的區別,以及不恰當使用導致的危險等內容,讓大家對系統級別的 String#intern有一個比較深入的認識。讓我們在使用和接觸它的時候能避免出現一些 bug,增強系統的健壯性。

不忘初心,方得始終


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲社区在线观看| 国产精品一区二区av影院萌芽| 日韩成人av网址| 欧美香蕉大胸在线视频观看| 一二美女精品欧洲| 福利二区91精品bt7086| 亚洲精品国产精品自产a区红杏吧| 欧美性色19p| 成人性教育视频在线观看| 97热精品视频官网| 亚洲国产高清自拍| 久久久久久网址| 久久天天躁日日躁| 亚洲娇小xxxx欧美娇小| 国产婷婷成人久久av免费高清| 久久久精品国产亚洲| 日韩激情第一页| 26uuu另类亚洲欧美日本一| 久久99热精品这里久久精品| 欧美日韩ab片| 精品国产拍在线观看| 久久韩剧网电视剧| 日韩免费在线电影| 中文字幕九色91在线| 亚洲毛片一区二区| 国产在线观看精品| 国产精品毛片a∨一区二区三区|国| 久久网福利资源网站| 日韩在线观看精品| 成人免费看黄网站| 亚洲人成在线免费观看| 欧美日韩精品中文字幕| 欧美午夜xxx| 国产欧洲精品视频| 色哟哟入口国产精品| 亚洲午夜精品久久久久久性色| 亚洲精品短视频| 深夜福利国产精品| 最近2019中文字幕在线高清| 国产精品旅馆在线| 国产在线播放不卡| 色妞欧美日韩在线| 亚洲a∨日韩av高清在线观看| 久久精品国产精品亚洲| 91精品国产综合久久男男| 午夜欧美大片免费观看| 高清视频欧美一级| 尤物99国产成人精品视频| 国产精品久久久久久久app| 国产精品久久久av| 成人av色在线观看| 欧美激情在线一区| 精品视频在线播放| 69av成年福利视频| 亚洲天堂男人天堂女人天堂| 久久91精品国产| 亚洲日韩欧美视频一区| 国产日韩欧美91| 中文欧美在线视频| 91在线观看欧美日韩| 91社影院在线观看| 久久久免费高清电视剧观看| 免费97视频在线精品国自产拍| 成人夜晚看av| 日韩欧美在线观看视频| 91久久精品日日躁夜夜躁国产| 亚洲经典中文字幕| 欧美日韩国产中文精品字幕自在自线| 韩国国内大量揄拍精品视频| 日韩欧美999| 欧美在线不卡区| 亚洲色图15p| 亚洲色图13p| 成人在线观看视频网站| 成人免费在线网址| 在线播放精品一区二区三区| 国产成人精品免高潮在线观看| 国产精品一区二区女厕厕| 欧美久久精品午夜青青大伊人| 91久久久在线| 亚洲国产成人精品久久| 亚洲电影第1页| 亚洲一区二区三区乱码aⅴ| 日韩中文在线观看| 国产成人在线亚洲欧美| 日本午夜精品理论片a级appf发布| 亚洲精品午夜精品| 亚洲精品电影网在线观看| 美女999久久久精品视频| 成人夜晚看av| 久青草国产97香蕉在线视频| 亚洲成年网站在线观看| 久久久久国产精品一区| 亚洲风情亚aⅴ在线发布| 成人福利免费观看| 国产成人亚洲综合91| 成人午夜在线观看| 亚洲一区二区三区在线免费观看| 97精品一区二区三区| 久久影院中文字幕| 亚洲2020天天堂在线观看| 欧美激情综合亚洲一二区| 亚洲高清免费观看高清完整版| 亚洲精品网址在线观看| 欧美高跟鞋交xxxxxhd| 45www国产精品网站| 亚洲国产精品免费| 国产亚洲精品成人av久久ww| 欧美久久久精品| 欧美另类极品videosbestfree| 日韩精品极品视频| 97av视频在线| 欧美成人在线影院| 欧美日韩国产精品一区二区三区四区| 欧美精品在线免费播放| 日本高清视频一区| 国产亚洲精品久久久久动| 中国人与牲禽动交精品| 2019中文字幕全在线观看| 91在线免费网站| 日韩h在线观看| 成年无码av片在线| 中文字幕亚洲综合久久筱田步美| 欧美老女人www| 国产精品久久久久久久一区探花| 国产精品久久久久国产a级| 亚洲人高潮女人毛茸茸| 国产精品小说在线| 亚洲激情视频在线观看| 欧美日韩在线影院| 国产97色在线|日韩| 91视频免费在线| 亚洲欧美一区二区三区久久| 国产精品久久久久久一区二区| 欧美裸体男粗大视频在线观看| 国产91免费看片| 国产日韩精品综合网站| 黑人精品xxx一区一二区| 亚洲自拍中文字幕| 久久久久久久999精品视频| 亚洲热线99精品视频| 亚洲自拍欧美色图| 成人黄色免费看| 亚洲国产天堂久久综合网| 久久精视频免费在线久久完整在线看| 国产精品精品一区二区三区午夜版| 成人精品在线观看| 亚洲精品国产精品乱码不99按摩| 精品视频在线播放色网色视频| 欧美性videos高清精品| 欧美精品国产精品日韩精品| 亚洲香蕉av在线一区二区三区| 日韩成人av一区| 7m精品福利视频导航| 国产精品第2页| 欧美亚洲日本网站| 欧美激情国产日韩精品一区18| 国产精品老女人视频| 亚洲性线免费观看视频成熟| 国产97在线播放| 欧美wwwwww| 欧美性xxxx极品hd满灌| 成人免费看吃奶视频网站| 亚洲日本欧美中文幕|