一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 學會Java字節碼指令,成為技術大佬

學會Java字節碼指令,成為技術大佬

2021-11-15 13:37沉默王二 Java教程

Java 字節碼指令是 JVM 體系中非常難啃的一塊硬骨頭,我估計有些讀者會有這樣的疑惑,“Java 字節碼難學嗎?我能不能學會???”本文帶領大家一探究竟,幫助大家搞懂java底層代碼如何執行

Java 官方的虛擬機 Hotspot 是基于棧的,而不是基于寄存器的。

基于棧的優點是可移植性更好、指令更短、實現起來簡單,但不能隨機訪問棧中的元素,完成相同功能所需要的指令數也比寄存器的要多,需要頻繁的入棧和出棧。

基于寄存器的優點是速度快,有利于程序運行速度的優化,但操作數需要顯式指定,指令也比較長。

Java 字節碼由操作碼和操作數組成。

  • 操作碼(Opcode):一個字節長度(0-255,意味著指令集的操作碼總數不可能超過 256 條),代表著某種特定的操作含義。
  • 操作數(Operands):零個或者多個,緊跟在操作碼之后,代表此操作需要的參數。

由于 Java 虛擬機是基于棧而不是寄存器的結構,所以大多數指令都只有一個操作碼。比如aload_0(將局部變量表中下標為 0 的數據壓入操作數棧中)就只有操作碼沒有操作數,而 invokespecial #1(調用成員方法或者構造方法,并傳遞常量池中下標為 1 的常量)就是由操作碼和操作數組成的。

01、加載與存儲指令

加載(load)和存儲(store)相關的指令是使用最頻繁的指令,用于將數據從棧幀的局部變量表和操作數棧之間來回傳遞。

1)將局部變量表中的變量壓入操作數棧中

  • xload_(x 為 i、l、f、d、a,n 默認為 0 到 3),表示將第 n 個局部變量壓入操作數棧中。
  • xload(x 為 i、l、f、d、a),通過指定參數的形式,將局部變量壓入操作數棧中,當使用這個指令時,表示局部變量的數量可能超過了 4 個

解釋一下

x 為操作碼助記符,表明是哪一種數據類型。見下表所示。

學會Java字節碼指令,成為技術大佬

像 arraylength 指令,沒有操作碼助記符,它沒有代表數據類型的特殊字符,但操作數只能是一個數組類型的對象。

大部分的指令都不支持 byte、short 和 char,甚至沒有任何指令支持 boolean 類型。編譯器會將 byte 和 short 類型的數據帶符號擴展(Sign-Extend)為 int 類型,將 boolean 和 char 零位擴展(Zero-Extend)為 int 類型。

舉例來說:

private void load(int age, String name, long birthday, boolean sex) {
    System.out.println(age + name + birthday + sex);
}

通過 jclasslib 看一下 load() 方法(4 個參數)的字節碼指令。

學會Java字節碼指令,成為技術大佬

  • iload_1:將局部變量表中下標為 1 的 int 變量壓入操作數棧中。
  • aload_2:將局部變量表中下標為 2 的引用數據類型變量(此時為 String)壓入操作數棧中。
  • lload_3:將局部變量表中下標為 3 的 long 型變量壓入操作數棧中。
  • iload 5:將局部變量表中下標為 5 的 int 變量(實際為 boolean)壓入操作數棧中。

通過查看局部變量表就能關聯上了。

學會Java字節碼指令,成為技術大佬

2)將常量池中的常量壓入操作數棧中

根據數據類型和入棧內容的不同,此類又可以細分為 const 系列、push 系列和 Idc 指令。

const 系列,用于特殊的常量入棧,要入棧的常量隱含在指令本身。

學會Java字節碼指令,成為技術大佬

push 系列,主要包括 bipush 和 sipush,前者接收 8 位整數作為參數,后者接收 16 位整數。

Idc 指令,當 const 和 push 不能滿足的時候,萬能的 Idc 指令就上場了,它接收一個 8 位的參數,指向常量池中的索引。

  • Idc_w:接收兩個 8 位數,索引范圍更大。
  • 如果參數是 long 或者 double,使用 Idc2_w 指令。

舉例來說:

public void pushConstLdc() {
    // 范圍 [-1,5]
    int iconst = -1;
    // 范圍 [-128,127]
    int bipush = 127;
    // 范圍 [-32768,32767]
    int sipush= 32767;
    // 其他 int
    int ldc = 32768;
    String aconst = null;
    String IdcString = "沉默王二";
}

 通過 jclasslib 看一下 pushConstLdc() 方法的字節碼指令。

學會Java字節碼指令,成為技術大佬

  • iconst_m1:將 -1 入棧。范圍 [-1,5]。
  • bipush 127:將 127 入棧。范圍 [-128,127]。
  • sipush 32767:將 32767 入棧。范圍 [-32768,32767]。
  • ldc #6 <32768>:將常量池中下標為 6 的常量 32768 入棧。
  • aconst_null:將 null 入棧。
  • ldc #7 <沉默王二>:將常量池中下標為 7 的常量“沉默王二”入棧。

3)將棧頂的數據出棧并裝入局部變量表中

主要是用來給局部變量賦值,這類指令主要以 store 的形式存在。

  • xstore_(x 為 i、l、f、d、a,n 默認為 0 到 3)
  • xstore(x 為 i、l、f、d、a)

明白了 xload_ 和 xload,再看 xstore_ 和 xstore 就會輕松得多,作用反了一下而已。

大家來想一個問題,為什么要有 xstore_ 和 xload_ 呢?它們的作用和 xstore n、xload n 不是一樣的嗎?

xstore_ 和 xstore n 的區別在于,前者相當于只有操作碼,占用 1 個字節;后者相當于由操作碼和操作數組成,操作碼占 1 個字節,操作數占 2 個字節,一共占 3 個字節。

由于局部變量表中前幾個位置總是非常常用,雖然 xstore_<n> 和 xload_<n> 增加了指令數量,但字節碼的體積變小了!

舉例來說:

public void store(int age, String name) {
    int temp = age + 2;
    String str = name;
}

通過 jclasslib 看一下 store() 方法的字節碼指令。

學會Java字節碼指令,成為技術大佬

  • istore_3:從操作數中彈出一個整數,并把它賦值給局部變量表中索引為 3 的變量。
  • astore 4:從操作數中彈出一個引用數據類型,并把它賦值給局部變量表中索引為 4 的變量。

通過查看局部變量表就能關聯上了。

學會Java字節碼指令,成為技術大佬

02、算術指令

算術指令用于對兩個操作數棧上的值進行某種特定運算,并把結果重新壓入操作數棧。可以分為兩類:整型數據的運算指令和浮點數據的運算指令。

需要注意的是,數據運算可能會導致溢出,比如兩個很大的正整數相加,很可能會得到一個負數。但 Java 虛擬機規范中并沒有對這種情況給出具體結果,因此程序是不會顯式報錯的。所以,大家在開發過程中,如果涉及到較大的數據進行加法、乘法運算的時候,一定要注意!

當發生溢出時,將會使用有符號的無窮大 Infinity 來表示;如果某個操作結果沒有明確的數學定義的話,將會使用 NaN 值來表示。而且所有使用 NaN 作為操作數的算術操作,結果都會返回 NaN。

舉例來說:

public void infinityNaN() {
    int i = 10;
    double j = i / 0.0;
    System.out.println(j); // Infinity

    double d1 = 0.0;
    double d2 = d1 / 0.0;
    System.out.println(d2); // NaN
}
  • 任何一個非零的數除以浮點數 0(注意不是 int 類型),可以想象結果是無窮大 Infinity 的。
  • 把這個非零的數換成 0 的時候,結果又不太好定義,就用 NaN 值來表示。

Java 虛擬機提供了兩種運算模式:

  • 向最接近數舍入:在進行浮點數運算時,所有的結果都必須舍入到一個適當的精度,不是特別精確的結果必須舍入為可被表示的最接近的精確值,如果有兩種可表示的形式與該值接近,將優先選擇最低有效位為零的(類似四舍五入)。
  • 向零舍入:將浮點數轉換為整數時,采用該模式,該模式將在目標數值類型中選擇一個最接近但是不大于原值的數字作為最精確的舍入結果(類似取整)。

我把所有的算術指令列一下:

  • 加法指令:iadd、ladd、fadd、dadd
  • 減法指令:isub、lsub、fsub、dsub
  • 乘法指令:imul、lmul、fmul、dmul
  • 除法指令:idiv、ldiv、fdiv、ddiv
  • 求余指令:irem、lrem、frem、drem
  • 自增指令:iinc

舉例來說:

public void calculate(int age) {
    int add = age + 1;
    int sub = age - 1;
    int mul = age * 2;
    int div = age / 3;
    int rem = age % 4;
    age++;
    age--;
}

通過 jclasslib 看一下 calculate() 方法的字節碼指令。

學會Java字節碼指令,成為技術大佬

  • iadd,加法
  • isub,減法
  • imul,乘法
  • idiv,除法
  • irem,取余
  • iinc,自增的時候 +1,自減的時候 -1

 03、類型轉換指令

可以分為兩種:

1)寬化,小類型向大類型轉換,比如 int–>long–>float–>double,對應的指令有:i2l、i2f、i2d、l2f、l2d、f2d。

  • 從 int 到 long,或者從 int 到 double,是不會有精度丟失的;
  • 從 int、long 到 float,或者 long 到 double 時,可能會發生精度丟失;
  • 從 byte、char 和 short 到 int 的寬化類型轉換實際上是隱式發生的,這樣可以減少字節碼指令,畢竟字節碼指令只有 256 個,占一個字節。

2)窄化,大類型向小類型轉換,比如從 int 類型到 byte、short 或者 char,對應的指令有:i2b、i2s、i2c;從 long 到 int,對應的指令有:l2i;從 float 到 int 或者 long,對應的指令有:f2i、f2l;從 double 到 int、long 或者 float,對應的指令有:d2i、d2l、d2f。

窄化很可能會發生精度丟失,畢竟是不同的數量級;

但 Java 虛擬機并不會因此拋出運行時異常。

舉例來說:

public void updown() {
    int i = 10;
    double d = i;
    
    float f = 10f;
    long ong = (long)f;
}

通過 jclasslib 看一下 updown() 方法的字節碼指令。

學會Java字節碼指令,成為技術大佬

  • i2d,int 寬化為 doublef
  • 2l, float 窄化為 long

 04、對象的創建和訪問指令

Java 是一門面向對象的編程語言,那么 Java 虛擬機是如何從字節碼層面進行支持的呢?

1)創建指令

數組也是一種對象,但它創建的字節碼指令和普通的對象不同。創建數組的指令有三種:

  • newarray:創建基本數據類型的數組
  • anewarray:創建引用類型的數組
  • multianewarray:創建多維數組

普通對象的創建指令只有一個,就是 new,它會接收一個操作數,指向常量池中的一個索引,表示要創建的類型。

舉例來說:

public void newObject() {
    String name = new String("沉默王二");
    File file = new File("無愁河的浪蕩漢子.book");
    int [] ages = {};
}

通過 jclasslib 看一下 newObject() 方法的字節碼指令。

學會Java字節碼指令,成為技術大佬

  • new #13 <java/lang/String>,創建一個 String 對象。
  • new #15 <java/io/File>,創建一個 File 對象。
  • newarray 10 (int),創建一個 int 類型的數組。

2)字段訪問指令

字段可以分為兩類,一類是成員變量,一類是靜態變量(static 關鍵字修飾的),所以字段訪問指令可以分為兩類:

  • 訪問靜態變量:getstatic、putstatic。
  • 訪問成員變量:getfield、putfield,需要創建對象后才能訪問。

舉例來說:

public class Writer {
    private String name;
    static String mark = "作者";

    public static void main(String[] args) {
        print(mark);
        Writer w = new Writer();
        print(w.name);
    }

    public static void print(String arg) {
        System.out.println(arg);
    }
}

通過 jclasslib 看一下 main() 方法的字節碼指令。

學會Java字節碼指令,成為技術大佬

  • getstatic #2 <com/itwanger/jvm/Writer.mark>,訪問靜態變量 mark
  • getfield #6 <com/itwanger/jvm/Writer.name>,訪問成員變量 name

 05、方法調用和返回指令

方法調用指令有 5 個,分別用于不同的場景:

  • invokevirtual:用于調用對象的成員方法,根據對象的實際類型進行分派,支持多態。
  • invokeinterface:用于調用接口方法,會在運行時搜索由特定對象實現的接口方法進行調用。
  • invokespecial:用于調用一些需要特殊處理的方法,包括構造方法、私有方法和父類方法。
  • invokestatic:用于調用靜態方法。
  • invokedynamic:用于在運行時動態解析出調用點限定符所引用的方法,并執行。

舉例來說:

public class InvokeExamples {
    private void run() {
        List ls = new ArrayList();
        ls.add("難頂");

        ArrayList als = new ArrayList();
        als.add("學不動了");
    }

    public static void print() {
        System.out.println("invokestatic");
    }

    public static void main(String[] args) {
        print();
        InvokeExamples invoke = new InvokeExamples();
        invoke.run();
    }
}

我們用 javap -c InvokeExamples.class 來反編譯一下。

Compiled from "InvokeExamples.java"
public class com.itwanger.jvm.InvokeExamples {
  public com.itwanger.jvm.InvokeExamples();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  private void run();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #4                  // String 難頂
      11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      16: pop
      17: new           #2                  // class java/util/ArrayList
      20: dup
      21: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
      24: astore_2
      25: aload_2
      26: ldc           #6                  // String 學不動了
      28: invokevirtual #7                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
      31: pop
      32: return

  public static void print();
    Code:
       0: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #9                  // String invokestatic
       5: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #11                 // Method print:()V
       3: new           #12                 // class com/itwanger/jvm/InvokeExamples
       6: dup
       7: invokespecial #13                 // Method "<init>":()V
      10: astore_1
      11: aload_1
      12: invokevirtual #14                 // Method run:()V
      15: return
}

InvokeExamples 類有 4 個方法,包括缺省的構造方法在內。

1)InvokeExamples() 構造方法中

缺省的構造方法內部會調用超類 Object 的初始化構造方法:

`invokespecial #1 // Method java/lang/Object."<init>":()V`

2)成員方法 run() 中

invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z

由于 ls 變量的引用類型為接口 List,所以 ls.add() 調用的是 invokeinterface 指令,等運行時再確定是不是接口 List 的實現對象 ArrayList 的 add() 方法。

invokevirtual #7 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z

由于 als 變量的引用類型已經確定為 ArrayList,所以 als.add() 方法調用的是 invokevirtual 指令。

3)main() 方法中

invokestatic #11 // Method print:()V

print() 方法是靜態的,所以調用的是 invokestatic 指令。

方法返回指令根據方法的返回值類型進行區分,常見的返回指令見下圖。

學會Java字節碼指令,成為技術大佬

06、操作數棧管理指令

常見的操作數棧管理指令有 pop、dup 和 swap。

  • 將一個或兩個元素從棧頂彈出,并且直接廢棄,比如 pop,pop2;
  • 復制棧頂的一個或兩個數值并將其重新壓入棧頂,比如 dup,dup2,dup_×1,dup2_×1,dup_×2,dup2_×2;
  • 將棧最頂端的兩個槽中的數值交換位置,比如 swap。

這些指令不需要指明數據類型,因為是按照位置壓入和彈出的。

舉例來說:

public class Dup {
    int age;
    public int incAndGet() {
        return ++age;
    }
}

通過 jclasslib 看一下 incAndGet() 方法的字節碼指令。

學會Java字節碼指令,成為技術大佬

  • aload_0:將 this 入棧。
  • dup:復制棧頂的 this。
  • getfield #2:將常量池中下標為 2 的常量加載到棧上,同時將一個 this 出棧。
  • iconst_1:將常量 1 入棧。
  • iadd:將棧頂的兩個值相加后出棧,并將結果放回棧上。
  • dup_x1:復制棧頂的元素,并將其插入 this 下面。
  • putfield #2: 將棧頂的兩個元素出棧,并將其賦值給字段 age。
  • ireturn:將棧頂的元素出棧返回。

 07、控制轉移指令

控制轉移指令包括:

  • 比較指令,比較棧頂的兩個元素的大小,并將比較結果入棧。
  • 條件跳轉指令,通常和比較指令一塊使用,在條件跳轉指令執行前,一般先用比較指令進行棧頂元素的比較,然后進行條件跳轉。
  • 比較條件轉指令,類似于比較指令和條件跳轉指令的結合體,它將比較和跳轉兩個步驟合二為一。
  • 多條件分支跳轉指令,專為 switch-case 語句設計的。
  • 無條件跳轉指令,目前主要是 goto 指令。

1)比較指令

比較指令有:dcmpg,dcmpl、fcmpg、fcmpl、lcmp,指令的第一個字母代表的含義分別是 double、float、long。注意,沒有 int 類型。

對于 double 和 float 來說,由于 NaN 的存在,有兩個版本的比較指令。拿 float 來說,有 fcmpg 和 fcmpl,區別在于,如果遇到 NaN,fcmpg 會將 1 壓入棧,fcmpl 會將 -1 壓入棧。

舉例來說。

public void lcmp(long a, long b) {
    if(a > b){}
}

通過 jclasslib 看一下 lcmp() 方法的字節碼指令。

學會Java字節碼指令,成為技術大佬

lcmp 用于兩個 long 型的數據進行比較。

2)條件跳轉指令

學會Java字節碼指令,成為技術大佬

這些指令都會接收兩個字節的操作數,它們的統一含義是,彈出棧頂元素,測試它是否滿足某一條件,滿足的話,跳轉到對應位置。

對于 long、float 和 double 類型的條件分支比較,會先執行比較指令返回一個整形值到操作數棧中后再執行 int 類型的條件跳轉指令。

對于 boolean、byte、char、short,以及 int,則直接使用條件跳轉指令來完成。

舉例來說。

public void fi() {
    int a = 0;
    if (a == 0) {
        a = 10;
    } else {
        a = 20;
    }
}

通過 jclasslib 看一下 fi() 方法的字節碼指令。

學會Java字節碼指令,成為技術大佬

3 ifne 12 (+9) 的意思是,如果棧頂的元素不等于 0,跳轉到第 12(3+9)行 12 bipush 20。

3)比較條件轉指令

學會Java字節碼指令,成為技術大佬

前綴“if_”后,以字符“i”開頭的指令針對 int 型整數進行操作,以字符“a”開頭的指令表示對象的比較。

舉例來說:

public void compare() {
    int i = 10;
    int j = 20;
    System.out.println(i > j);
}

通過 jclasslib 看一下 compare() 方法的字節碼指令。

學會Java字節碼指令,成為技術大佬

11 if_icmple 18 (+7) 的意思是,如果棧頂的兩個 int 類型的數值比較的話,如果前者小于后者時跳轉到第 18 行(11+7)。

4)多條件分支跳轉指令

主要有 tableswitch 和 lookupswitch,前者要求多個條件分支值是連續的,它內部只存放起始值和終止值,以及若干個跳轉偏移量,通過給定的操作數 index,可以立即定位到跳轉偏移量位置,因此效率比較高;后者內部存放著各個離散的 case-offset 對,每次執行都要搜索全部的 case-offset 對,找到匹配的 case 值,并根據對應的 offset 計算跳轉地址,因此效率較低。

舉例來說:

public void switchTest(int select) {
    int num;
    switch (select) {
        case 1:
            num = 10;
            break;
        case 2:
        case 3:
            num = 30;
            break;
        default:
            num = 40;
    }
}

通過 jclasslib 看一下 switchTest() 方法的字節碼指令。

學會Java字節碼指令,成為技術大佬

case 2 的時候沒有 break,所以 case 2 和 case 3 是連續的,用的是 tableswitch。如果等于 1,跳轉到 28 行;如果等于 2 和 3,跳轉到 34 行,如果是 default,跳轉到 40 行。

5)無條件跳轉指令

goto 指令接收兩個字節的操作數,共同組成一個帶符號的整數,用于指定指令的偏移量,指令執行的目的就是跳轉到偏移量給定的位置處。

前面的例子里都出現了 goto 的身影,也很好理解。如果指令的偏移量特別大,超出了兩個字節的范圍,可以使用指令 goto_w,接收 4 個字節的操作數。

想要走得更遠,Java 字節碼這塊就必須得硬碰硬地吃透,希望這些分享可以幫助到大家~

路漫漫其修遠兮,吾將上下而求索

除了以上這些指令,還有異常處理指令和同步控制指令,很多Java 方面的系列文章,例如 Java 核心語法、Java 集合框架、Java IO、Java 并發編程、Java 虛擬機等,持續更新中,希望大家多多關注支持服務器之家!

原文鏈接:https://blog.csdn.net/qing_gee/article/details/119320646

延伸 · 閱讀

精彩推薦
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
主站蜘蛛池模板: 好男人资源免费观看 | 免费日本视频 | 欧美香蕉视频 | 日本ccc三级 | 亚洲精品国产综合久久一线 | 成人在线视频在线观看 | 亚洲激情欧美 | 国产一卡二卡3卡4卡四卡在线 | 国产在线视频福利 | 亚洲大片在线观看 | 私人影院在线免费观看 | 国产成人一区二区三区 | 亚洲免费黄色网 | 亚洲高清国产拍精品动图 | 久久99精国产一区二区三区四区 | 天色综合| 日本人妖视频 | 日韩亚洲欧美理论片 | 国产一区二区在线观看视频 | 国产精品原创视频 | 香蕉久久ac一区二区三区 | 久久www免费人成_看片高清 | 91看片淫黄大片欧美看国产片 | 午夜亚洲精品久久久久久 | 草逼的视频 | 成人国产精品一区二区不卡 | 日韩免费一级片 | 久久免费国产视频 | 国产一卡二卡3卡4卡四卡在线 | 视频一区在线观看 | 色综久久天天综合绕视看 | 被强迫调教的高辣小说 | 欧美一区二区三区四区在线观看 | 天天操天天做 | 波多野结在线观看 | free性丰满hd性欧美人体 | 日本在线亚州精品视频在线 | 亚洲成人免费看 | 操碰免费视频 | 日本在线视频网址 | 91久久色 |