日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

jni2

發(fā)布時(shí)間:2023/12/18 编程问答 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 jni2 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

JNI 調(diào)用構(gòu)造方法和父類實(shí)例方法

在前面我們學(xué)習(xí)到了在 Native 層如何調(diào)用 Java 靜態(tài)方法和實(shí)例方法,其中調(diào)用實(shí)例方法的示例代碼中也提到了調(diào)用構(gòu)造函數(shù)來(lái)實(shí)始化一個(gè)對(duì)象,但沒(méi)有詳細(xì)介紹,一帶而過(guò)了。還沒(méi)有閱讀過(guò)的同學(xué)請(qǐng)移步《JNI——C/C++ 訪問(wèn) Java 實(shí)例方法和靜態(tài)方法》閱讀。這章詳細(xì)來(lái)介紹下初始一個(gè)對(duì)象的兩種方式,以及如何調(diào)用子類對(duì)象重寫的父類實(shí)例方法。

構(gòu)造方法和父類實(shí)例方法

我們先回過(guò)一下,在 Java 中實(shí)例化一個(gè)對(duì)象和調(diào)用父類實(shí)例方法的流程。先看一段代碼:

package com.study.jnilearn; public class Animal {public void run() {System.out.println("Animal.run...");} }package com.study.jnilearn; public class Cat extends Animal {@Overridepublic void run() {System.out.println(name + " Cat.run...");} }public static void main(String[] args) {Animal cat = new Cat("湯姆");cat.run(); }

正如你所看到的那樣,上面這段代碼非常簡(jiǎn)單,有兩個(gè)類 Animal 和 Cat,Animal 類中定義了 run 和 getName 兩個(gè)方法,Cat 繼承自 Animal,并重寫了父類的 run 方法。在 main 方法中,首先定義了一個(gè) Animal 類型的變量 cat,并指向了 Cat 類的實(shí)例對(duì)象,然后調(diào)用了它的 run 方法。在執(zhí)行 new Cat(“湯姆”)這段代碼時(shí),會(huì)先為 Cat 類分配內(nèi)存空間(所分配的內(nèi)存空間大小由 Cat 類的成員變量數(shù)量決定),然后調(diào)用 Cat 的帶參構(gòu)造方法初始化對(duì)象。?cat 是 Animal 類型,但它指向的是 Cat 實(shí)例對(duì)象的引用,而且 Cat 重寫了父類的 run 方法,因?yàn)檎{(diào)用 run 方法時(shí)有多態(tài)存在,所以訪問(wèn)的是 Cat 的 run 而非 Animal 的 run,運(yùn)行后打印的結(jié)果為:湯姆 Cat.run…

如果要調(diào)用父類的 run 方法,只需在 Cat 的 run 方法中調(diào)用 super.run() 即可,相當(dāng)?shù)暮?jiǎn)單。

寫過(guò) C 或 C++ 的同學(xué)應(yīng)該都有一個(gè)很深刻的內(nèi)存管理概念,棧空間和堆空間,棧空間的內(nèi)存大小受操作系統(tǒng)限制,由操作系統(tǒng)自動(dòng)來(lái)管理,速度較快,所以在函數(shù)中定義的局部變量、函數(shù)形參變量都存儲(chǔ)在棧空間。操作系統(tǒng)沒(méi)有限制堆空間的內(nèi)存大小,只受物理內(nèi)存的限制,內(nèi)存需要程序員自己管理。在 C 語(yǔ)言中用 malloc 關(guān)鍵字動(dòng)態(tài)分配的內(nèi)存和在 C++ 中用 new 創(chuàng)建的對(duì)象所分配內(nèi)存都存儲(chǔ)在堆空間,內(nèi)存使用完之后分別用free或delete/delete[]釋放。這里不過(guò)多的討論 C/C++ 內(nèi)存管理方面的知識(shí),有興趣的同學(xué)請(qǐng)自行百度。做 Java 的童鞋眾所周知,寫 Java 程序是不需要手動(dòng)來(lái)管理內(nèi)存的,內(nèi)存管理那些煩鎖的事情全都交由一個(gè)叫 GC 的線程來(lái)管理(當(dāng)一個(gè)對(duì)象沒(méi)有被其它對(duì)象所引用時(shí),該對(duì)象就會(huì)被 GC 釋放)。但我覺(jué)得 Java 內(nèi)部的內(nèi)存管理原理和 C/C++ 是非常相似的,上例中,Animal cat = new Cat(“湯姆”);局部變量 cat 存放在棧空間上,new Cat (“湯姆”);創(chuàng)建的實(shí)例對(duì)象存放在堆空間,返回一個(gè)內(nèi)存地址的引用,存儲(chǔ)在 cat 變量中。這樣就可以通過(guò) cat 變量所指向的引用訪問(wèn) Cat 實(shí)例當(dāng)中所有可見(jiàn)的成員了。

所以創(chuàng)建一個(gè)對(duì)象分為 2 步:

  • 為對(duì)象分配內(nèi)存空間
  • 初始化對(duì)象(調(diào)用對(duì)象的構(gòu)造方法)

下面通過(guò)一個(gè)示例來(lái)了解在 JNI 中是如何調(diào)用對(duì)象構(gòu)造方法和父類實(shí)例方法的。為了讓示例能清晰的體現(xiàn)構(gòu)造方法和父類實(shí)例方法的調(diào)用流程,定義了 Animal 和 Cat 兩個(gè)類,Animal 定義了一個(gè) String 形參的構(gòu)造方法,一個(gè)成員變量 name、兩個(gè)成員函數(shù) run 和 getName,Cat 繼承自 Animal,并重寫了 run 方法。在 JNI 中實(shí)現(xiàn)創(chuàng)建 Cat 對(duì)象的實(shí)例,調(diào)用 Animal 類的 run 和 getName 方法。代碼如下所示。

// Animal.java package com.study.jnilearn; public class Animal {protected String name;public Animal(String name) {this.name = name;System.out.println("Animal Construct call...");}public String getName() {System.out.println("Animal.getName Call...");return this.name;}public void run() {System.out.println("Animal.run...");} }// Cat.java package com.study.jnilearn; public class Cat extends Animal {public Cat(String name) {super(name);System.out.println("Cat Construct call....");}@Overridepublic String getName() {return "My name is " + this.name;}@Overridepublic void run() {System.out.println(name + " Cat.run...");} }// AccessSuperMethod.java package com.study.jnilearn; public class AccessSuperMethod {public native static void callSuperInstanceMethod(); public static void main(String[] args) {callSuperInstanceMethod();}static {System.loadLibrary("AccessSuperMethod");} }

AccessSuperMethod 類是程序的入口,其中定義了一個(gè) native 方法 callSuperInstanceMethod。用 javah 生成的 jni 函數(shù)原型如下。

/* Header for class com_study_jnilearn_AccessSuperMethod */#ifndef _Included_com_study_jnilearn_AccessSuperMethod #define _Included_com_study_jnilearn_AccessSuperMethod #ifdef __cplusplus extern "C" { #endif /** Class: com_study_jnilearn_AccessSuperMethod* Method: callSuperInstanceMethod* Signature: ()V*/ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessSuperMethod_callSuperInstanceMethod(JNIEnv *, jclass);#ifdef __cplusplus } #endif #endif

實(shí)現(xiàn) Java_com_study_jnilearn_AccessSuperMethod_callSuperInstanceMethod 函數(shù),如下所示。

/ AccessSuperMethod.c#include "com_study_jnilearn_AccessSuperMethod.h"JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessSuperMethod_callSuperInstanceMethod(JNIEnv *env, jclass cls) {jclass cls_cat;jclass cls_animal;jmethodID mid_cat_init;jmethodID mid_run;jmethodID mid_getName;jstring c_str_name;jobject obj_cat;const char *name = NULL;// 1、獲取Cat類的class引用cls_cat = (*env)->FindClass(env, "com/study/jnilearn/Cat");if (cls_cat == NULL) {return;}// 2、獲取Cat的構(gòu)造方法ID(構(gòu)造方法的名統(tǒng)一為:<init>)mid_cat_init = (*env)->GetMethodID(env, cls_cat, "<init>", "(Ljava/lang/String;)V");if (mid_cat_init == NULL) {return; // 沒(méi)有找到只有一個(gè)參數(shù)為String的構(gòu)造方法}// 3、創(chuàng)建一個(gè)String對(duì)象,作為構(gòu)造方法的參數(shù)c_str_name = (*env)->NewStringUTF(env, "湯姆貓");if (c_str_name == NULL) {return; // 創(chuàng)建字符串失敗(內(nèi)存不夠)}// 4、創(chuàng)建Cat對(duì)象的實(shí)例(調(diào)用對(duì)象的構(gòu)造方法并初始化對(duì)象)obj_cat = (*env)->NewObject(env,cls_cat, mid_cat_init,c_str_name);if (obj_cat == NULL) {return;}//-------------- 5、調(diào)用Cat父類Animal的run和getName方法 --------------cls_animal = (*env)->FindClass(env, "com/study/jnilearn/Animal");if (cls_animal == NULL) {return;}// 例1: 調(diào)用父類的run方法mid_run = (*env)->GetMethodID(env, cls_animal, "run", "()V"); // 獲取父類Animal中run方法的idif (mid_run == NULL) {return;}// 注意:obj_cat是Cat的實(shí)例,cls_animal是Animal的Class引用,mid_run是Animal類中的方法ID(*env)->CallNonvirtualVoidMethod(env, obj_cat, cls_animal, mid_run);// 例2:調(diào)用父類的getName方法// 獲取父類Animal中g(shù)etName方法的idmid_getName = (*env)->GetMethodID(env, cls_animal, "getName", "()Ljava/lang/String;");if (mid_getName == NULL) {return;}c_str_name = (*env)->CallNonvirtualObjectMethod(env, obj_cat, cls_animal, mid_getName);name = (*env)->GetStringUTFChars(env, c_str_name, NULL);printf("In C: Animal Name is %s\n", name);// 釋放從java層獲取到的字符串所分配的內(nèi)存(*env)->ReleaseStringUTFChars(env, c_str_name, name);quit:// 刪除局部引用(jobject或jobject的子類才屬于引用變量),允許VM釋放被局部變量所引用的資源(*env)->DeleteLocalRef(env, cls_cat);(*env)->DeleteLocalRef(env, cls_animal);(*env)->DeleteLocalRef(env, c_str_name);(*env)->DeleteLocalRef(env, obj_cat); }

運(yùn)行結(jié)果

代碼講解 - 調(diào)用構(gòu)造方法

調(diào)用構(gòu)造方法和調(diào)用對(duì)象的實(shí)例方法方式是相似的,傳入”< init >”作為方法名查找類的構(gòu)造方法ID,然后調(diào)用JNI函數(shù)NewObject調(diào)用對(duì)象的構(gòu)造函數(shù)初始化對(duì)象。如下代碼所示。

obj_cat = (*env)->NewObject(env,cls_cat,mid_cat_init,c_str_name);

上述這段代碼調(diào)用了 JNI 函數(shù) NewObject 創(chuàng)建了 Class 引用的一個(gè)實(shí)例對(duì)象。這個(gè)函數(shù)做了 2 件事情

  • 創(chuàng)建一個(gè)未初始化的對(duì)象并分配內(nèi)存空間
  • 調(diào)用對(duì)象的構(gòu)造函數(shù)初始化對(duì)象。這兩步也可以分開進(jìn)行,為對(duì)象分配內(nèi)存,然后再初始化對(duì)象,如下代碼所示:
// 1、創(chuàng)建一個(gè)未初始化的對(duì)象,并分配內(nèi)存obj_cat = (*env)->AllocObject(env, cls_cat);if (obj_cat) {// 2、調(diào)用對(duì)象的構(gòu)造函數(shù)初始化對(duì)象(*env)->CallNonvirtualVoidMethod(env,obj_cat, cls_cat, mid_cat_init, c_str_name);if ((*env)->ExceptionCheck(env)) { // 檢查異常goto quit;}}

AllocObject 函數(shù)創(chuàng)建的是一個(gè)未初始化的對(duì)象,后面在用這個(gè)對(duì)象之前,必須調(diào)用CallNonvirtualVoidMethod 調(diào)用對(duì)象的構(gòu)造函數(shù)初始化該對(duì)象。而且在使用時(shí)一定要非常小心,確保在一個(gè)對(duì)象上面,構(gòu)造函數(shù)最多被調(diào)用一次。有時(shí),先創(chuàng)建一個(gè)初始化的對(duì)象,然后在合適的時(shí)間再調(diào)用構(gòu)造函數(shù)的方式是很有用的。盡管如此,大部分情況下,應(yīng)該使用 NewObject,盡量避免使用容易出錯(cuò)的 AllocObject/CallNonvirtualVoidMethod 函數(shù)。

代碼講解 - 調(diào)用父類實(shí)例方法

如果一個(gè)方法被定義在父類中,在子類中被覆蓋,也可以調(diào)用父類中的這個(gè)實(shí)例方法。JNI 提供了一系列函數(shù)CallNonvirtualXXXMethod 來(lái)支持調(diào)用各種返回值類型的實(shí)例方法。調(diào)用一個(gè)定義在父類中的實(shí)例方法,須遵循下面的步驟。

使用 GetMethodID 函數(shù)從一個(gè)指向父類的 Class 引用當(dāng)中獲取方法 ID。

cls_animal = (*env)->FindClass(env, "com/study/jnilearn/Animal"); if (cls_animal == NULL) {return; }//例1: 調(diào)用父類的run方法 mid_run = (*env)->GetMethodID(env, cls_animal, "run", "()V"); // 獲取父類Animal中run方法的id if (mid_run == NULL) {return; }

傳入子類對(duì)象、父類 Class 引用、父類方法 ID 和參數(shù),并調(diào)用 CallNonvirtualVoidMethod、 CallNonvirtualBooleanMethod、CallNonvirtualIntMethod 等一系列函數(shù)中的一個(gè)。其中CallNonvirtualVoidMethod 也可以被用來(lái)調(diào)用父類的構(gòu)造函數(shù)。

// 注意:obj_cat是Cat的實(shí)例,cls_animal是Animal的Class引用,mid_run是Animal類中的方法ID (*env)->CallNonvirtualVoidMethod(env, obj_cat, cls_animal, mid_run);

其實(shí)在開發(fā)當(dāng)中,這種調(diào)用父類實(shí)例方法的情況是很少遇到的,通常在 JAVA 中可以很簡(jiǎn)單地做到:?super.func();但有些特殊需求也可能會(huì)用到,所以知道有這么回事還是很有必要的。

?

JNI 調(diào)用性能測(cè)試及優(yōu)化

在前面幾章我們學(xué)習(xí)到了,在 Java 中聲明一個(gè) native 方法,然后生成本地接口的函數(shù)原型聲明,再用 C/C++ 實(shí)現(xiàn)這些函數(shù),并生成對(duì)應(yīng)平臺(tái)的動(dòng)態(tài)共享庫(kù)放到 Java 程序的類路徑下,最后在 Java 程序中調(diào)用聲明的 native 方法就間接的調(diào)用到了 C/C++ 編寫的函數(shù)了,在 C/C++ 中寫的程序可以避開 JVM 的內(nèi)存開銷過(guò)大的限制、處理高性能的計(jì)算、調(diào)用系統(tǒng)服務(wù)等功能。同時(shí)也學(xué)習(xí)到了在本地代碼中通過(guò) JNI 提供的接口,調(diào)用 Java 程序中的任意方法和對(duì)象的屬性。這是 JNI 提供的一些優(yōu)勢(shì)。但做過(guò) Java 的童鞋應(yīng)該都明白,Java 程序是運(yùn)行在 JVM 上的,所以在 Java 中調(diào)用 C/C++ 或其它語(yǔ)言這種跨語(yǔ)言的接口時(shí),或者說(shuō)在 C/C++ 代碼中通過(guò) JNI 接口訪問(wèn) Java 中對(duì)象的方法或?qū)傩詴r(shí),相比 Java 調(diào)用自已的方法,性能是非常低的!網(wǎng)上有朋友針對(duì) Java 調(diào)用本地接口,Java 調(diào) Java 方法做了一次詳細(xì)的測(cè)試,來(lái)充分說(shuō)明在享受 JNI 給程序帶來(lái)優(yōu)勢(shì)的同時(shí),也要接受其所帶來(lái)的性能開銷,請(qǐng)看下面一組測(cè)試數(shù)據(jù)。

Java 調(diào)用 JNI 空函數(shù)與 Java 調(diào)用 Java 空方法性能測(cè)試。

測(cè)試環(huán)境:JDK1.4.2_19、JDK1.5.0_04 和 JDK1.6.0_14,測(cè)試的重復(fù)次數(shù)都是一億次。測(cè)試結(jié)果的絕對(duì)數(shù)值意義不大,僅供參考。因?yàn)楦鶕?jù) JVM 和機(jī)器性能的不同,測(cè)試所產(chǎn)生的數(shù)值也會(huì)不同,但不管什么機(jī)器和 JVM 應(yīng)該都能反應(yīng)同一個(gè)問(wèn)題,Java 調(diào)用 native 接口,要比 Java 調(diào)用 Java 方法性能要低很多。

Java 調(diào)用 Java 空方法的性能:

JDK 版本Java 調(diào) Java 耗時(shí)平均每秒調(diào)用次數(shù)
1.6329ms303951367次
1.5312ms320512820次
1.4312ms27233115次

Java 調(diào)用 JNI 空函數(shù)的性能:

JDK版本Java調(diào)Java耗時(shí)平均每秒調(diào)用次數(shù)
1.61531ms65316786次
1.51891ms52882072次
1.43672ms27233115次

從上述測(cè)試數(shù)據(jù)可以看出 JDK 版本越高,JNI 調(diào)用的性能也越好。在 JDK1.5 中,僅僅是空方法調(diào)用,JNI 的性能就要比 Java 內(nèi)部調(diào)用慢將近 5 倍,而在 JDK1.4 下更是慢了十多倍。

JNI查找方法ID、字段ID、Class引用性能測(cè)試

當(dāng)我們?cè)诒镜卮a中要訪問(wèn) Java 對(duì)象的字段或調(diào)用它們的方法時(shí),本機(jī)代碼必須調(diào)用 FindClass()、GetFieldID()、GetStaticFieldID、GetMethodID()和 GetStaticMethodID()。對(duì)于 GetFieldID()、GetStaticFieldID、GetMethodID() 和 GetStaticMethodID(),為特定類返回的 ID 不會(huì)在 JVM 進(jìn)程的生存期內(nèi)發(fā)生變化。但是,獲取字段或方法的調(diào)用有時(shí)會(huì)需要在 JVM 中完成大量工作,因?yàn)樽侄魏头椒赡苁菑某愔欣^承而來(lái)的,這會(huì)讓 JVM 向上遍歷類層次結(jié)構(gòu)來(lái)找到它們。由于 ID 對(duì)于特定類是相同的,因此只需要查找一次,然后便可重復(fù)使用。同樣,查找類對(duì)象的開銷也很大,因此也應(yīng)該緩存它們。下面對(duì)調(diào)用 JNI 接口 FindClass 查找 Class、GetFieldID 獲取類的字段 ID 和 GetFieldValue 獲取字段的值的性能做的一個(gè)測(cè)試。緩存表示只調(diào)用一次,不緩存就是每次都調(diào)用相應(yīng)的 JNI 接口:

java.version = 1.6.0_14

  • JNI 字段讀取 (緩存Class=false ,緩存字段ID=false) 耗時(shí) : 79172 ms 平均每秒 : 1263072
  • JNI 字段讀取 (緩存Class=true ,緩存字段ID=false) 耗時(shí) : 25015 ms 平均每秒 : 3997601
  • JNI 字段讀取 (緩存Class=false ,緩存字段ID=true) 耗時(shí) : 50765 ms 平均每秒 : 1969861
  • JNI 字段讀取 (緩存Class=true ,緩存字段ID=true) 耗時(shí) : 2125 ms 平均每秒 : 47058823

java.version = 1.5.0_04

  • JNI 字段讀取 (緩存Class=false ,緩存字段ID=false) 耗時(shí) : 87109 ms 平均每秒 : 1147987
  • JNI 字段讀取 (緩存Class=true ,緩存字段ID=false) 耗時(shí) : 32031 ms 平均每秒 : 3121975
  • JNI 字段讀取 (緩存Class=false ,緩存字段ID=true) 耗時(shí) : 51657 ms 平均每秒 : 1935846
  • JNI 字段讀取 (緩存Class=true ,緩存字段ID=true) 耗時(shí) : 2187 ms 平均每秒 : 45724737

java.version = 1.4.2_19

  • JNI 字段讀取 (緩存Class=false ,緩存字段ID=false) 耗時(shí) : 97500 ms 平均每秒 : 1025641
  • JNI 字段讀取 (緩存Class=true ,緩存字段ID=false) 耗時(shí) : 38110 ms 平均每秒 : 2623983
  • JNI 字段讀取 (緩存Class=false ,緩存字段ID=true) 耗時(shí) : 55204 ms 平均每秒 : 1811462
  • JNI 字段讀取 (緩存Class=true ,緩存字段ID=true) 耗時(shí) : 4187 ms 平均每秒 : 23883448

根據(jù)上面的測(cè)試數(shù)據(jù)得知,查找 class 和 ID (屬性和方法 ID)消耗的時(shí)間比較大。只是讀取字段值的時(shí)間基本上跟上面的 JNI 空方法是一個(gè)數(shù)量級(jí)。而如果每次都根據(jù)名稱查找 class 和 field 的話,性能要下降高達(dá)40倍。讀取一個(gè)字段值的性能在百萬(wàn)級(jí)上,在交互頻繁的 JNI 應(yīng)用中是不能忍受的。 消耗時(shí)間最多的就是查找class,因此在 native 里保存 class 和 member id 是很有必要的。class 和 member id 在一定范圍內(nèi)是穩(wěn)定的,但在動(dòng)態(tài)加載的 class loader 下,保存全局的 class 要么可能失效,要么可能造成無(wú)法卸載classloader,在諸如 OSGI 框架下的 JNI 應(yīng)用還要特別注意這方面的問(wèn)題。在讀取字段值和查找 FieldID 上,JDK1.4 和 1.5、1.6 的差距是非常明顯的。但在最耗時(shí)的查找 class 上,三個(gè)版本沒(méi)有明顯差距。

通過(guò)上面的測(cè)試可以明顯的看出,在調(diào)用 JNI 接口獲取方法 ID、字段 ID 和 Class 引用時(shí),如果沒(méi)用使用緩存的話,性能低至 4 倍。所以在 JNI 開發(fā)中,合理的使用緩存技術(shù)能給程序提高極大的性能。緩存有兩種,分別為使用時(shí)緩存和類靜態(tài)初始化時(shí)緩存,區(qū)別主要在于緩存發(fā)生的時(shí)刻。

使用時(shí)緩存

字段 ID、方法 ID 和 Class 引用在函數(shù)當(dāng)中使用的同時(shí)就緩存起來(lái)。下面看一個(gè)示例:

package com.study.jnilearn;public class AccessCache {private String str = "Hello";public native void accessField(); // 訪問(wèn)str成員變量public native String newString(char[] chars, int len); // 根據(jù)字符數(shù)組和指定長(zhǎng)度創(chuàng)建String對(duì)象public static void main(String[] args) {AccessCache accessCache = new AccessCache();accessCache.nativeMethod();char chars[] = new char[7];chars[0] = '中';chars[1] = '華';chars[2] = '人';chars[3] = '民';chars[4] = '共';chars[5] = '和';chars[6] = '國(guó)';String str = accessCache.newString(chars, 6);System.out.println(str);}static {System.loadLibrary("AccessCache");} }

javah 生成的頭文件:com_study_jnilearn_AccessCache.h

/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_study_jnilearn_AccessCache */ #ifndef _Included_com_study_jnilearn_AccessCache #define _Included_com_study_jnilearn_AccessCache #ifdef __cplusplus extern "C" { #endif /** Class: com_study_jnilearn_AccessCache* Method: accessField* Signature: ()V*/ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessCache_accessField(JNIEnv *, jobject);/** Class: com_study_jnilearn_AccessCache* Method: newString* Signature: ([CI)Ljava/lang/String;*/ JNIEXPORT jstring JNICALL Java_com_study_jnilearn_AccessCache_newString(JNIEnv *, jobject, jcharArray, jint);#ifdef __cplusplus } #endif #endif

實(shí)現(xiàn)頭文件中的函數(shù):AccessCache.c

// AccessCache.c #include "com_study_jnilearn_AccessCache.h"JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessCache_accessField(JNIEnv *env, jobject obj) {// 第一次訪問(wèn)時(shí)將字段存到內(nèi)存數(shù)據(jù)區(qū),直到程序結(jié)束才會(huì)釋放,可以起到緩存的作用static jfieldID fid_str = NULL;jclass cls_AccessCache;jstring j_str;const char *c_str;cls_AccessCache = (*env)->GetObjectClass(env, obj); // 獲取該對(duì)象的Class引用if (cls_AccessCache == NULL) {return;}// 先判斷字段ID之前是否已經(jīng)緩存過(guò),如果已經(jīng)緩存過(guò)則不進(jìn)行查找if (fid_str == NULL) {fid_str = (*env)->GetFieldID(env,cls_AccessCache,"str","Ljava/lang/String;");// 再次判斷是否找到該類的str字段if (fid_str == NULL) {return;}}j_str = (*env)->GetObjectField(env, obj, fid_str); // 獲取字段的值c_str = (*env)->GetStringUTFChars(env, j_str, NULL);if (c_str == NULL) {return; // 內(nèi)存不夠}printf("In C:\n str = \"%s\"\n", c_str);(*env)->ReleaseStringUTFChars(env, j_str, c_str); // 釋放從從JVM新分配字符串的內(nèi)存空間// 修改字段的值j_str = (*env)->NewStringUTF(env, "12345");if (j_str == NULL) {return;}(*env)->SetObjectField(env, obj, fid_str, j_str);// 釋放本地引用(*env)->DeleteLocalRef(env,cls_AccessCache);(*env)->DeleteLocalRef(env,j_str); }JNIEXPORT jstring JNICALL Java_com_study_jnilearn_AccessCache_newString (JNIEnv *env, jobject obj, jcharArray j_char_arr, jint len) {jcharArray elemArray;jchar *chars = NULL;jstring j_str = NULL;static jclass cls_string = NULL;static jmethodID cid_string = NULL;// 注意:這里緩存局引用的做法是錯(cuò)誤,這里做為一個(gè)反面教材提醒大家,下面會(huì)說(shuō)到。if (cls_string == NULL) {cls_string = (*env)->FindClass(env, "java/lang/String");if (cls_string == NULL) {return NULL;}}// 緩存String的構(gòu)造方法IDif (cid_string == NULL) {cid_string = (*env)->GetMethodID(env, cls_string, "<init>", "([C)V");if (cid_string == NULL) {return NULL;}}printf("In C array Len: %d\n", len);// 創(chuàng)建一個(gè)字符數(shù)組elemArray = (*env)->NewCharArray(env, len);if (elemArray == NULL) {return NULL;}// 獲取數(shù)組的指針引用,注意:不能直接將jcharArray作為SetCharArrayRegion函數(shù)最后一個(gè)參數(shù)chars = (*env)->GetCharArrayElements(env, j_char_arr,NULL);if (chars == NULL) {return NULL;}// 將Java字符數(shù)組中的內(nèi)容復(fù)制指定長(zhǎng)度到新的字符數(shù)組中(*env)->SetCharArrayRegion(env, elemArray, 0, len, chars);// 調(diào)用String對(duì)象的構(gòu)造方法,創(chuàng)建一個(gè)指定字符數(shù)組為內(nèi)容的String對(duì)象j_str = (*env)->NewObject(env, cls_string, cid_string, elemArray);// 釋放本地引用(*env)->DeleteLocalRef(env, elemArray);return j_str; }

例1、在 Java_com_study_jnilearn_AccessCache_accessField 函數(shù)中定義了一個(gè)靜態(tài)變量fid_str用于存儲(chǔ)字段的 ID,每次調(diào)用函數(shù)的時(shí)候

static jfieldID fid_str = NULL;

在代碼段

// 先判斷字段ID之前是否已經(jīng)緩存過(guò),如果已經(jīng)緩存過(guò)則不進(jìn)行查找if (fid_str == NULL) {fid_str = (*env)->GetFieldID(env,cls_AccessCache,"str","Ljava/lang/String;");// 再次判斷是否找到該類的str字段if (fid_str == NULL) {return;}}

判斷字段 ID 是否已經(jīng)緩存,如果沒(méi)有先取出來(lái)存到fid_str中,下次再調(diào)用的時(shí)候該變量已經(jīng)有值了,不用再去JVM中獲取,起到了緩存的作用。

在 Java_com_study_jnilearn_AccessCache_newString 函數(shù)中定義了兩個(gè)變量cls_string和cid_string,分別用于存儲(chǔ) java.lang.String 類的 Class 引用和 String 的構(gòu)造方法 ID。在使用前會(huì)先判斷是否已經(jīng)緩存過(guò),如果沒(méi)有則調(diào)用 JNI 的接口從 JVM 中獲取 String 的 Class 引用和構(gòu)造方法 ID 存儲(chǔ)到靜態(tài)變量當(dāng)中。下次再調(diào)用該函數(shù)時(shí)就可以直接使用,不需要再去找一次了,也達(dá)到了緩存的效果,大家第一反映都會(huì)這么認(rèn)為。但是請(qǐng)注意:cls_string是一個(gè)局部引用,與方法和字段 ID 不一樣,局部引用在函數(shù)結(jié)束后會(huì)被 JVM 自動(dòng)釋放掉,這時(shí)cls_string成為了一個(gè)野針對(duì)(指向的內(nèi)存空間已被釋放,但變量的值仍然是被釋放后的內(nèi)存地址,不為 NULL),當(dāng)下次再調(diào)用 Java_com_xxxx_newString 這個(gè)函數(shù)的時(shí)候,會(huì)試圖訪問(wèn)一個(gè)無(wú)效的局部引用,從而導(dǎo)致非法的內(nèi)存訪問(wèn)造成程序崩潰。所以在函數(shù)內(nèi)用 static 緩存局部引用這種方式是錯(cuò)誤的。下篇文章會(huì)介紹局部引用和全局引用,利用全局引用來(lái)防止這種問(wèn)題,請(qǐng)關(guān)注。

類靜態(tài)初始化緩存

在調(diào)用一個(gè)類的方法或?qū)傩灾?#xff0c;Java 虛擬機(jī)會(huì)先檢查該類是否已經(jīng)加載到內(nèi)存當(dāng)中,如果沒(méi)有則會(huì)先加載,然后緊接著會(huì)調(diào)用該類的靜態(tài)初始化代碼塊,所以在靜態(tài)初始化該類的過(guò)程當(dāng)中計(jì)算并緩存該類當(dāng)中的字段 ID 和方法 ID 也是個(gè)不錯(cuò)的選擇。下面看一個(gè)示例:

package com.study.jnilearn;public class AccessCache {public static native void initIDs(); public native void nativeMethod();public void callback() {System.out.println("AccessCache.callback invoked!");}public static void main(String[] args) {AccessCache accessCache = new AccessCache();accessCache.nativeMethod();}static {System.loadLibrary("AccessCache");initIDs();} } /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_study_jnilearn_AccessCache */ #ifndef _Included_com_study_jnilearn_AccessCache #define _Included_com_study_jnilearn_AccessCache #ifdef __cplusplus extern "C" { #endif /** Class: com_study_jnilearn_AccessCache* Method: initIDs* Signature: ()V*/ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessCache_initIDs(JNIEnv *, jclass);/** Class: com_study_jnilearn_AccessCache* Method: nativeMethod* Signature: ()V*/ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessCache_nativeMethod(JNIEnv *, jobject);#ifdef __cplusplus } #endif #endif // AccessCache.c#include "com_study_jnilearn_AccessCache.h"jmethodID MID_AccessCache_callback;JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessCache_initIDs (JNIEnv *env, jclass cls) {printf("initIDs called!!!\n");MID_AccessCache_callback = (*env)->GetMethodID(env,cls,"callback","()V"); }JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessCache_nativeMethod (JNIEnv *env, jobject obj) {printf("In C Java_com_study_jnilearn_AccessCache_nativeMethod called!!!\n");(*env)->CallVoidMethod(env, obj, MID_AccessCache_callback); }

JVM 加載 AccessCache.class 到內(nèi)存當(dāng)中之后,會(huì)調(diào)用該類的靜態(tài)初始化代碼塊,即 static 代碼塊,先調(diào)用System.loadLibrary 加載動(dòng)態(tài)庫(kù)到 JVM 中,緊接著調(diào)用 native 方法 initIDs,會(huì)調(diào)用用到本地函數(shù)Java_com_study_jnilearn_AccessCache_initIDs,在該函數(shù)中獲取需要緩存的 ID,然后存入全局變量當(dāng)中。下次需要用到這些 ID 的時(shí)候,直接使用全局變量當(dāng)中的即可,調(diào)用 Java 的 callback 函數(shù)。

(*env)->CallVoidMethod(env, obj, MID_AccessCache_callback);

兩種緩存方式比較

如果在寫 JNI 接口時(shí),不能控制方法和字段所在類的源碼的話,用使用時(shí)緩存比較合理。但比起類靜態(tài)初始化時(shí)緩存來(lái)說(shuō),用使用時(shí)緩存有一些缺點(diǎn):

  • 使用前,每次都需要檢查是否已經(jīng)緩存該 ID 或 Class 引用
  • 如果在用使用時(shí)緩存的 ID,要注意只要本地代碼依賴于這個(gè) ID 的值,那么這個(gè)類就不會(huì)被 unload。另外一方面,如果緩存發(fā)生在靜態(tài)初始化時(shí),當(dāng)類被 unload 或 reload 時(shí),ID 會(huì)被重新計(jì)算。因?yàn)?#xff0c;盡量在類靜態(tài)初始化時(shí)就緩存字段 ID、方法 ID 和類的 Class 引用。

?

JNI 局部引用、全局引用和弱全局引用

這篇文章比較偏理論,詳細(xì)介紹了在編寫本地代碼時(shí)三種引用的使用場(chǎng)景和注意事項(xiàng)。可能看起來(lái)有點(diǎn)枯燥,但引用是在 JNI 中最容易出錯(cuò)的一個(gè)點(diǎn),如果使用不當(dāng),容易使程序造成內(nèi)存溢出,程序崩潰等現(xiàn)象。《Android JNI局部引用表溢出》這篇文章是一個(gè) JNI 引用使用不當(dāng)造成引用表溢出,最終導(dǎo)致程序崩潰的例子。建議看完這篇文章之后,再去看。

做 Java 的朋友都知道,在編碼的過(guò)程當(dāng)中,內(nèi)存管理這一塊完全是透明的。new 一個(gè)類的實(shí)例時(shí),只知道創(chuàng)建完這個(gè)類的實(shí)例之后,會(huì)返回這個(gè)實(shí)例的一個(gè)引用,然后就可以拿著這個(gè)引用訪問(wèn)它的所有數(shù)據(jù)成員了(屬性、方法)。完全不用管 JVM 內(nèi)部是怎么實(shí)現(xiàn)的,如何為新創(chuàng)建的對(duì)象來(lái)申請(qǐng)內(nèi)存,也不用管對(duì)象使用完之后內(nèi)存是怎么釋放的,只需知道有一個(gè)垃圾回器在幫忙管理這些事情就 OK 的了。有經(jīng)驗(yàn)的朋友也許知道啟動(dòng)一個(gè) Java 程序,如果沒(méi)有手動(dòng)創(chuàng)建其它線程,默認(rèn)會(huì)有兩個(gè)線程在跑,一個(gè)是 main 線程,另一個(gè)就是 GC 線程(負(fù)責(zé)將一些不再使用的對(duì)象回收)。如果你曾經(jīng)是做 Java 的然后轉(zhuǎn)去做 C++,會(huì)感覺(jué)很不習(xí)慣,在 C++ 中 new 一個(gè)對(duì)象,使用完了還要做一次 delete 操作,malloc 一次同樣也要調(diào)用 free 來(lái)釋放相應(yīng)的內(nèi)存,否則你的程序就會(huì)有內(nèi)存泄露了。而且在 C/C++ 中內(nèi)存還分棧空間和堆空間,其中局部變量、函數(shù)形參變量、for 中定義的臨時(shí)變量所分配的內(nèi)存空間都是存放在棧空間(而且還要注意大小的限制),用 new 和 malloc 申請(qǐng)的內(nèi)存都存放在堆空間。但 C/C++ 里的內(nèi)存管理還遠(yuǎn)遠(yuǎn)不止這些,這些只是最基礎(chǔ)的內(nèi)存管理常識(shí)。做 Java 的人聽(tīng)到這些肯定會(huì)偷樂(lè)了,咱寫 Java 的時(shí)候這些都不用管,全都交給 GC 就萬(wàn)事無(wú)優(yōu)了。手動(dòng)管理內(nèi)存雖然麻煩,而且需要特別細(xì)心,一不小心就有可能造成內(nèi)存泄露和野指針訪問(wèn)等程序致命的問(wèn)題,但凡事都有利弊,手動(dòng)申請(qǐng)和釋放內(nèi)存對(duì)程序的掌握比較靈活,不會(huì)受到平臺(tái)的限制。比如我們寫Android程序的時(shí)候,內(nèi)存使用就受Dalivk虛擬機(jī)的限制,從最初版本的16~24M,到后來(lái)的 32M 到 64M,可能隨著以后移動(dòng)設(shè)備物理內(nèi)存的不大擴(kuò)大,后面的 Android 版本內(nèi)存限制可能也會(huì)隨著提高。但在 C/C++ 這層,就完全不受虛擬機(jī)的限制了。比如要在 Android 中要存儲(chǔ)一張超高清的圖片,剛好這張圖片的大小超過(guò)了 Dalivk 虛擬機(jī)對(duì)每個(gè)應(yīng)用的內(nèi)存大小限制,Java 此時(shí)就顯得無(wú)能為力了,但在C/C++ 看來(lái)就是小菜一碟了,malloc(1024102450)。C/C++ 程序員得意的說(shuō)道,Java 不是說(shuō)是一門純面象對(duì)象的語(yǔ)言嗎,所以除了基本數(shù)據(jù)類型外,其它任何類型所創(chuàng)建的對(duì)象,JVM 所申請(qǐng)的內(nèi)存都存在堆空間。上面提高到了 GC,是負(fù)責(zé)回收不再使用的對(duì)象,它的全稱是 Garbage Collection,也就是所謂的垃圾回收。JVM 會(huì)在適當(dāng)?shù)臅r(shí)機(jī)觸發(fā) GC 操作,一旦進(jìn)行 GC 操作,就會(huì)將一些不再使用的對(duì)象進(jìn)行回收。那么哪些對(duì)象會(huì)被認(rèn)為是不再使用,并且可以被回收的呢?我們來(lái)看下面二張圖。(注:圖摘自博主郭霖的《Android 最佳性能實(shí)踐(二)——分析內(nèi)存的使用情況》)

上圖當(dāng)中,每個(gè)藍(lán)色的圓圈就代表一個(gè)內(nèi)存當(dāng)中的對(duì)象,而圓圈之間的箭頭就是它們的引用關(guān)系。這些對(duì)象有些是處于活動(dòng)狀態(tài)的,而有些就已經(jīng)不再被使用了。那么 GC 操作會(huì)從一個(gè)叫作 Roots 的對(duì)象開始檢查,所有它可以訪問(wèn)到的對(duì)象就說(shuō)明還在使用當(dāng)中,應(yīng)該進(jìn)行保留,而其它的對(duì)象就表示已經(jīng)不再被使用了,如下圖所示:

可以看到,目前所有黃色的對(duì)象都處于活動(dòng)狀態(tài),仍然會(huì)被系統(tǒng)繼續(xù)保留,而藍(lán)色的對(duì)象就會(huì)在 GC 操作當(dāng)中被系統(tǒng)回收掉了,這就是 JVM 執(zhí)行一次 GC 的簡(jiǎn)單流程。

上面說(shuō)的廢話好像有點(diǎn)多哈,下面進(jìn)入正題。通過(guò)上面的討論,大家都知道,如果一個(gè) Java 對(duì)象沒(méi)有被其它成員變量或靜態(tài)變量所引用的話,就隨時(shí)有可能會(huì)被 GC 回收掉。所以我們?cè)诰帉懕镜卮a時(shí),要注意從 JVM 中獲取到的引用在使用時(shí)被 GC 回收的可能性。由于本地代碼不能直接通過(guò)引用操作 JVM 內(nèi)部的數(shù)據(jù)結(jié)構(gòu),要進(jìn)行這些操作必須調(diào)用相應(yīng)的 JNI 接口來(lái)間接操作所引用的數(shù)據(jù)結(jié)構(gòu)。JNI 提供了和 Java 相對(duì)應(yīng)的引用類型,供本地代碼配合 JNI 接口間接操作 JVM 內(nèi)部的數(shù)據(jù)內(nèi)容使用。如:jobject、jstring、jclass、jarray、jintArray 等。因?yàn)槲覀冎煌ㄟ^(guò) JNI 接口操作 JNI 提供的引用類型數(shù)據(jù)結(jié)構(gòu),而且每個(gè) JVM 都實(shí)現(xiàn)了 JNI 規(guī)范相應(yīng)的接口,所以我們不必?fù)?dān)心特定 JVM 中對(duì)象的存儲(chǔ)方式和內(nèi)部數(shù)據(jù)結(jié)構(gòu)等信息,我們只需要學(xué)習(xí) JNI 中三種不同的引用即可。

由于 Java 程序運(yùn)行在虛擬機(jī)中的這個(gè)特點(diǎn),在 Java 中創(chuàng)建的對(duì)象、定義的變量和方法,內(nèi)部對(duì)象的數(shù)據(jù)結(jié)構(gòu)是怎么定義的,只有 JVM 自己知道。如果我們?cè)?C/C++ 中想要訪問(wèn) Java 中對(duì)象的屬性和方法時(shí),是不能夠直接操作 JVM 內(nèi)部 Java 對(duì)象的數(shù)據(jù)結(jié)構(gòu)的。想要在 C/C++ 中正確的訪問(wèn) Java 的數(shù)據(jù)結(jié)構(gòu),JVM 就必須有一套規(guī)則來(lái)約束 C/C++ 與 Java 互相訪問(wèn)的機(jī)制,所以才有了 JNI 規(guī)范,JNI 規(guī)范定義了一系列接口,任何實(shí)現(xiàn)了這套 JNI 接口的 Java 虛擬機(jī),C/C++ 就可以通過(guò)調(diào)用這一系列接口來(lái)間接的訪問(wèn) Java 中的數(shù)據(jù)結(jié)構(gòu)。比如前面文章中學(xué)習(xí)到的常用 JNI 接口有:GetStringUTFChars(從 Java 虛擬機(jī)中獲取一個(gè)字符串)、ReleaseStringUTFChars(釋放從 JVM 中獲取字符串所分配的內(nèi)存空間)、NewStringUTF、GetArrayLength、GetFieldID、GetMethodID、FindClass 等。

三種引用簡(jiǎn)介及區(qū)別

在 JNI 規(guī)范中定義了三種引用:局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)。區(qū)別如下:

局部引用

通過(guò) NewLocalRef 和各種 JNI 接口創(chuàng)建(FindClass、NewObject、GetObjectClass和NewCharArray等)。會(huì)阻止 GC 回收所引用的對(duì)象,不在本地函數(shù)中跨函數(shù)使用,不能跨線前使用。函數(shù)返回后局部引用所引用的對(duì)象會(huì)被JVM 自動(dòng)釋放,或調(diào)用 DeleteLocalRef 釋放。(*env)->DeleteLocalRef(env,local_ref)

jclass cls_string = (*env)->FindClass(env, "java/lang/String"); jcharArray charArr = (*env)->NewCharArray(env, len); jstring str_obj = (*env)->NewObject(env, cls_string, cid_string, elemArray); jstring str_obj_local_ref = (*env)->NewLocalRef(env,str_obj); // 通過(guò)NewLocalRef函數(shù)創(chuàng)建 ...

全局引用

調(diào)用 NewGlobalRef 基于局部引用創(chuàng)建,會(huì)阻 GC 回收所引用的對(duì)象。可以跨方法、跨線程使用。JVM 不會(huì)自動(dòng)釋放,必須調(diào)用 DeleteGlobalRef 手動(dòng)釋放。(*env)->DeleteGlobalRef(env,g_cls_string)

static jclass g_cls_string; void TestFunc(JNIEnv* env, jobject obj) {jclass cls_string = (*env)->FindClass(env, "java/lang/String");g_cls_string = (*env)->NewGlobalRef(env,cls_string); }

弱全局引用

調(diào)用 NewWeakGlobalRef 基于局部引用或全局引用創(chuàng)建,不會(huì)阻止 GC 回收所引用的對(duì)象,可以跨方法、跨線程使用。引用不會(huì)自動(dòng)釋放,在 JVM 認(rèn)為應(yīng)該回收它的時(shí)候(比如內(nèi)存緊張的時(shí)候)進(jìn)行回收而被釋放。或調(diào)用DeleteWeakGlobalRef 手動(dòng)釋放。(*env)->DeleteWeakGlobalRef(env,g_cls_string)

static jclass g_cls_string; void TestFunc(JNIEnv* env, jobject obj) {jclass cls_string = (*env)->FindClass(env, "java/lang/String");g_cls_string = (*env)->NewWeakGlobalRef(env,cls_string); }

局部引用

局部引用也稱本地引用,通常是在函數(shù)中創(chuàng)建并使用。會(huì)阻止 GC 回收所引用的對(duì)象。比如,調(diào)用 NewObject 接口創(chuàng)建一個(gè)新的對(duì)象實(shí)例并返回一個(gè)對(duì)這個(gè)對(duì)象的局部引用。局部引用只有在創(chuàng)建它的本地方法返回前有效,本地方法返回到 Java 層之后,如果 Java 層沒(méi)有對(duì)返回的局部引用使用的話,局部引用就會(huì)被 JVM 自動(dòng)釋放。你可能會(huì)為了提高程序的性能,在函數(shù)中將局部引用存儲(chǔ)在靜態(tài)變量中緩存起來(lái),供下次調(diào)用時(shí)使用。這種方式是錯(cuò)誤的,因?yàn)楹瘮?shù)返回后局部引很可能馬上就會(huì)被釋放掉,靜態(tài)變量中存儲(chǔ)的就是一個(gè)被釋放后的內(nèi)存地址,成了一個(gè)野針對(duì),下次再使用的時(shí)候就會(huì)造成非法地址的訪問(wèn),使程序崩潰。請(qǐng)看下面一個(gè)例子,錯(cuò)誤的緩存了 String 的 Class 引用。

/*錯(cuò)誤的局部引用*/ JNIEXPORT jstring JNICALL Java_com_study_jnilearn_AccessCache_newString (JNIEnv *env, jobject obj, jcharArray j_char_arr, jint len) {jcharArray elemArray;jchar *chars = NULL;jstring j_str = NULL;static jclass cls_string = NULL;static jmethodID cid_string = NULL;// 注意:錯(cuò)誤的引用緩存if (cls_string == NULL) {cls_string = (*env)->FindClass(env, "java/lang/String");if (cls_string == NULL) {return NULL;}}// 緩存String的構(gòu)造方法IDif (cid_string == NULL) {cid_string = (*env)->GetMethodID(env, cls_string, "<init>", "([C)V");if (cid_string == NULL) {return NULL;}}//省略額外的代碼.......elemArray = (*env)->NewCharArray(env, len);// ....j_str = (*env)->NewObject(env, cls_string, cid_string, elemArray);// 釋放局部引用(*env)->DeleteLocalRef(env, elemArray);return j_str; }

上面代碼中,我們省略了和我們討論無(wú)關(guān)的代碼。因?yàn)?FindClass 返回一個(gè)對(duì) java.lang.String 對(duì)象的局部引用,上面代碼中緩存 cls_string 做法是錯(cuò)誤的。假設(shè)一個(gè)本地方法 C.f 調(diào)用了 newString。

JNIEXPORT jstring JNICALLJava_C_f(JNIEnv *env, jobject this){char *c_str = ...;...return newString(c_str); }

Java_com_study_jnilearn_AccessCache_newString 下面簡(jiǎn)稱 newString。

C.f 方法返回后,JVM 會(huì)釋放在這個(gè)方法執(zhí)行期間創(chuàng)建的所有局部引用,也包含對(duì) String 的 Class 引用cls_string。當(dāng)再次調(diào)用 newString 時(shí),newString 所指向引用的內(nèi)存空間已經(jīng)被釋放,成為了一個(gè)野指針,再訪問(wèn)這個(gè)指針的引用時(shí),會(huì)導(dǎo)致因非法的內(nèi)存訪問(wèn)造成程序崩潰。

... ... = C.f(); // 第一次調(diào)是OK的 ... = C.f(); // 第二次調(diào)用時(shí),訪問(wèn)的是一個(gè)無(wú)效的引用. ...

釋放局部引用

釋放一個(gè)局部引用有兩種方式,一個(gè)是本地方法執(zhí)行完畢后 JVM 自動(dòng)釋放,另外一個(gè)是自己調(diào)用 DeleteLocalRef 手動(dòng)釋放。既然 JVM 會(huì)在函數(shù)返回后會(huì)自動(dòng)釋放所有局部引用,為什么還需要手動(dòng)釋放呢?大部分情況下,我們?cè)趯?shí)現(xiàn)一個(gè)本地方法時(shí)不必?fù)?dān)心局部引用的釋放問(wèn)題,函數(shù)被調(diào)用完成后,JVM 會(huì)自動(dòng)釋放函數(shù)中創(chuàng)建的所有局部引用。盡管如此,以下幾種情況下,為了避免內(nèi)存溢出,我們應(yīng)該手動(dòng)釋放局部引用。

JNI 會(huì)將創(chuàng)建的局部引用都存儲(chǔ)在一個(gè)局部引用表中,如果這個(gè)表超過(guò)了最大容量限制,就會(huì)造成局部引用表溢出,使程序崩潰。經(jīng)測(cè)試,Android 上的 JNI 局部引用表最大數(shù)量是 512 個(gè)。當(dāng)我們?cè)趯?shí)現(xiàn)一個(gè)本地方法時(shí),可能需要?jiǎng)?chuàng)建大量的局部引用,如果沒(méi)有及時(shí)釋放,就有可能導(dǎo)致 JNI 局部引用表的溢出,所以,在不需要局部引用時(shí)就立即調(diào)用 DeleteLocalRef 手動(dòng)刪除。比如,在下面的代碼中,本地代碼遍歷一個(gè)特別大的字符串?dāng)?shù)組,每遍歷一個(gè)元素,都會(huì)創(chuàng)建一個(gè)局部引用,當(dāng)對(duì)使用完這個(gè)元素的局部引用時(shí),就應(yīng)該馬上手動(dòng)釋放它。

for (i = 0; i < len; i++) {jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);... /* 使用jstr */(*env)->DeleteLocalRef(env, jstr); // 使用完成之后馬上釋放 }

在編寫 JNI 工具函數(shù)時(shí),工具函數(shù)在程序當(dāng)中是公用的,被誰(shuí)調(diào)用你是不知道的。上面 newString 這個(gè)函數(shù)演示了怎么樣在工具函數(shù)中使用完局部引用后,調(diào)用 DeleteLocalRef 刪除。不這樣做的話,每次調(diào)用 newString 之后,都會(huì)遺留兩個(gè)引用占用空間(elemArray和cls_string,cls_string 不用 static 緩存的情況下)。

如果你的本地函數(shù)不會(huì)返回。比如一個(gè)接收消息的函數(shù),里面有一個(gè)死循環(huán),用于等待別人發(fā)送消息過(guò)來(lái)while(true) { if (有新的消息) { 處理之。。。。} else { 等待新的消息。。。}}。如果在消息循環(huán)當(dāng)中創(chuàng)建的引用你不顯示刪除,很快將會(huì)造成 JVM 局部引用表溢出。

局部引用會(huì)阻止所引用的對(duì)象被 GC 回收。比如你寫的一個(gè)本地函數(shù)中剛開始需要訪問(wèn)一個(gè)大對(duì)象,因此一開始就創(chuàng)建了一個(gè)對(duì)這個(gè)對(duì)象的引用,但在函數(shù)返回前會(huì)有一個(gè)大量的非常復(fù)雜的計(jì)算過(guò)程,而在這個(gè)計(jì)算過(guò)程當(dāng)中是不需要前面創(chuàng)建的那個(gè)大對(duì)象的引用的。但是,在計(jì)算的過(guò)程當(dāng)中,如果這個(gè)大對(duì)象的引用還沒(méi)有被釋放的話,會(huì)阻止 GC 回收這個(gè)對(duì)象,內(nèi)存一直占用者,造成資源的浪費(fèi)。所以這種情況下,在進(jìn)行復(fù)雜計(jì)算之前就應(yīng)該把引用給釋放了,以免不必要的資源浪費(fèi)。

/* 假如這是一個(gè)本地方法實(shí)現(xiàn) */ JNIEXPORT void JNICALL Java_pkg_Cls_func(JNIEnv *env, jobject this) {lref = ... /* lref引用的是一個(gè)大的Java對(duì)象 */... /* 在這里已經(jīng)處理完業(yè)務(wù)邏輯后,這個(gè)對(duì)象已經(jīng)使用完了 */(*env)->DeleteLocalRef(env, lref); /* 及時(shí)刪除這個(gè)對(duì)這個(gè)大對(duì)象的引用,GC就可以對(duì)它回收,并釋放相應(yīng)的資源*/lengthyComputation(); /* 在里有個(gè)比較耗時(shí)的計(jì)算過(guò)程 */return; /* 計(jì)算完成之后,函數(shù)返回之前所有引用都已經(jīng)釋放 */ }

管理局部引用

JNI 提供了一系列函數(shù)來(lái)管理局部引用的生命周期。這些函數(shù)包括:EnsureLocalCapacity、NewLocalRef、PushLocalFrame、PopLocalFrame、DeleteLocalRef。JNI 規(guī)范指出,任何實(shí)現(xiàn) JNI 規(guī)范的 JVM,必須確保每個(gè)本地函數(shù)至少可以創(chuàng)建 16 個(gè)局部引用(可以理解為虛擬機(jī)默認(rèn)支持創(chuàng)建 16 個(gè)局部引用)。實(shí)際經(jīng)驗(yàn)表明,這個(gè)數(shù)量已經(jīng)滿足大多數(shù)不需要和 JVM 中內(nèi)部對(duì)象有太多交互的本地方函數(shù)。如果需要?jiǎng)?chuàng)建更多的引用,可以通過(guò)調(diào)用 EnsureLocalCapacity 函數(shù),確保在當(dāng)前線程中創(chuàng)建指定數(shù)量的局部引用,如果創(chuàng)建成功則返回 0,否則創(chuàng)建失敗,并拋出 OutOfMemoryError 異常。EnsureLocalCapacity 這個(gè)函數(shù)是 1.2 以上版本才提供的,為了向下兼容,在編譯的時(shí)候,如果申請(qǐng)創(chuàng)建的局部引用超過(guò)了本地引用的最大容量,在運(yùn)行時(shí) JVM 會(huì)調(diào)用 FatalError 函數(shù)使程序強(qiáng)制退出。在開發(fā)過(guò)程當(dāng)中,可以為 JVM 添加-verbose:jni參數(shù),在編譯的時(shí)如果發(fā)現(xiàn)本地代碼在試圖申請(qǐng)過(guò)多的引用時(shí),會(huì)打印警告信息提示我們要注意。在下面的代碼中,遍歷數(shù)組時(shí)會(huì)獲取每個(gè)元素的引用,使用完了之后不手動(dòng)刪除,不考慮內(nèi)存因素的情況下,它可以為這種創(chuàng)建大量的局部引用提供足夠的空間。由于沒(méi)有及時(shí)刪除局部引用,因此在函數(shù)執(zhí)行期間,會(huì)消耗更多的內(nèi)存。

/*處理函數(shù)邏輯時(shí),確保函數(shù)能創(chuàng)建len個(gè)局部引用*/ if((*env)->EnsureLocalCapacity(env,len) != 0) {... /*申請(qǐng)len個(gè)局部引用的內(nèi)存空間失敗 OutOfMemoryError*/return; } for(i=0; i < len; i++) {jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);// ... 使用jstr字符串/*這里沒(méi)有刪除在for中臨時(shí)創(chuàng)建的局部引用*/ }

另外,除了 EnsureLocalCapacity 函數(shù)可以擴(kuò)充指定容量的局部引用數(shù)量外,我們也可以利用 Push/PopLocalFrame 函數(shù)對(duì)創(chuàng)建作用范圍層層嵌套的局部引用。例如,我們把上面那段處理字符串?dāng)?shù)組的代碼用 Push/PopLocalFrame 函數(shù)對(duì)重寫。

#define N_REFS ... /*最大局部引用數(shù)量*/ for (i = 0; i < len; i++) {if ((*env)->PushLocalFrame(env, N_REFS) != 0) {... /*內(nèi)存溢出*/}jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);... /* 使用jstr */(*env)->PopLocalFrame(env, NULL); }

PushLocalFrame 為當(dāng)前函數(shù)中需要用到的局部引用創(chuàng)建了一個(gè)引用堆棧,(如果之前調(diào)用 PushLocalFrame 已經(jīng)創(chuàng)建了 Frame,在當(dāng)前的本地引用棧中仍然是有效的)每遍歷一次調(diào)用(*env)->GetObjectArrayElement(env, arr, i);返回一個(gè)局部引用時(shí),JVM 會(huì)自動(dòng)將該引用壓入當(dāng)前局部引用棧中。而 PopLocalFrame 負(fù)責(zé)銷毀棧中所有的引用。這樣一來(lái),Push/PopLocalFrame 函數(shù)對(duì)提供了對(duì)局部引用生命周期更方便的管理,而不需要時(shí)刻關(guān)注獲取一個(gè)引用后,再調(diào)用 DeleteLocalRef 來(lái)釋放引用。在上面的例子中,如果在處理 jstr 的過(guò)程當(dāng)中又創(chuàng)建了局部引用,則 PopLocalFrame 執(zhí)行時(shí),這些局部引用將全都會(huì)被銷毀。在調(diào)用 PopLocalFrame 銷毀當(dāng)前 frame 中的所有引用前,如果第二個(gè)參數(shù) result 不為空,會(huì)由 result 生成一個(gè)新的局部引用,再把這個(gè)新生成的局部引用存儲(chǔ)在上一個(gè) frame 中。請(qǐng)看下面的示例。

// 函數(shù)原型 jobject (JNICALL *PopLocalFrame)(JNIEnv *env, jobject result);jstring other_jstr; for (i = 0; i < len; i++) {if ((*env)->PushLocalFrame(env, N_REFS) != 0) {... /*內(nèi)存溢出*/}jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);... /* 使用jstr */if (i == 2) {other_jstr = jstr;}other_jstr = (*env)->PopLocalFrame(env, other_jstr); // 銷毀局部引用棧前返回指定的引用 }

還要注意的一個(gè)問(wèn)題是,局部引用不能跨線程使用,只在創(chuàng)建它的線程有效。不要試圖在一個(gè)線程中創(chuàng)建局部引用并存儲(chǔ)到全局引用中,然后在另外一個(gè)線程中使用。

全局引用

全局引用可以跨方法、跨線程使用,直到它被手動(dòng)釋放才會(huì)失效。同局部引用一樣,也會(huì)阻止它所引用的對(duì)象被 GC 回收。與局部引用創(chuàng)建方式不同的是,只能通過(guò) NewGlobalRef 函數(shù)創(chuàng)建。下面這個(gè)版本的 newString 演示怎么樣使用一個(gè)全局引用。

JNIEXPORT jstring JNICALL Java_com_study_jnilearn_AccessCache_newString (JNIEnv *env, jobject obj, jcharArray j_char_arr, jint len) {// ...jstring jstr = NULL;static jclass cls_string = NULL;if (cls_string == NULL) {jclass local_cls_string = (*env)->FindClass(env, "java/lang/String");if (cls_string == NULL) {return NULL;}// 將java.lang.String類的Class引用緩存到全局引用當(dāng)中cls_string = (*env)->NewGlobalRef(env, local_cls_string);// 刪除局部引用(*env)->DeleteLocalRef(env, local_cls_string);// 再次驗(yàn)證全局引用是否創(chuàng)建成功if (cls_string == NULL) {return NULL;}}// ....return jstr; }

弱全局引用

弱全局引用使用 NewGlobalWeakRef 創(chuàng)建,使用 DeleteGlobalWeakRef 釋放。下面簡(jiǎn)稱弱引用。與全局引用類似,弱引用可以跨方法、線程使用。但與全局引用很重要不同的一點(diǎn)是,弱引用不會(huì)阻止 GC 回收它引用的對(duì)象。在newString 這個(gè)函數(shù)中,我們也可以使用弱引用來(lái)存儲(chǔ) String 的 Class 引用,因?yàn)?java.lang.String 這個(gè)類是系統(tǒng)類,永遠(yuǎn)不會(huì)被 GC 回收。當(dāng)本地代碼中緩存的引用不一定要阻止 GC 回收它所指向的對(duì)象時(shí),弱引用就是一個(gè)最好的選擇。假設(shè),一個(gè)本地方法mypkg.MyCls.f需要緩存一個(gè)指向類mypkg.MyCls2的引用,如果在弱引用中緩存的話,仍然允許mypkg.MyCls2這個(gè)類被 unload,因?yàn)槿跻貌粫?huì)阻止 GC 回收所引用的對(duì)象。請(qǐng)看下面的代碼段。

JNIEXPORT void JNICALL Java_mypkg_MyCls_f(JNIEnv *env, jobject self) {static jclass myCls2 = NULL;if (myCls2 == NULL){jclass myCls2Local = (*env)->FindClass(env, "mypkg/MyCls2");if (myCls2Local == NULL){return; /* 沒(méi)有找到mypkg/MyCls2這個(gè)類 */}myCls2 = NewWeakGlobalRef(env, myCls2Local);if (myCls2 == NULL){return; /* 內(nèi)存溢出 */}}... /* 使用myCls2的引用 */ }

我們假設(shè) MyCls 和 MyCls2 有相同的生命周期(例如,他們可能被相同的類加載器加載),因?yàn)槿跻玫拇嬖?#xff0c;我們不必?fù)?dān)心 MyCls 和它所在的本地代碼在被使用時(shí),MyCls2 這個(gè)類出現(xiàn)先被 unload,后來(lái)又會(huì) preload 的情況。當(dāng)然,如果真的發(fā)生這種情況時(shí)(MyCls 和 MyCls2 此時(shí)的生命周期不同),我們?cè)谑褂萌跻脮r(shí),必須先檢查緩存過(guò)的弱引用是指向活動(dòng)的類對(duì)象,還是指向一個(gè)已經(jīng)被 GC 給 unload 的類對(duì)象。下面馬上告訴你怎樣檢查弱引用是否活動(dòng),即引用的比較。

引用比較

給定兩個(gè)引用(不管是全局、局部還是弱全局引用),我們只需要調(diào)用 IsSameObject 來(lái)判斷它們兩個(gè)是否指向相同的對(duì)象。例如:(*env)->IsSameObject(env, obj1, obj2),如果 obj1 和 obj2 指向相同的對(duì)象,則返回 JNI_TRUE(或者 1),否則返回 JNI_FALSE(或者 0)。有一個(gè)特殊的引用需要注意:NULL,JNI 中的 NULL 引用指向 JVM 中的 null 對(duì)象。如果 obj 是一個(gè)局部或全局引用,使用(*env)->IsSameObject(env, obj, NULL)?或者obj == NULL?來(lái)判斷 obj 是否指向一個(gè) null 對(duì)象即可。但需要注意的是,IsSameObject 用于弱全局引用與 NULL 比較時(shí),返回值的意義是不同于局部引用和全局引用的。

jobject local_obj_ref = (*env)->NewObject(env, xxx_cls,xxx_mid); jobject g_obj_ref = (*env)->NewWeakGlobalRef(env, local_ref); // ... 業(yè)務(wù)邏輯處理 jboolean isEqual = (*env)->IsSameObject(env, g_obj_ref, NULL);

在上面的 IsSameObject 調(diào)用中,如果 g_obj_ref 指向的引用已經(jīng)被回收,會(huì)返回 JNI_TRUE,如果 wobj 仍然指向一個(gè)活動(dòng)對(duì)象,會(huì)返回 JNI_FALSE。

釋放全局引用

每一個(gè) JNI 引用被建立時(shí),除了它所指向的 JVM 中對(duì)象的引用需要占用一定的內(nèi)存空間外,引用本身也會(huì)消耗掉一個(gè)數(shù)量的內(nèi)存空間。作為一個(gè)優(yōu)秀的程序員,我們應(yīng)該對(duì)程序在一個(gè)給定的時(shí)間段內(nèi)使用的引用數(shù)量要十分小心。短時(shí)間內(nèi)創(chuàng)建大量而沒(méi)有被立即回收的引用很可能就會(huì)導(dǎo)致內(nèi)存溢出。 ??? 當(dāng)我們的本地代碼不再需要一個(gè)全局引用時(shí),應(yīng)該馬上調(diào)用 DeleteGlobalRef 來(lái)釋放它。如果不手動(dòng)調(diào)用這個(gè)函數(shù),即使這個(gè)對(duì)象已經(jīng)沒(méi)用了,JVM 也不會(huì)回收這個(gè)全局引用所指向的對(duì)象。 ???? 同樣,當(dāng)我們的本地代碼不再需要一個(gè)弱全局引用時(shí),也應(yīng)該調(diào)用 DeleteWeakGlobalRef 來(lái)釋放它,如果不手動(dòng)調(diào)用這個(gè)函數(shù)來(lái)釋放所指向的對(duì)象,JVM 仍會(huì)回收弱引用所指向的對(duì)象,但弱引用本身在引用表中所占的內(nèi)存永遠(yuǎn)也不會(huì)被回收。

管理引用的規(guī)則

前面對(duì)三種引用已做了一個(gè)全面的介紹,下面來(lái)總結(jié)一下引用的管理規(guī)則和使用時(shí)的一些注意事項(xiàng),使用好引用的目的就是為了減少內(nèi)存使用和對(duì)象被引用保持而不能釋放,造成內(nèi)存浪費(fèi)。所以在開發(fā)當(dāng)中要特別小心!

通常情況下,有兩種本地代碼使用引用時(shí)要注意:

  • 直接實(shí)現(xiàn)Java層聲明的native函數(shù)的本地代碼 當(dāng)編寫這類本地代碼時(shí),要當(dāng)心不要造成全局引用和弱引用的累加,因?yàn)楸镜胤椒▓?zhí)行完畢后,這兩種引用不會(huì)被自動(dòng)釋放。
  • 被用在任何環(huán)境下的工具函數(shù)。例如:方法調(diào)用、屬性訪問(wèn)和異常處理的工具函數(shù)等。

編寫工具函數(shù)的本地代碼時(shí),要當(dāng)心不要在函數(shù)的調(diào)用軌跡上遺漏任何的局部引用,因?yàn)楣ぞ吆瘮?shù)被調(diào)用的場(chǎng)合和次數(shù)是不確定的,一量被大量調(diào)用,就很有可能造成內(nèi)存溢出。所以在編寫工具函數(shù)時(shí),請(qǐng)遵守下面的規(guī)則:

  • 一個(gè)返回值為基本類型的工具函數(shù)被調(diào)用時(shí),它決不能造成局部、全局、弱全局引用被回收的累加。
  • 當(dāng)一個(gè)返回值為引用類型的工具函數(shù)被調(diào)用時(shí),它除了返回的引用以外,它決不能造成其它局部、全局、弱引用的累加。

對(duì)于工具函數(shù)來(lái)說(shuō),為了使用緩存技術(shù)而創(chuàng)建一些全局引用或者弱全局引用是正常的。如果一個(gè)工具函數(shù)返回的是一個(gè)引用,我們應(yīng)該寫好注釋詳細(xì)說(shuō)明返回引用的類型,以便于使用者更好的管理它們。下面的代碼中,頻繁地調(diào)用工具函數(shù) GetInfoString,我們需要知道 GetInfoString 返回引用的類型是什么,以便于每次使用完成后調(diào)用相應(yīng)的 JNI 函數(shù)來(lái)釋放掉它。

while (JNI_TRUE) {jstring infoString = GetInfoString(info);... /* 處理infoString */??? /* 使用完成之后,調(diào)用DeleteLocalRef、DeleteGlobalRef、DeleteWeakGlobalRef哪一個(gè)函數(shù)來(lái)釋放這個(gè)引用呢?*/ }

函數(shù) NewLocalRef 有時(shí)被用來(lái)確保一個(gè)工具函數(shù)返回一個(gè)局部引用。我們改造一下 newString 這個(gè)函數(shù),演示一下這個(gè)函數(shù)的用法。下面的 newString 是把一個(gè)被頻繁調(diào)用的字符串“CommonString”緩存在了全局引用里。

JNIEXPORT jstring JNICALL Java_com_study_jnilearn_AccessCache_newString {static jstring result;/* 使用wstrncmp函數(shù)比較兩個(gè)Unicode字符串 */if (wstrncmp("CommonString", chars, len) == 0){/* 將"CommonString"這個(gè)字符串緩存到全局引用中 */static jstring cachedString = NULL;if (cachedString == NULL){/* 先創(chuàng)建"CommonString"這個(gè)字符串 */jstring cachedStringLocal = ...;/* 然后將這個(gè)字符串緩存到全局引用中 */cachedString = (*env)->NewGlobalRef(env, cachedStringLocal);}// 基于全局引用創(chuàng)建一個(gè)局引用返回,也同樣會(huì)阻止GC回收所引用的這個(gè)對(duì)象,因?yàn)樗鼈冎赶虻氖峭粋€(gè)對(duì)象return (*env)->NewLocalRef(env, cachedString); }... return result; }

在管理局部引用的生命周期中,Push/PopLocalFrame 是非常方便且安全的。我們可以在本地函數(shù)的入口處調(diào)用PushLocalFrame,然后在出口處調(diào)用 PopLocalFrame,這樣的話,在函數(shù)內(nèi)任何位置創(chuàng)建的局部引用都會(huì)被釋放。而且,這兩個(gè)函數(shù)是非常高效的,強(qiáng)烈建議使用它們。需要注意的是,如果在函數(shù)的入口處調(diào)用了PushLocalFrame,記住要在函數(shù)所有出口(有 return 語(yǔ)句出現(xiàn)的地方)都要調(diào)用 PopLocalFrame。在下面的代碼中,對(duì) PushLocalFrame 的調(diào)用只有一次,但調(diào)用 PopLocalFrame 確有多次,當(dāng)然你也可以使用 goto 語(yǔ)句來(lái)統(tǒng)一處理。

jobject f(JNIEnv *env, ...) {jobject result;if ((*env)->PushLocalFrame(env, 10) < 0){/* 調(diào)用PushLocalFrame獲取10個(gè)局部引用失敗,不需要調(diào)用PopLocalFrame */return NULL;}...result = ...; // 創(chuàng)建局部引用resultif (...){/* 返回前先彈出棧頂?shù)膄rame */result = (*env)->PopLocalFrame(env, result);return result;}...result = (*env)->PopLocalFrame(env, result);/* 正常返回 */return result; }

上面的代碼同樣演示了函數(shù) PopLocalFrame 的第二個(gè)參數(shù)的用法,局部引用 result 一開始在 PushLocalFrame 創(chuàng)建在當(dāng)前 frame 里面,而把 result 傳入 PopLocalFrame 中時(shí),PopLocalFrame 在彈出當(dāng)前的 frame 前,會(huì)由 result 生成一個(gè)新的局部引用,再將這個(gè)新生成的局部引用存儲(chǔ)在上一個(gè) frame 當(dāng)中。

?

編譯C文件:

gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC -shared AccessSuperMethod.c -o libAccessSuperMethod.so

https://blog.csdn.net/xyang81/article/details/45770551

https://blog.csdn.net/xyang81/article/details/44873769

轉(zhuǎn)載于:https://www.cnblogs.com/EMH899/p/10800644.html

總結(jié)

以上是生活随笔為你收集整理的jni2的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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

国产一区免费看 | 国产视频精品免费播放 | 亚洲欧洲久久久 | 狠狠色丁香婷婷综合久小说久 | 中文字幕一区二区三区在线播放 | 中文字幕中文字幕在线一区 | 日韩欧美高清不卡 | 四虎永久网站 | 久久在草 | 亚洲精品视频第一页 | 人人操日日干 | 夜夜骑日日 | 久草免费在线 | 97成人精品视频在线观看 | 啪啪精品 | 久久久69| 国产日韩三级 | 国产又粗又长又硬免费视频 | 日韩电影中文字幕在线 | 亚洲精品一区二区18漫画 | 日韩视频在线不卡 | 中文国产字幕 | 在线视频专区 | 国产精品网站一区二区三区 | 曰本三级在线 | 婷婷五天天在线视频 | 久久亚洲人 | 人人超碰人人 | 又色又爽又激情的59视频 | 亚洲综合少妇 | 久久黄色美女 | 日韩精品免费一区二区在线观看 | 亚洲草视频 | 欧美日韩一区二区在线观看 | 黄色天堂在线观看 | 97理论电影| 在线三级av | 精品999在线观看 | 中文在线www | 日韩字幕| 在线观看va | 中文字幕电影在线 | 精品视频免费久久久看 | 久久成人高清 | 免费a视频 | 国产群p | 青青草国产精品 | 天天插伊人 | av免费看在线 | 久久久久伊人 | 欧美日韩中字 | 国内视频在线观看 | 天天搞天天 | 天天操天天插 | 久久66热这里只有精品 | 欧美日韩在线视频一区 | 人人爽影院 | 91网址在线观看 | 国产在线视频资源 | 久久99在线观看 | 国产aaa大片 | 在线免费精品视频 | 精品久久久久一区二区国产 | 永久免费的啪啪网站免费观看浪潮 | 国产 日韩 在线 亚洲 字幕 中文 | 在线视频欧美日韩 | 婷婷激情五月 | 美女av免费看 | 美女网站视频久久 | 99精品视频一区 | 久久久国产一区二区三区四区小说 | 麻豆久久久久 | 草在线 | 欧美精品久久久久久久 | 成人影片在线免费观看 | 国内少妇自拍视频一区 | 国产精品久久久久久久电影 | 国产高清视频在线播放一区 | 久久一区精品 | 色99网| 蜜臀av一区二区 | 丁香六月天婷婷 | 色综合在| 成人免费在线观看入口 | 在线免费观看成人 | 亚洲影视九九影院在线观看 | 国产一级片久久 | 久久经典国产视频 | 视频国产在线观看18 | 中文字幕人成不卡一区 | 在线观看国产高清视频 | 中文字幕资源网 国产 | 69久久99精品久久久久婷婷 | 亚洲日韩精品欧美一区二区 | 丁香六月久久综合狠狠色 | 夜色资源站国产www在线视频 | 一性一交视频 | 国产经典av| 91超国产 | www.国产高清| 黄色精品免费 | 成片人卡1卡2卡3手机免费看 | 久久综合精品国产一区二区三区 | 啪啪凸凸 | 人人艹视频 | 鲁一鲁影院 | av在线免费观看不卡 | 国产黄色特级片 | 日韩在线理论 | 中文字幕久久久精品 | 91av手机在线观看 | 婷婷5月激情5月 | 中文字幕免费高 | 日本丰满少妇免费一区 | 91爱爱视频 | 99草视频 | 又黄又爽又刺激的视频 | 久久国产精品视频免费看 | 久久综合久久久 | 黄色成年网站 | 精品国产免费一区二区三区五区 | 久久久久国产精品免费 | 国产高清视频在线播放一区 | 久久99欧美| 国产精品久久久久久久久久久杏吧 | 色综合www | 亚洲第一中文字幕 | www.成人精品 | 日韩av一区二区三区四区 | 一区二区三区日韩在线观看 | 国产在线自| 日日夜夜天天综合 | 亚洲第一久久久 | 97超级碰碰碰视频在线观看 | 91大神精品视频在线观看 | 午夜视频久久久 | 成人免费视频播放 | 在线观看mv的中文字幕网站 | 国产成人高清 | 亚洲乱码精品久久久 | 国产精品网站 | a黄在线观看 | www.精选视频.com | 久久天天拍| 国产色资源 | 高清av在线| 国产在线精品二区 | 草樱av | 欧美a级一区二区 | 欧美精品v国产精品 | 色九九在线 | 国产高清精 | 四虎在线免费观看 | 欧美日韩高清一区二区 | 亚洲人成影院在线 | 91精品免费看 | 丝袜少妇在线 | 日韩欧美在线观看一区二区 | 久久 地址 | 国产成人精品a | 久草在线观看资源 | 五月婷久| 揉bbb玩bbb少妇bbb | 免费在线a | 亚洲91中文字幕无线码三区 | 毛片a级片 | 天天干天天搞天天射 | 在线91播放| 亚洲最大成人免费网站 | 国产精品不卡在线观看 | 亚洲国产精品传媒在线观看 | 综合激情网... | 国产视频一区二区在线播放 | 欧美成人a在线 | 97免费视频在线 | 婷婷婷国产在线视频 | 国产美女黄网站免费 | 久久九九久久 | 亚洲黄色免费在线 | 国产精品一区二区吃奶在线观看 | 99热国内精品 | 国产精品a成v人在线播放 | 六月丁香综合 | 国产成人一区二区三区在线观看 | 欧美视频在线观看免费网址 | 免费能看的av | 日韩黄色中文字幕 | 91精品伦理 | 香蕉视频国产在线 | 午夜神马福利 | 综合色狠狠 | 久久久久国产精品一区 | 亚洲涩涩网站 | 亚洲人人精品 | 又黄又刺激的视频 | 欧美成亚洲 | 欧美少妇的秘密 | 九九九在线观看视频 | jizzjizzjizz亚洲 | 六月丁香在线观看 | 婷婷丁香久久五月婷婷 | 西西444www大胆无视频 | 人人澡人人添人人爽一区二区 | 久久久精品电影 | 亚洲国产高清视频 | 六月丁香婷婷网 | 九九视频网站 | 国产精品久久久久久久久久直播 | 亚洲人人av| 欧美一区二区免费在线观看 | 国产精品视频久久 | 一区 二区电影免费在线观看 | 国产福利在线免费观看 | 亚洲精品视频在线观看免费视频 | 成人免费看电影 | 91成人精品视频 | 国产成人精品av在线观 | 毛片精品免费在线观看 | 亚洲国产中文字幕 | 欧美巨乳网 | 天天操夜夜操国产精品 | 日韩电影一区二区在线观看 | 精品国精品自拍自在线 | 精品久久久久久久久久久久 | 亚洲经典在线 | 婷婷激情久久 | 五月婷婷av在线 | 免费在线国产视频 | 日本韩国在线不卡 | 视频二区在线 | 精品亚洲视频在线 | 中国一级片在线播放 | av成人免费在线观看 | 天天干天天插伊人网 | 亚洲精品自在在线观看 | 四虎国产精品成人免费影视 | a级国产乱理论片在线观看 伊人宗合网 | 国产无吗一区二区三区在线欢 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 国产精品一区二区三区久久久 | 人人精久 | 国产成人精品av久久 | 久久av网址 | 欧美精品一区二区在线播放 | 中文字幕久久精品 | 国产高清精品在线观看 | 99久久精品电影 | av蜜桃在线 | 日批视频 | 日日爽视频 | 精品国内自产拍在线观看视频 | 成人免费观看网址 | 天天躁天天狠天天透 | 69av视频在线 | 日韩在线电影 | www久草 | 麻豆系列在线观看 | 国产一级精品视频 | 国产精品九九视频 | 国产又黄又硬又爽 | 亚洲婷婷在线视频 | 四虎国产永久在线精品 | 午夜精品电影一区二区在线 | 亚洲日韩精品欧美一区二区 | 婷婷久草 | 超碰97网站 | 少妇精69xxtheporn | 99精品在线观看视频 | 久久一区91| 在线观看网站av | 日韩在线视频网站 | 欧美久久久久久久久 | 天天干天天操天天搞 | 成人avav| 五月天.com | 亚洲一区二区天堂 | www.狠狠插.com | 97人人添人澡人人爽超碰动图 | 国产精品久久久久久久久久久不卡 | 五月婷婷久久综合 | 热久久这里只有精品 | 中文字幕免费成人 | 久免费视频 | 久久久亚洲精华液 | 精品国产乱码久久久久 | 怡红院av| 日韩理论片 | 在线国产91| 成人动漫精品一区二区 | 91国内在线 | 亚洲一区网 | 超碰精品在线 | 天天色天天操综合网 | 亚洲一区日韩 | 精品国产一区二区三区在线 | 日韩在线免费不卡 | 国产精品国产三级国产aⅴ无密码 | 欧美精彩视频 | 日日干夜夜干 | 超碰av在线播放 | 手机色站 | 色妞色视频一区二区三区四区 | 国产一区二区三区四区在线 | 日韩欧美电影在线观看 | 免费午夜视频在线观看 | 精品国产一区二区三区久久久久久 | 日韩免费高清 | 香蕉视频网站在线观看 | 人人射人人射 | 久久国色夜色精品国产 | 成人小视频在线 | 在线视频手机国产 | 91在线观 | 久久国产精品一二三区 | 日本成人a | 欧美二区在线播放 | av成人黄色| 五月激情丁香婷婷 | 久久国产精品99久久久久久老狼 | 2019天天干天天色 | 国产不卡精品 | 99热这里精品 | 日韩午夜在线播放 | 国产成人在线免费观看 | 国产九九热视频 | av在线播放中文字幕 | 最新高清无码专区 | 国产精品一二三 | 一区 二区 精品 | 99精品视频免费观看视频 | 五月天天av| 91av视频播放 | 91看片淫黄大片在线播放 | 9i看片成人免费看片 | 久久久免费少妇 | www91在线 | 亚洲欧美国产精品久久久久 | 丁香在线观看完整电影视频 | 国产xxxx做受性欧美88 | 欧美国产日韩一区 | 美女视频a美女大全免费下载蜜臀 | 99精品视频免费全部在线 | 永久免费av在线播放 | 五月黄色 | 天天干 天天摸 天天操 | 久久久精品欧美 | 国产一级黄 | 精品在线观 | 91手机电视 | 亚洲精品国产精品国产 | 国产大片黄色 | 99视频这里只有 | 精品久久中文 | 在线免费观看黄色小说 | 国产亚洲精品精品精品 | 9色在线视频 | 天天干天天操天天搞 | 天天操天天射天天爱 | 亚洲精品免费在线视频 | 国产白浆视频 | 国产短视频在线播放 | 一区二区三区四区在线免费观看 | 久久国产精品一国产精品 | 999久久久久久久久6666 | 精品国产乱码 | 色在线免费观看 | 国产一二区精品 | 99精品欧美一区二区三区黑人哦 | 国产剧情一区二区在线观看 | 91在线小视频 | 六月激情丁香 | 免费在线一区二区 | 在线 欧美 日韩 | 99理论片 | 国内外激情视频 | 久久国产欧美日韩精品 | 色噜噜在线观看视频 | 日本大片免费观看在线 | 丁香婷婷综合五月 | 国产精品免费一区二区三区 | 国产精品久久久久久久久久不蜜月 | 在线观看你懂的网址 | 男女激情网址 | 欧美aa一级 | 国模吧一区 | 在线观看完整版免费 | 美女网站黄在线观看 | 国产精品久久久久久久免费大片 | 在线播放视频一区 | 亚洲综合视频在线 | 成人a免费看| 中文字幕专区高清在线观看 | 欧美另类高清 | 精品久久久久久国产偷窥 | 国产精品久久久久9999 | 男女免费视频观看 | 成人免费在线电影 | 亚洲精品久久久久中文字幕m男 | 中文字幕精品一区二区精品 | 久99久精品 | 久久久久免费电影 | 成人av片在线观看 | 久久久高清视频 | 狠狠操综合 | 久久夜色电影 | 一区二区 不卡 | av在线之家电影网站 | 欧美日韩一二三四区 | 久久久片 | 精品成人免费 | 久久免费99精品久久久久久 | 视频在线日韩 | 四虎成人免费观看 | 亚洲精品成人av在线 | 人人看97 | 亚洲精品资源在线观看 | 中文字幕在线一二 | 日韩国产欧美在线视频 | 亚洲成人精品影院 | 日本一区二区免费在线观看 | 一本一道久久a久久精品蜜桃 | 国产视频中文字幕 | 九九视频免费在线观看 | 日韩色爱| 日韩专区av| 日韩在线电影一区二区 | 国产一级片观看 | 狠狠干夜夜操 | 国产精品一区在线观看 | 国产精品电影一区 | 最近高清中文字幕在线国语5 | 国产一级在线观看 | 成人免费在线播放 | 伊人网综合在线观看 | 成人在线视频论坛 | av成人在线看 | 国产在线国偷精品产拍免费yy | 香蕉久草| 国产成人一级电影 | 高清av不卡 | 国产精品一区二区在线观看 | 欧美日韩三级在线观看 | 亚洲精品乱码久久久一二三 | 免费午夜av | 91精品网站在线观看 | 成人a视频在线观看 | 午夜国产在线 | 亚洲精品在线国产 | 久久精品5| 91在线视频观看免费 | 国产精品一区二区果冻传媒 | 最近中文字幕国语免费av | 久久中文字幕导航 | 亚洲1区 在线 | 亚洲精品动漫成人3d无尽在线 | 久久久影院官网 | 亚洲欧美日韩一二三区 | 亚洲精品国精品久久99热一 | 欧美日韩高清一区二区三区 | 成人av电影免费观看 | 夜夜躁狠狠躁 | 日批在线观看 | 久久国产精品一区二区 | 手机色在线 | 在线免费观看视频一区二区三区 | 日韩中文字幕亚洲一区二区va在线 | 99久久久成人国产精品 | 日韩精品不卡在线 | 亚洲精品视频在线观看免费视频 | 99在线精品观看 | 亚洲成人av一区二区 | 91久久奴性调教 | 天天添夜夜操 | 国产精品久久久久一区 | 久久久美女 | 91精品国产九九九久久久亚洲 | 国产精品 999 | 国产亚洲精品女人久久久久久 | 久久香蕉电影网 | 青青草华人在线视频 | 国产精彩在线视频 | 色网av| av日韩精品 | 日本在线观看中文字幕无线观看 | 国产精品视频免费在线观看 | 午夜精品一区二区三区在线 | 91免费网| 国产中年夫妇高潮精品视频 | 在线天堂亚洲 | 精品国内自产拍在线观看视频 | 天天插天天爱 | 国产精品视频永久免费播放 | 国产精品午夜在线 | 尤物九九久久国产精品的分类 | 伊人五月天 | 国产日韩精品欧美 | 成人永久视频 | 亚洲精品小区久久久久久 | 亚洲一二视频 | 久久精品成人欧美大片古装 | 亚洲国产欧洲综合997久久, | 成人动漫一区二区三区 | 亚洲黄色区 | 五月天综合在线 | 日本中文字幕在线播放 | 91网址在线看 | 天天做夜夜做 | 国产在线更新 | 久久99精品久久久久久久久久久久 | 亚洲一区久久久 | 亚洲视频播放 | 欧美日韩视频在线观看一区二区 | 欧美另类z0zx | 香蕉视频久久 | 欧美日产一区 | 天天操网站 | 亚洲精品在线播放视频 | 日本大片免费观看在线 | 奇米网网址 | 天天操天天操一操 | 精品久久久久久亚洲综合网站 | 国产一级电影 | 国产精品99久久久久久久久 | 日韩在线 | 96国产在线 | 午夜精品一区二区三区可下载 | 免费看黄的视频 | 国产成人综合精品 | 天天色天天射天天综合网 | 欧美性生交大片免网 | 婷婷5月色 | 国产一区免费在线 | 欧美日韩不卡一区二区 | 久久久av免费 | 日韩欧美在线国产 | 91大神dom调教在线观看 | 一级免费黄视频 | 国产尤物一区二区三区 | 天天操,夜夜操 | 激情av网址| 国产黄色片久久 | 日本久久久久久 | 亚洲精品午夜久久久 | 欧美一级片在线观看视频 | 日韩理论电影在线 | 精品一区二区三区香蕉蜜桃 | 亚洲精品在线观看av | 91九色视频在线 | 国产日韩欧美精品在线观看 | 日韩午夜高清 | 伊人精品影院 | 伊人久久国产精品 | 久久精品99久久久久久2456 | 免费看的黄色片 | 国产成人一区二区在线观看 | 欧美大片第1页 | 中文字幕在线看 | 欧美精品一区二区在线播放 | 久久久久久网址 | 黄色三级在线观看 | 韩国在线一区二区 | 国产一区自拍视频 | 亚洲一区二区三区毛片 | 男女靠逼app | 久草在线手机视频 | 婷婷激情站 | 黄色小视频在线观看免费 | 亚洲理论电影 | 亚洲一级片 | 97在线观看 | 色资源网在线观看 | 亚洲国产精品久久久久 | 日韩在线观看中文 | 狠狠色丁香婷婷综合久小说久 | 91激情视频在线 | 免费中文字幕在线观看 | 国内久久久久久 | 久久激情小说 | 亚洲一区精品人人爽人人躁 | 97精品国产| 九九色综合 | 在线视频婷婷 | 国产在线观看免费观看 | 免费看的av片 | 中文字幕国产 | 91免费看片黄| 成人日韩av| 青青河边草免费直播 | 97网站| 一区二区三区四区五区在线视频 | 成人久久电影 | 国产成人精品久久久久蜜臀 | 欧美三人交 | 日日碰狠狠添天天爽超碰97久久 | 欧美日韩xx| 高清一区二区 | www.久久免费 | 欧美黑吊大战白妞欧美 | 久久九九九九 | 久久综合色播五月 | 99精品视频在线看 | 91麻豆精品国产91久久久无限制版 | 午夜精品久久久久99热app | 97综合网 | 亚洲国产中文在线 | 日韩欧美一区二区不卡 | 三级av小说| 婷婷综合久久 | 欧美激情视频三区 | 美女视频免费一区二区 | 亚洲国产午夜精品 | 成人一区二区三区中文字幕 | 日韩激情视频 | 在线视频在线观看 | 精品一区二区在线播放 | 免费无遮挡动漫网站 | 九九九毛片 | 日本精品中文字幕 | 91免费视频黄 | 亚洲精品videossex少妇 | 一区二区三区四区五区在线 | 中文字幕av在线电影 | 天天综合网 天天综合色 | 成人在线免费视频 | 中文字幕在线播放日韩 | 97国产人人| 日韩精品免费在线观看 | 美女免费网站 | 97在线观看 | 啪啪精品 | 久久在线免费观看视频 | 五月天丁香亚洲 | 五月天天av| 亚洲精品乱码久久久久久写真 | 丁香狠狠 | 中文字幕av全部资源www中文字幕在线观看 | 胖bbbb搡bbbb擦bbbb | 91尤物在线播放 | 免费又黄又爽视频 | 国产精品一区二区免费在线观看 | 中文区中文字幕免费看 | 久久久久久久久久久久亚洲 | 免费在线一区二区 | 成人中文字幕+乱码+中文字幕 | 日韩免费观看av | 麻豆视频免费在线播放 | 久久毛片高清国产 | 日日夜夜精品免费视频 | 国产精品99久久免费观看 | 91大神电影 | 久久免费黄色 | 欧美日韩精品在线观看视频 | 久久免费国产精品1 | 成人黄色影片在线 | 男女激情片在线观看 | 成人av播放 | 91视频免费看片 | 久草香蕉在线 | 欧美久草在线 | 91视频在线自拍 | 午夜久久影院 | 亚洲午夜久久久久久久久 | 精品视频资源站 | 国产伦理剧 | 久久字幕网 | 综合色中色 | 欧美成人精品三级在线观看播放 | 九九影视理伦片 | 91av电影| 日韩精品一区二区三区中文字幕 | 亚洲激情六月 | 亚洲电影黄色 | 久久免费视频3 | 国产精品久久久久毛片大屁完整版 | 天天射天天干天天 | 国产视频一区二区三区在线 | 国产69精品久久久久久 | 国产成人a亚洲精品v | 久久免费大片 | 久久99日韩 | 国产精品观看在线亚洲人成网 | 91成人黄色 | 国产免费久久av | 国产精品久久嫩一区二区免费 | 五月天婷婷在线播放 | 久久久久久久久毛片 | 亚洲永久av | 国产日韩中文字幕 | 亚洲国产精品成人女人久久 | 免费又黄又爽的视频 | 久久国产欧美日韩精品 | 久久久久高清 | 狠狠88综合久久久久综合网 | 草久中文字幕 | 成人黄大片| 欧美精品一区二区免费 | 国产欧美日韩精品一区二区免费 | 免费亚洲视频在线观看 | 婷婷伊人五月天 | 久久久久女人精品毛片九一 | 97电影手机 | 97精品国产97久久久久久免费 | 99在线国产| 久久激情精品 | 91色网址| 99精品美女 | 中文字幕一区二区在线观看 | 欧美日韩三级在线观看 | 99在线热播 | 成人小视频在线观看免费 | 香蕉视频4aa | 在线看成人片 | 免费视频一二三区 | 九九免费观看全部免费视频 | 免费69视频 | 日韩三级视频在线看 | 日韩精品一区二区三区在线视频 | 午夜av在线播放 | 免费福利视频网 | 久久特级毛片 | 成人精品一区二区三区电影免费 | 亚洲精品免费视频 | 91精品国产成人 | 中文字幕免费观看全部电影 | 国产色中涩 | 2019中文字幕第一页 | 黄色国产高清 | 免费看黄在线网站 | 亚洲成人网在线 | 欧美日本不卡视频 | 91精品福利在线 | www.久久久精品 | 国产美女久久 | 天天躁日日躁狠狠躁 | 一本一道波多野毛片中文在线 | 久久伊人精品天天 | 天堂久久电影网 | 亚洲视频免费在线观看 | 天天想夜夜操 | 国产一区二区精品91 | 国产91免费看 | .国产精品成人自产拍在线观看6 | 最近中文字幕大全 | 亚洲乱码一区 | 日韩女同一区二区三区在线观看 | 欧美午夜视频在线 | av黄色在线观看 | 日韩有码欧美 | 美女视频黄是免费的 | 色噜噜在线观看 | 亚洲人成免费 | 久久精品综合视频 | 国产一区在线免费观看 | 天天射天天射 | 1区2区3区在线观看 三级动图 | 一级性生活片 | 最近中文字幕免费av | 国产精品久久久视频 | 超碰成人av| 顶级欧美色妇4khd | 99re6热在线精品视频 | 久久深夜福利免费观看 | 久草在线资源观看 | 久久久精品国产一区二区 | 日韩区在线观看 | 久久99在线 | 精品久久久久久久久久 | 一级免费观看 | 日韩.com| 91夫妻自拍 | 色综合亚洲精品激情狠狠 | 999久久久欧美日韩黑人 | 日韩精品aaa | 久操操 | 国产成人精品一区二区三区网站观看 | 国产一区成人在线 | 精品视频一区在线 | 2019中文在线观看 | 久久精品日产第一区二区三区乱码 | 久久九九久久九九 | 久久久久人人 | 三上悠亚一区二区在线观看 | 久久婷婷一区二区三区 | 国产精品资源在线 | 欧洲精品一区二区 | 久久人人爽人人爽人人片 | 91传媒91久久久 | 欧美成人69av | 在线观看自拍 | 在线成人小视频 | 中文字幕第一页在线 | 久久蜜臀一区二区三区av | 免费碰碰 | 久久香蕉电影 | 色av资源网 | 激情深爱.com| www.久草.com| 久久女同性恋中文字幕 | 欧美日韩一区三区 | 国产成年免费视频 | 久久在线免费观看视频 | 成人免费视频播放 | 国产97色| 黄色aaa毛片 | 亚洲 av网站 | 久久全国免费视频 | 精品自拍av | 一本一本久久a久久精品牛牛影视 | 有码中文字幕在线观看 | 四川bbb搡bbb爽爽视频 | 高清av免费看 | 成人av片免费观看app下载 | 97色视频在线 | 2024国产精品视频 | 欧美激情视频一区 | 亚洲国产影院av久久久久 | 麻豆精品视频在线 | 久久久 精品 | 狠狠色丁香久久婷婷综 | 久久激情小视频 | 色妞色视频一区二区三区四区 | 成人av网址大全 | 手机av在线不卡 | 久久伊人精品天天 | 国产99区| 999久久精品| 国产精品第二页 | 亚洲精品综合在线 | 999男人的天堂 | 黄色片亚洲 | 日韩精品无码一区二区三区 | 国产三级视频 | 中文字幕在线观看视频一区二区三区 | 国产不卡在线观看视频 | 色网站免费在线看 | 中文字幕在线免费看线人 | 久久免费视频精品 | 国内精品久久久久久久久久清纯 | 国产91精品一区二区 | 美女久久久久 | 久久99国产综合精品 | 国产美女免费观看 | 欧美日韩视频观看 | 黄色精品网站 | 亚洲精品国产精品国自 | 亚洲免费在线看 | 黄色三级在线看 | 国产精品免费在线 | 久久网页 | 国产视频中文字幕在线观看 | 久久精品精品 | 午夜色大片在线观看 | 国产黄色精品在线 | 色综合久久88色综合天天6 | a级片网站 | 特级西西444www大精品视频免费看 | 亚洲激情在线观看 | 黄色一级免费电影 | 奇米网网址 | 日日夜夜综合网 | 欧美人人| 欧美国产日韩激情 | 超碰在线最新地址 | 国产亚洲小视频 | 国产精品久久久久久a | 日本精品视频免费观看 | 欧美精品免费在线观看 | 免费日韩一区 | 丁香婷婷激情国产高清秒播 | 亚洲精品无 | 欧美一级视频在线观看 | 国产精品无av码在线观看 | 在线 视频 一区二区 | 久久综合精品国产一区二区三区 | 欧美日韩精品免费观看视频 | 久草在线精品观看 | 欧美福利在线播放 | 操操操人人 | 99视频免费看 | 久久精品一区二区三区四区 | 国产中文在线视频 | 九九综合在线 | 最近中文字幕视频网 | 久草在线精品观看 | 超碰夜夜| 蜜臀av性久久久久av蜜臀三区 | 91一区啪爱嗯打偷拍欧美 | 欧美在线视频一区二区 | av日韩国产 | 黄色电影网站在线观看 | 久久99热这里只有精品国产 | 国产精品久久久久久69 | 久久久私人影院 | 美女视频黄是免费的 | av线上免费观看 | 免费激情在线电影 | 91麻豆精品久久久久久 | 国产福利一区二区三区视频 | 在线国产观看 | 日本乱码在线 | 91手机电影| 国产糖心vlog在线观看 | 国产私拍在线 | 国产成人精品一区二区三区福利 | 亚洲国产视频a | 亚洲国产人午在线一二区 | 免费日韩一区二区三区 | 亚洲精品视频在线 | 国产精品久久久久9999吃药 | 国产福利91精品一区 | 91亚洲欧美激情 | 国产香蕉视频在线观看 | 中文字幕电影在线 | av在线一级 | 91在线播放综合 | 国产精品免费观看国产网曝瓜 | 一级黄色免费网站 | 日韩欧美69| 免费三级骚 | 国产成人专区 | 久久一二三四 | www.亚洲视频.com | 国产精品激情在线观看 | 91一区一区三区 | 国产精品久久久免费 | 激情小说久久 | 狠狠伊人 | 欧美日韩久久久 | 99久久久久成人国产免费 | 国产韩国日本高清视频 | 日韩欧美国产精品 | 欧美日本啪啪无遮挡网站 | 中文字幕免费国产精品 | 国产在线a免费观看 | 国产精品爽爽爽 | 亚洲精品2区| 黄色三几片 | 一区二区三区精品在线视频 | 美国三级黄色大片 | 成年人视频免费在线播放 | 国产人成一区二区三区影院 | 91一区二区三区在线观看 | 玖玖在线观看视频 | 免费视频一区二区 | 999久久久免费视频 午夜国产在线观看 | 免费午夜视频在线观看 | 国产日产高清dvd碟片 | 日韩高清一区 | 亚洲婷婷丁香 | 伊人五月天 | 蜜桃视频日本 | 在线观看av小说 | 日日干视频 | 91久久爱热色涩涩 | 99热在| 国产精品成人一区二区三区吃奶 | 久久婷婷国产 | 久久一区二区三区超碰国产精品 | 午夜久草| 欧美亚洲精品在线观看 | 亚洲伦理中文字幕 | 中文字幕视频 | 超碰日韩在线 | 蜜臀久久99精品久久久无需会员 | 天天天天天天干 | 国产亚洲精品久久网站 | 日韩欧美视频免费在线观看 | 精品免费视频123区 午夜久久成人 | 久久国产精品免费视频 | 综合久久久| 亚洲视频axxx | 成人免费视频免费观看 | 国产免费成人av | 日本aaaa级毛片在线看 | 激情网五月 | 亚洲天天看 | 爱爱一区 | 国产五十路毛片 | 美女黄濒| 五月婷婷久久综合 | 欧美一性一交一乱 | 国产又粗又猛又色 | 久久99久久99 | 美女免费网站 | 一区二区三区免费 | 久草精品在线播放 | 国产成人免费在线观看 | 成人一区二区三区中文字幕 | 深夜国产在线 | a√天堂资源 | 天天草天天色 | 久草视频免费 | 97人人模人人爽人人少妇 | 天堂在线免费视频 | 日韩在线观看的 | 中文字幕影片免费在线观看 | 四虎国产视频 | 亚洲一级免费观看 |