<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    LetsCoding.cn

    天地之間有桿秤,拿秤砣砸老百姓。

    字節(jié)碼基礎(chǔ):JVM字節(jié)碼初探

    歡迎來(lái)到“Under The Hood“第三期。前兩期我們分別介紹了JVM的基本結(jié)構(gòu)和功能Java類文件的基本結(jié)構(gòu),本期的主要內(nèi)容有:字節(jié)碼所操作的原始類型、類型轉(zhuǎn)換的字節(jié)碼,以及操作JVM棧的字節(jié)碼。

    字節(jié)碼格式

    字節(jié)碼是JVM的機(jī)器語(yǔ)言。JVM加載類文件時(shí),對(duì)類中的每個(gè)方法,它都會(huì)得到一個(gè)字節(jié)碼流。這些字節(jié)碼流保存在JVM的方法區(qū)中。在程序運(yùn)行過(guò)程中,當(dāng)一個(gè)方法被調(diào)用時(shí),它的字節(jié)碼流就會(huì)被執(zhí)行。根據(jù)特定JVM設(shè)計(jì)者的選擇,它們可以通過(guò)解釋的方式,即時(shí)編譯(Just-in-time compilation)的方式或其他技術(shù)的方式被執(zhí)行。

    方法的字節(jié)碼流就是JVM的指令(instruction)序列。每條指令包含一個(gè)單字節(jié)的操作碼(opcode)和0個(gè)或多個(gè)操作數(shù)(operand)。操作碼指明要執(zhí)行的操作。如果JVM在執(zhí)行操作前,需要更多的信息,這些信息會(huì)以0個(gè)或多個(gè)操作數(shù)的方式,緊跟在操作碼的后面。

    每種類型的操作碼都有一個(gè)助記符(mnemonic)。類似典型的匯編語(yǔ)言風(fēng)格,Java字節(jié)碼流可以用它們的助記符和緊跟在后面的操作數(shù)來(lái)表示。例如,下面的字節(jié)碼流可以分解成多個(gè)助記符的形式。

    1. // 字節(jié)碼流: 03 3b 84 00 01 1a 05 68 3b a7 ff f9
    2. // 分解后:
    3. iconst_0      // 03
    4. istore_0      // 3b
    5. iinc 01     // 84 00 01
    6. iload_0       // 1a
    7. iconst_2      // 05
    8. imul          // 68
    9. istore_0      // 3b
    10. goto -7       // a7 ff f9

    字節(jié)碼指令集被設(shè)計(jì)的很緊湊。除了處理跳表的2條指令以外,所有的指令都以字節(jié)邊界對(duì)齊。操作碼的總數(shù)很少,一個(gè)字節(jié)就能搞定。這最小化了JVM加載前,通過(guò)網(wǎng)絡(luò)傳輸?shù)念愇募拇笮。灰彩沟肑VM可以維持很小的實(shí)現(xiàn)。

    JVM中,所有的計(jì)算都是圍繞棧(stack)而展開(kāi)的。因?yàn)镴VM沒(méi)有存儲(chǔ)任意數(shù)值的寄存器(register),所有的操作數(shù)在計(jì)算開(kāi)始之前,都必須先壓入棧中。因此,字節(jié)碼指令主要是用來(lái)操作棧的。例如,在上面的字節(jié)碼序列中,通過(guò)iload_0先把本地變量(local variable)入棧,然后用iconst_2把數(shù)字2入棧的方式,來(lái)計(jì)算本地變量乘以2。兩個(gè)整數(shù)都入棧之后,imul指令有效的從棧中彈出它們,然后做乘法,最后把運(yùn)算結(jié)果壓入棧中。istore_0指令把結(jié)果從棧頂彈出,保存回本地變量。JVM被設(shè)計(jì)成基于棧,而不是寄存器的機(jī)器,這使得它在如80486寄存器架構(gòu)不佳的處理器上,也能被高效的實(shí)現(xiàn)。

    原始類型(primitive types)

    JVM支持7種原始數(shù)據(jù)類型。Java程序員可以聲明和使用這些數(shù)據(jù)類型的變量,而Java字節(jié)碼,處理這些數(shù)據(jù)類型。下表列出了這7種原始數(shù)據(jù)類型:

    類型
    定義
    byte 單字節(jié)有符號(hào)二進(jìn)制補(bǔ)碼整數(shù)
    short 2字節(jié)有符號(hào)二進(jìn)制補(bǔ)碼整數(shù)
    int 4字節(jié)有符號(hào)二進(jìn)制補(bǔ)碼整數(shù)
    long 8字節(jié)有符號(hào)二進(jìn)制補(bǔ)碼整數(shù)
    float 4字節(jié)IEEE 754單精度浮點(diǎn)數(shù)
    double 8字節(jié)IEEE 754雙精度浮點(diǎn)數(shù)
    char 2字節(jié)無(wú)符號(hào)Unicode字符

    原始數(shù)據(jù)類型以操作數(shù)的方式出現(xiàn)在字節(jié)碼流中。所有長(zhǎng)度超過(guò)1字節(jié)的原始類型,都以大端(big-endian)的方式保存在字節(jié)碼流中,這意味著高位字節(jié)出現(xiàn)在低位字節(jié)之前。例如,為了把常量值256(0×0100)壓入棧中,你可以用sipush操作碼,后跟一個(gè)短操作數(shù)。短操作數(shù)會(huì)以“01 00”的方式出現(xiàn)在字節(jié)碼流中,因?yàn)镴VM是大端的。如果JVM是小端(little-endian)的,短操作數(shù)將會(huì)是“00 01”。

    1. // Bytecode stream: 17 01 00
    2. // Dissassembly:
    3. sipush 256;      // 17 01 00

    把常量(constants)壓入棧中

    很多操作碼都可以把常量壓入棧中。操作碼以3中不同的方式指定入棧的常量值:由操作碼隱式指明,作為操作數(shù)跟在操作碼之后,或者從常量池(constant pool)中獲取。

    有些操作碼本身就指明了要入棧的數(shù)據(jù)類型和常量數(shù)值。例如,iconst_1告訴JVM把整數(shù)1壓入棧中。這種操作碼,是為不同類型而經(jīng)常入棧的數(shù)值而定義的。它們?cè)谧止?jié)碼流中只占用1個(gè)字節(jié),增進(jìn)了字節(jié)碼的執(zhí)行效率,并減小了字節(jié)碼流的大小。下表列出了int型和float型的操作碼:

    操作碼
    操作數(shù)
    描述
    iconst_m1 (none) pushes int -1 onto the stack
    iconst_0 (none) pushes int 0 onto the stack
    iconst_1 (none) pushes int 1 onto the stack
    iconst_2 (none) pushes int 2 onto the stack
    iconst_3 (none) pushes int 3 onto the stack
    iconst_4 (none) pushes int 4 onto the stack
    iconst_5 (none) pushes int 5 onto the stack
    fconst_0 (none) pushes float 0 onto the stack
    fconst_1 (none) pushes float 1 onto the stack
    fconst_2 (none) pushes float 2 onto the stack

    下面列出的操作碼處理的int型和float型都是32位的值。Java棧單元(slot)是32位寬的,因此,每次一個(gè)int數(shù)和float數(shù)入棧,它都占用一個(gè)單元。下表列出的操作碼處理long型和double型。long型和double型的數(shù)值占用64位。每次一個(gè)long數(shù)或double數(shù)被壓入棧中,它都占用2個(gè)棧單元。下面的表格,列出了隱含處理long型和double型的操作碼

    操作碼
    操作數(shù)
    描述
    lconst_0 (none) pushes long 0 onto the stack
    lconst_1 (none) pushes long 1 onto the stack
    dconst_0 (none) pushes double 0 onto the stack
    dconst_1 (none) pushes double 1 onto the stack

    另外還有一個(gè)隱含入棧常量值的操作碼,aconst_null,它把空對(duì)象(null object)的引用(reference)壓入棧中。對(duì)象引用的格式取決于JVM實(shí)現(xiàn)。對(duì)象引用指向垃圾收集堆(garbage-collected heap)中的對(duì)象。空對(duì)象引用,意味著一個(gè)變量當(dāng)前沒(méi)有指向任何合法對(duì)象。aconst_null操作碼用在給引用變量賦null值的時(shí)候。

    操作碼
    操作數(shù)
    描述
    aconst_null (none) pushes a null object reference onto the stack

    有2個(gè)操作碼需要緊跟一個(gè)操作數(shù)來(lái)指明入棧的常量值。下表列出的操作碼,用來(lái)把合法的byte型和short型的常量值壓入棧中。byte型或short型的值在入棧之前,先被擴(kuò)展成int型的值,因?yàn)闂卧?2位寬的。對(duì)byte型和short型的操作,實(shí)際上是基于它們擴(kuò)展后的int型值的。

    操作碼
    操作數(shù)
    描述
    bipush byte1 expands byte1 (a byte type) to an int and pushes it onto the stack
    sipush byte1, byte2 expands byte1, byte2 (a short type) to an int and pushes it onto the stack

    有3個(gè)操作碼把常量池中的常量值壓入棧中。所有和類關(guān)聯(lián)的常量,如final變量,都被保存在類的常量池中。把常量池中的常量壓入棧中的操作碼,都有一個(gè)操作數(shù),它表示需要入棧的常量在常量池中的索引。JVM會(huì)根據(jù)索引查找常量,確定它的類型,并把它壓入棧中。

    在字節(jié)碼流中,常量池索引(constant pool index)是一個(gè)緊跟在操作碼后的無(wú)符號(hào)值。操作碼lcd1和lcd2把32位的項(xiàng)壓入棧中,如int或float。兩者的區(qū)別在于lcd1只適用于1-255的常量池索引位,因?yàn)樗乃饕挥?個(gè)字節(jié)。(常量池0號(hào)位未被使用。)lcd2的索引有2個(gè)字節(jié),所以它可以適用于常量池的任意位置。lcd2w也有一個(gè)2字節(jié)的索引,它被用來(lái)指示任意含有64位的long或double型數(shù)據(jù)的常量池位置。下表列出了把常量池中的常量壓入棧中的操作碼:

    操作碼
    操作數(shù)
    描述
    ldc1 indexbyte1 pushes 32-bit constant_pool entry specified by indexbyte1 onto the stack
    ldc2 indexbyte1, indexbyte2 pushes 32-bit constant_pool entry specified by indexbyte1, indexbyte2 onto the stack
    ldc2w indexbyte1, indexbyte2 pushes 64-bit constant_pool entry specified by indexbyte1,indexbyte2 onto the stack

    把局部變量(local variables)壓入棧中

    局部變量保存在棧幀的一個(gè)特殊區(qū)域中。棧幀是當(dāng)前執(zhí)行方法正在使用的棧區(qū)。每個(gè)棧幀包含3個(gè)部分:本地變量區(qū),執(zhí)行環(huán)境和操作數(shù)棧區(qū)。把本地變量入棧實(shí)際上包含了把數(shù)值從棧幀的本地變量區(qū)移動(dòng)到操作數(shù)棧區(qū)。操作數(shù)棧區(qū)總是在棧的頂部,所以,把一個(gè)值壓到當(dāng)前棧幀的操作數(shù)棧區(qū)頂部,跟壓到整個(gè)JVM棧的頂部是一個(gè)意思。

    Java棧是一個(gè)先進(jìn)后出(LIFO)的32位寬的棧。所有的本地變量至少占用32位,因?yàn)闂V械拿總€(gè)單元都是32位寬的。像long和double類型的64位的本地變量會(huì)占用2個(gè)棧單元。byte和short型的本地變量會(huì)當(dāng)做int型來(lái)存儲(chǔ),但只擁有較小類型的合法值。例如,表示byte型的int型本地變量取值范圍總是-128到127。

    每個(gè)本地變量都有一個(gè)唯一索引。方法棧幀的本地變量區(qū),可以當(dāng)成是一個(gè)擁有32位寬的元素的數(shù)組,每個(gè)元素都可以用數(shù)組索引來(lái)尋址。long和double型的占用2個(gè)單元的本地變量,且用低位元素的索引尋址。例如,對(duì)一個(gè)占用2單元和3單元的double數(shù)值,會(huì)用索引2來(lái)引用。

    有一些操作碼可以把int和float型本地變量壓入操作數(shù)棧。部分操作碼,定義成隱含常用本地變量地址的引用。例如,iload_0加載處在位置0的int型本地變量。其他本地變量,通過(guò)操作碼后跟一個(gè)字節(jié)的本地變量索引的方式壓入棧中。iload指令就是這種操作碼類型的一個(gè)例子。iload后的一個(gè)字節(jié)被解釋成指向本地變量的8位無(wú)符號(hào)索引。

    類似iload所用的8位無(wú)符號(hào)本地變量索引,限制了一個(gè)方法最多只能有256個(gè)本地變量。有一個(gè)單獨(dú)的wide指令可以把8位索引擴(kuò)展為16位索引,則使得本地變量數(shù)的上限提高到64k個(gè)。操作碼wide只有1個(gè)操作數(shù)。wide和它的操作數(shù),出現(xiàn)在像iload之類的有一個(gè)8位無(wú)符號(hào)本地變量索引的指令之前。JVM會(huì)把wide的操作數(shù)和iload的操作數(shù)合并為一個(gè)16位的無(wú)符號(hào)本地變量索引。

    下表列出了把int和float型本地變量壓入棧中的操作碼:

    操作碼
    操作數(shù)
    描述
    iload vindex pushes int from local variable position vindex
    iload_0 (none) pushes int from local variable position zero
    iload_1 (none) pushes int from local variable position one
    iload_2 (none) pushes int from local variable position two
    iload_3 (none) pushes int from local variable position three
    fload vindex pushes float from local variable position vindex
    fload_0 (none) pushes float from local variable position zero
    fload_1 (none) pushes float from local variable position one
    fload_2 (none) pushes float from local variable position two
    fload_3 (none) pushes float from local variable position three

    接下來(lái)的這張表,列出了把long和double型本地變量壓入棧中的指令。這些指令把64位的數(shù)從棧幀的本地變量去移動(dòng)到操作數(shù)區(qū)。

    操作碼
    操作數(shù)
    描述
    lload vindex pushes long from local variable positions vindex and (vindex + 1)
    lload_0 (none) pushes long from local variable positions zero and one
    lload_1 (none) pushes long from local variable positions one and two
    lload_2 (none) pushes long from local variable positions two and three
    lload_3 (none) pushes long from local variable positions three and four
    dload vindex pushes double from local variable positions vindex and (vindex + 1)
    dload_0 (none) pushes double from local variable positions zero and one
    dload_1 (none) pushes double from local variable positions one and two
    dload_2 (none) pushes double from local variable positions two and three
    dload_3 (none) pushes double from local variable positions three and four

    最后一組操作碼,把32位的對(duì)象引用從棧幀的本地變量區(qū)移動(dòng)到操作數(shù)區(qū)。如下表:

    操作碼
    操作數(shù)
    描述
    aload vindex pushes object reference from local variable position vindex
    aload_0 (none) pushes object reference from local variable position zero
    aload_1 (none) pushes object reference from local variable position one
    aload_2 (none) pushes object reference from local variable position two
    aload_3 (none) pushes object reference from local variable position three

    彈出到本地變量

    每一個(gè)將局部變量壓入棧中的操作碼,都有一個(gè)對(duì)應(yīng)的負(fù)責(zé)彈出棧頂元素到本地變量中的操作碼。這些操作碼的名字可以通過(guò)替換入棧操作碼名中的“load”為“store”得到。下表列出了將int和float型數(shù)值彈出操作數(shù)棧到本地變量中的操作碼。這些操作碼將一個(gè)32位的值從棧頂移動(dòng)到本地變量中。

    操作碼
    操作數(shù)
    描述
    istore vindex pops int to local variable position vindex
    istore_0 (none) pops int to local variable position zero
    istore_1 (none) pops int to local variable position one
    istore_2 (none) pops int to local variable position two
    istore_3 (none) pops int to local variable position three
    fstore vindex pops float to local variable position vindex
    fstore_0 (none) pops float to local variable position zero
    fstore_1 (none) pops float to local variable position one
    fstore_2 (none) pops float to local variable position two
    fstore_3 (none) pops float to local variable position three

    下一張表中,展示了負(fù)責(zé)將long和double類型數(shù)值出棧并存到局部變量的字節(jié)碼指令,這些指令將64位的值從操作數(shù)棧頂移動(dòng)到本地變量中。

    操作碼
    操作數(shù)
    描述
    lstore vindex pops long to local variable positions vindex and (vindex + 1)
    lstore_0 (none) pops long to local variable positions zero and one
    lstore_1 (none) pops long to local variable positions one and two
    lstore_2 (none) pops long to local variable positions two and three
    lstore_3 (none) pops long to local variable positions three and four
    dstore vindex pops double to local variable positions vindex and (vindex + 1)
    dstore_0 (none) pops double to local variable positions zero and one
    dstore_1 (none) pops double to local variable positions one and two
    dstore_2 (none) pops double to local variable positions two and three
    dstore_3 (none) pops double to local variable positions three and four

    最后一組操作碼,負(fù)責(zé)將32位的對(duì)象引用從操作數(shù)棧頂移動(dòng)到本地變量中。

    操作碼
    操作數(shù)
    描述
    astore vindex pops object reference to local variable position vindex
    astore_0 (none) pops object reference to local variable position zero
    astore_1 (none) pops object reference to local variable position one
    astore_2 (none) pops object reference to local variable position two
    astore_3 (none) pops object reference to local variable position three

    類型轉(zhuǎn)換

    JVM中有一些操作碼用來(lái)將一種基本類型的數(shù)值轉(zhuǎn)換成另外一種。字節(jié)碼流中的轉(zhuǎn)換操作碼后面不跟操作數(shù),被轉(zhuǎn)換的值取自棧頂。JVM彈出棧頂?shù)闹担D(zhuǎn)換后再將結(jié)果壓入棧中。下表列出了在int,long,float和double間轉(zhuǎn)換的操作碼。這四種類型組合的每一個(gè)可能的轉(zhuǎn)換,都有一個(gè)對(duì)應(yīng)的操作碼。

    操作碼
    操作數(shù)
    描述
    i2l (none) converts int to long
    i2f (none) converts int to float
    i2d (none) converts int to double
    l2i (none) converts long to int
    l2f (none) converts long to float
    l2d (none) converts long to double
    f2i (none) converts float to int
    f2l (none) converts float to long
    f2d (none) converts float to double
    d2i (none) converts double to int
    d2l (none) converts double to long
    d2f (none) converts double to float

    下表列出了將int型轉(zhuǎn)換為更小類型的操作碼。不存在直接將long,float,double型轉(zhuǎn)換為比int型小的類型的操作碼。因此,像float到byte這樣的轉(zhuǎn)換,需要兩步。第一步,f2i將float轉(zhuǎn)換為int,第二步,int2byte操作碼將int轉(zhuǎn)換為byte。

    操作碼
    操作數(shù)
    描述
    int2byte (none) converts int to byte
    int2char (none) converts int to char
    int2short (none) converts int to short

    雖然存在將int轉(zhuǎn)換為更小類型(byte,short,char)的操作碼,但是不存在反向轉(zhuǎn)換的操作碼。這是因?yàn)閎yte,short和char型的數(shù)值在入棧之前會(huì)轉(zhuǎn)換成int型。byte,short和char型數(shù)值的算術(shù)運(yùn)算,首先要將這些類型的值轉(zhuǎn)為int,然后執(zhí)行算術(shù)運(yùn)算,最后得到int型結(jié)果。也就是說(shuō),如果兩個(gè)byte型的數(shù)相加,會(huì)得到一個(gè)int型的結(jié)果,如果你想要byte型的結(jié)果,你必須顯式地將int類型的結(jié)果轉(zhuǎn)換為byte類型的值。例如,下面的代碼編譯出錯(cuò):

    1. class BadArithmetic {
    2.     byte addOneAndOne() {
    3.         byte a = 1;
    4.         byte b = 1;
    5.         byte c = a + b;
    6.         return c;
    7.     }
    8. }

    javac會(huì)對(duì)上面的代碼給出如下錯(cuò)誤:

    1. BadArithmetic.java(7): Incompatible type for declaration.
    2. Explicit cast needed to convert int to byte.
    3.                 byte c = a + b;
    4.                      ^

    Java程序員必須顯式的把a(bǔ) + b的結(jié)果轉(zhuǎn)換為byte,這樣才能通過(guò)編譯。

    1. class GoodArithmetic {
    2.     byte addOneAndOne() {
    3.         byte a = 1;
    4.         byte b = 1;
    5.         byte c = (byte) (a + b);
    6.         return c;
    7.     }
    8. }

    這樣,javac會(huì)很高興的生成GoodArithmetic.class文件,它包含如下的addOneAndOne()方法的字節(jié)碼序列:

    1. iconst_1  // Push int constant 1.
    2. istore_1  // Pop into local variable 1, which is a: byte a = 1;
    3. iconst_1  // Push int constant 1 again.
    4. istore_2  // Pop into local variable 2, which is b: byte b = 1;
    5. iload_1   // Push a (a is already stored as an int in local variable 1).
    6. iload_2   // Push b (b is already stored as an int in local variable 2).
    7. iadd      // Perform addition. Top of stack is now (a + b), an int.
    8. int2byte  // Convert int result to byte (result still occupies 32 bits).
    9. istore_3  // Pop into local variable 3, which is byte c: byte c = (byte) (a + b);
    10. iload_3   // Push the value of c so it can be returned.
    11. ireturn   // Proudly return the result of the addition: return c;

    本文譯自:Bytecode basics

    原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明: 轉(zhuǎn)載自碼農(nóng)合作社
    本文鏈接地址: 字節(jié)碼基礎(chǔ):JVM字節(jié)碼初探

    posted on 2014-05-22 02:07 Rolandz 閱讀(5121) 評(píng)論(4)  編輯  收藏 所屬分類: 編程實(shí)踐

    評(píng)論

    # re: 字節(jié)碼基礎(chǔ):JVM字節(jié)碼初探 2014-05-22 05:48 萬(wàn)利鎖業(yè)

    期待更新吧  回復(fù)  更多評(píng)論   

    # re: 字節(jié)碼基礎(chǔ):JVM字節(jié)碼初探 2014-05-23 19:48 IT前線

    在學(xué)校沒(méi)學(xué)這些,工作了也沒(méi)接觸,學(xué)習(xí)了,謝謝
    www.itqx.net  回復(fù)  更多評(píng)論   

    # re: 字節(jié)碼基礎(chǔ):JVM字節(jié)碼初探 2014-05-24 22:32 asd

    在學(xué)校沒(méi)學(xué)到  回復(fù)  更多評(píng)論   

    # re: 字節(jié)碼基礎(chǔ):JVM字節(jié)碼初探 2014-05-26 11:52 手機(jī)賺錢網(wǎng)-手機(jī)賺錢軟件排行,手機(jī)賺錢平臺(tái)http://www.9izhuanqian.com

    手機(jī)賺錢網(wǎng)-手機(jī)賺錢軟件排行,手機(jī)賺錢平臺(tái) http://www.9izhuanqian.com  回復(fù)  更多評(píng)論   

    導(dǎo)航

    統(tǒng)計(jì)

    留言簿(1)

    隨筆分類(12)

    隨筆檔案(19)

    積分與排名

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

    主站蜘蛛池模板: av无码久久久久不卡免费网站| 大胆亚洲人体视频| 亚洲第一AV网站| 亚洲av日韩综合一区二区三区| 久久这里只精品热免费99| 日韩免费无砖专区2020狼| 亚洲视频免费一区| 中文字幕在线免费看| 国产一区二区免费在线| 亚洲人成人77777网站不卡 | 国产成人亚洲精品无码AV大片| 84pao国产成视频免费播放| 久久精品夜色噜噜亚洲A∨| 亚洲欧美成人av在线观看| 四虎成年永久免费网站| 亚洲av永久无码精品漫画| 四虎国产精品成人免费久久| 成年女人毛片免费播放人| 亚洲精品中文字幕麻豆| 国产免费爽爽视频在线观看| 亚洲国产精品激情在线观看| 亚洲国产成人久久综合| 国产卡一卡二卡三免费入口| 久久久亚洲欧洲日产国码二区| 三上悠亚在线观看免费| 亚洲精品tv久久久久久久久久| 亚洲国产精品无码观看久久| 97无码免费人妻超级碰碰碰碰| 亚洲网站免费观看| 亚洲成人免费在线| 亚洲精品乱码久久久久久自慰| 一级毛片a免费播放王色电影 | 日本中文一区二区三区亚洲| 亚洲乱码中文字幕在线| 九九九精品成人免费视频| 亚洲字幕在线观看| 最近在线2018视频免费观看| 亚洲国产一区二区a毛片| 成全视频免费观看在线看| 亚洲成AV人在线播放无码| 手机看片国产免费永久|