Java對(duì)象內(nèi)存布局

博客鏈接:http://www.ideabuffer.cn/2017/05/06/Java對(duì)象內(nèi)存布局/


我們知道在Java中基本數(shù)據(jù)類型的大小,例如int類型占4個(gè)字節(jié)、long類型占8個(gè)字節(jié),那么Integer對(duì)象和Long對(duì)象會(huì)占用多少內(nèi)存呢?本文介紹一下Java對(duì)象在堆中的內(nèi)存結(jié)構(gòu)以及對(duì)象大小的計(jì)算。

對(duì)象的內(nèi)存布局

一個(gè)Java對(duì)象在內(nèi)存中包括對(duì)象頭、實(shí)例數(shù)據(jù)和補(bǔ)齊填充3個(gè)部分:

對(duì)象頭

  • Mark Word:包含一系列的標(biāo)記位,比如輕量級(jí)鎖的標(biāo)記位,偏向鎖標(biāo)記位等等。在32位系統(tǒng)占4字節(jié),在64位系統(tǒng)中占8字節(jié);
  • Class Pointer:用來(lái)指向?qū)ο髮?duì)應(yīng)的Class對(duì)象(其對(duì)應(yīng)的元數(shù)據(jù)對(duì)象)的內(nèi)存地址。在32位系統(tǒng)占4字節(jié),在64位系統(tǒng)中占8字節(jié);
  • Length:如果是數(shù)組對(duì)象,還有一個(gè)保存數(shù)組長(zhǎng)度的空間,占4個(gè)字節(jié);

對(duì)象實(shí)際數(shù)據(jù)

對(duì)象實(shí)際數(shù)據(jù)包括了對(duì)象的所有成員變量,其大小由各個(gè)成員變量的大小決定,比如:byte和boolean是1個(gè)字節(jié),short和char是2個(gè)字節(jié),int和float是4個(gè)字節(jié),long和double是8個(gè)字節(jié),reference是4個(gè)字節(jié)(64位系統(tǒng)中是8個(gè)字節(jié))。

Primitive Type Memory Required(bytes)
boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8

對(duì)于reference類型來(lái)說,在32位系統(tǒng)上占用4bytes, 在64位系統(tǒng)上占用8bytes。

對(duì)齊填充

Java對(duì)象占用空間是8字節(jié)對(duì)齊的,即所有Java對(duì)象占用bytes數(shù)必須是8的倍數(shù)。例如,一個(gè)包含兩個(gè)屬性的對(duì)象:int和byte,這個(gè)對(duì)象需要占用8+4+1=13個(gè)字節(jié),這時(shí)就需要加上大小為3字節(jié)的padding進(jìn)行8字節(jié)對(duì)齊,最終占用大小為16個(gè)字節(jié)。

注意:以上對(duì)64位操作系統(tǒng)的描述是未開啟指針壓縮的情況,關(guān)于指針壓縮會(huì)在下文中介紹。

對(duì)象頭占用空間大小

這里說明一下32位系統(tǒng)和64位系統(tǒng)中對(duì)象所占用內(nèi)存空間的大?。?/p>

  • 在32位系統(tǒng)下,存放Class Pointer的空間大小是4字節(jié),MarkWord是4字節(jié),對(duì)象頭為8字節(jié);
  • 在64位系統(tǒng)下,存放Class Pointer的空間大小是8字節(jié),MarkWord是8字節(jié),對(duì)象頭為16字節(jié);
  • 64位開啟指針壓縮的情況下,存放Class Pointer的空間大小是4字節(jié),MarkWord是8字節(jié),對(duì)象頭為12字節(jié);
  • 如果是數(shù)組對(duì)象,對(duì)象頭的大小為:數(shù)組對(duì)象頭8字節(jié)+數(shù)組長(zhǎng)度4字節(jié)+對(duì)齊4字節(jié)=16字節(jié)。其中對(duì)象引用占4字節(jié)(未開啟指針壓縮的64位為8字節(jié)),數(shù)組MarkWord為4字節(jié)(64位未開啟指針壓縮的為8字節(jié));
  • 靜態(tài)屬性不算在對(duì)象大小內(nèi)。

指針壓縮

從上文的分析中可以看到,64位JVM消耗的內(nèi)存會(huì)比32位的要多大約1.5倍,這是因?yàn)閷?duì)象指針在64位JVM下有更寬的尋址。對(duì)于那些將要從32位平臺(tái)移植到64位的應(yīng)用來(lái)說,平白無(wú)辜多了1/2的內(nèi)存占用,這是開發(fā)者不愿意看到的。

從JDK 1.6 update14開始,64位的JVM正式支持了 -XX:+UseCompressedOops 這個(gè)可以壓縮指針,起到節(jié)約內(nèi)存占用的新參數(shù)。

什么是OOP?

OOP的全稱為:Ordinary Object Pointer,就是普通對(duì)象指針。啟用CompressOops后,會(huì)壓縮的對(duì)象:

  • 每個(gè)Class的屬性指針(靜態(tài)成員變量);
  • 每個(gè)對(duì)象的屬性指針;
  • 普通對(duì)象數(shù)組的每個(gè)元素指針。

當(dāng)然,壓縮也不是所有的指針都會(huì)壓縮,對(duì)一些特殊類型的指針,JVM是不會(huì)優(yōu)化的,例如指向PermGen的Class對(duì)象指針、本地變量、堆棧元素、入?yún)?、返回值和NULL指針不會(huì)被壓縮。

啟用指針壓縮

在Java程序啟動(dòng)時(shí)增加JVM參數(shù):-XX:+UseCompressedOops來(lái)啟用。

注意:32位HotSpot VM是不支持UseCompressedOops參數(shù)的,只有64位HotSpot VM才支持。

本文中使用的是JDK 1.8,默認(rèn)該參數(shù)就是開啟的。

查看對(duì)象的大小

接下來(lái)我們使用http://www.javamex.com/中提供的classmexer.jar來(lái)計(jì)算對(duì)象的大小。

運(yùn)行環(huán)境:JDK 1.8,Java HotSpot(TM) 64-Bit Server VM

基本數(shù)據(jù)類型

對(duì)于基本數(shù)據(jù)類型來(lái)說,是比較簡(jiǎn)單的,因?yàn)槲覀円呀?jīng)知道每個(gè)基本數(shù)據(jù)類型的大小。代碼如下:

/**
 * VM options:
 * -javaagent:/Users/sangjian/dev/source-files/classmexer-0_03/classmexer.jar
 * -XX:+UseCompressedOops
 */
public class TestObjectSize {


    int a;
    long b;
    static int c;

    public static void main(String[] args) throws IOException {
        TestObjectSize testObjectSize = new TestObjectSize();
        // 打印對(duì)象的shallow size
        System.out.println("Shallow Size: " + MemoryUtil.memoryUsageOf(testObjectSize) + " bytes");
        // 打印對(duì)象的 retained size
        System.out.println("Retained Size: " + MemoryUtil.deepMemoryUsageOf(testObjectSize) + " bytes");
        System.in.read();
    }
}

注意:在運(yùn)行前需要設(shè)置javaagent參數(shù),在JVM啟動(dòng)參數(shù)中添加-javaagent:/path_to_agent/classmexer.jar來(lái)運(yùn)行。

有關(guān)Shallow Size和Retained Size請(qǐng)參考http://blog.csdn.net/e5945/article/details/7708253。

開啟指針壓縮的情況

運(yùn)行查看結(jié)果:

Shallow Size: 24 bytes
Retained Size: 24 bytes

根據(jù)上文的分析可以知道,64位開啟指針壓縮的情況下:

  • 對(duì)象頭大小=Class Pointer的空間大小為4字節(jié)+MarkWord為8字節(jié)=12字節(jié);
  • 實(shí)際數(shù)據(jù)大小=int類型4字節(jié)+long類型8字節(jié)=12字節(jié)(靜態(tài)變量不在計(jì)算范圍之內(nèi))

在MAT中分析的結(jié)果如下:

所以大小是24字節(jié)。其實(shí)這里并沒有padding,因?yàn)檎檬?4字節(jié)。如果我們把long b;換成int b;之后,再來(lái)看一下結(jié)果:

Shallow Size: 24 bytes
Retained Size: 24 bytes

大小并沒有變化,說明這里做了padding,并且padding的大小是4字節(jié)。

這里的Shallow Size和Retained Size是一樣的,因?yàn)槎际腔緮?shù)據(jù)類型。

關(guān)閉指針壓縮的情況

如果要關(guān)閉指針壓縮,在JVM參數(shù)中添加-XX:-UseCompressedOops來(lái)關(guān)閉,再運(yùn)行上述代碼查看結(jié)果:

Shallow Size: 24 bytes
Retained Size: 24 bytes

分析一下在64位未開啟指針壓縮的情況下:

  • 對(duì)象頭大小=Class Pointer的空間大小為8字節(jié)+MarkWord為8字節(jié)=16字節(jié);
  • 實(shí)際數(shù)據(jù)大小=int類型4字節(jié)+long類型8字節(jié)=12字節(jié)(靜態(tài)變量不在計(jì)算范圍之內(nèi));

這里計(jì)算后大小為16+12=28字節(jié),這時(shí)候就需要padding來(lái)補(bǔ)齊了,所以padding為4字節(jié),最后的大小就是32字節(jié)。

我們?cè)侔?code>long b;換成int b;之后呢?通過上面的計(jì)算結(jié)果可以知道,實(shí)際數(shù)據(jù)大小就應(yīng)該是int類型4字節(jié)+int類型4字節(jié)=8字節(jié),對(duì)象頭大小為16字節(jié),那么不需要做padding,對(duì)象的大小為24字節(jié):

Shallow Size: 24 bytes
Retained Size: 24 bytes

數(shù)組類型

64位系統(tǒng)中,數(shù)組對(duì)象的對(duì)象頭占用24 bytes,啟用壓縮后占用16字節(jié)。比普通對(duì)象占用內(nèi)存多是因?yàn)樾枰~外的空間存儲(chǔ)數(shù)組的長(zhǎng)度?;A(chǔ)數(shù)據(jù)類型數(shù)組占用的空間包括數(shù)組對(duì)象頭以及基礎(chǔ)數(shù)據(jù)類型數(shù)據(jù)占用的內(nèi)存空間。由于對(duì)象數(shù)組中存放的是對(duì)象的引用,所以數(shù)組對(duì)象的Shallow Size=數(shù)組對(duì)象頭+length * 引用指針大小,Retained Size=Shallow Size+length*每個(gè)元素的Retained Size。

代碼如下:

/**
 * VM options:
 * -javaagent:/Users/sangjian/dev/source-files/classmexer-0_03/classmexer.jar
 * -XX:+UseCompressedOops
 */
public class TestObjectSize {


    long[] arr = new long[6];

    public static void main(String[] args) throws IOException {
        TestObjectSize testObjectSize = new TestObjectSize();
        // 打印對(duì)象的shallow size
        System.out.println("Shallow Size: " + MemoryUtil.memoryUsageOf(testObjectSize) + " bytes");
        // 打印對(duì)象的 retained size
        System.out.println("Retained Size: " + MemoryUtil.deepMemoryUsageOf(testObjectSize) + " bytes");
        System.in.read();
    }
}

開啟指針壓縮的情況

結(jié)果如下:

Shallow Size: 16 bytes
Retained Size: 80 bytes

Shallow Size比較簡(jiǎn)單,這里對(duì)象頭大小為12字節(jié), 實(shí)際數(shù)據(jù)大小為4字節(jié),所以Shallow Size為16。

對(duì)于Retained Size來(lái)說,要計(jì)算數(shù)組占用的大小,對(duì)于數(shù)組來(lái)說,它的對(duì)象頭部多了一個(gè)用來(lái)存儲(chǔ)數(shù)組長(zhǎng)度的空間,該空間大小為4字節(jié),所以數(shù)組對(duì)象的大小=引用對(duì)象頭大小12字節(jié)+存儲(chǔ)數(shù)組長(zhǎng)度的空間大小4字節(jié)+數(shù)組的長(zhǎng)度數(shù)組中對(duì)象的Retained Size+padding大小*

下面分析一下上述代碼中的long[] arr = new long[6];,它是一個(gè)長(zhǎng)度為6的long類型的數(shù)組,由于long類型的大小為8字節(jié),所以數(shù)組中的實(shí)際數(shù)據(jù)是68=48字節(jié),那么數(shù)組對(duì)象的大小=12+4+68+0=64,最終的Retained Size=Shallow Size + 數(shù)組對(duì)象大小=16+64=80。

通過MAT查看如下:

關(guān)閉指針壓縮的情況

結(jié)果如下:

Shallow Size: 24 bytes
Retained Size: 96 bytes

這個(gè)結(jié)果大家應(yīng)該能自己分析出來(lái)了,因?yàn)檫@時(shí)引用對(duì)象頭為16字節(jié),那么數(shù)組的大小=16+4+6*8+4=72,(這里最后一個(gè)4是padding),所以Retained Size=Shallow Size + 數(shù)組對(duì)象大小=24+72=96。

通過MAT查看如下:

包裝類型

包裝類(Boolean/Byte/Short/Character/Integer/Long/Double/Float)占用內(nèi)存的大小等于對(duì)象頭大小加上底層基礎(chǔ)數(shù)據(jù)類型的大小。

包裝類型的Retained Size占用情況如下:

Numberic Wrappers +useCompressedOops -useCompressedOops
Byte, Boolean 16 bytes 24 bytes
Short, Character 16 bytes 24 bytes
Integer, Float 16 bytes 24 bytes
Long, Double 24 bytes 24 bytes

代碼如下:

/**
 * VM options:
 * -javaagent:/Users/sangjian/dev/source-files/classmexer-0_03/classmexer.jar
 * -XX:+UseCompressedOops
 */
public class TestObjectSize {


    Boolean a = new Boolean(false);
    Byte b = new Byte("1");
    Short c = new Short("1");
    Character d = new Character('a');
    Integer e = new Integer(1);
    Float f = new Float(2.5);
    Long g = new Long(123L);
    Double h = new Double(2.5D);

    public static void main(String[] args) throws IOException {
        TestObjectSize testObjectSize = new TestObjectSize();
        // 打印對(duì)象的shallow size
        System.out.println("Shallow Size: " + MemoryUtil.memoryUsageOf(testObjectSize) + " bytes");
        // 打印對(duì)象的 retained size
        System.out.println("Retained Size: " + MemoryUtil.deepMemoryUsageOf(testObjectSize) + " bytes");
        System.in.read();
    }
}

開啟指針壓縮的情況

結(jié)果如下:

Shallow Size: 48 bytes
Retained Size: 192 bytes

MAT中的結(jié)果如下:

關(guān)閉指針壓縮的情況

結(jié)果如下:

Shallow Size: 80 bytes
Retained Size: 272 bytes

MAT中的結(jié)果如下:

String類型

在JDK1.7及以上版本中,java.lang.String中包含2個(gè)屬性,一個(gè)用于存放字符串?dāng)?shù)據(jù)的char[], 一個(gè)int類型的hashcode, 部分源代碼如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
    ...
}

因此,在關(guān)閉指針壓縮時(shí),一個(gè)String對(duì)象的大小為:

  • Shallow Size=對(duì)象頭大小16字節(jié)+int類型大小4字節(jié)+數(shù)組引用大小8字節(jié)+padding4字節(jié)=32字節(jié);

  • Retained Size=Shallow Size+char數(shù)組的Retained Size

在開啟指針壓縮時(shí),一個(gè)String對(duì)象的大小為:

  • Shallow Size=對(duì)象頭大小12字節(jié)+int類型大小4字節(jié)+數(shù)組引用大小4字節(jié)+padding4字節(jié)=24字節(jié);

  • Retained Size=Shallow Size+char數(shù)組的Retained Size

代碼如下:

/**
 * VM options:
 * -javaagent:/Users/sangjian/dev/source-files/classmexer-0_03/classmexer.jar
 * -XX:+UseCompressedOops
 */
public class TestObjectSize {


    String s = "test";

    public static void main(String[] args) throws IOException {
        TestObjectSize testObjectSize = new TestObjectSize();
        // 打印對(duì)象的shallow size
        System.out.println("Shallow Size: " + MemoryUtil.memoryUsageOf(testObjectSize) + " bytes");
        // 打印對(duì)象的 retained size
        System.out.println("Retained Size: " + MemoryUtil.deepMemoryUsageOf(testObjectSize) + " bytes");
        System.in.read();
    }
}

開啟指針壓縮的情況

結(jié)果如下:

Shallow Size: 16 bytes
Retained Size: 64 bytes

MAT中的結(jié)果如下:

關(guān)閉指針壓縮的情況

結(jié)果如下:

Shallow Size: 24 bytes
Retained Size: 88 bytes

MAT中的結(jié)果如下:

其他引用類型的大小

根據(jù)上面的分析,可以計(jì)算出一個(gè)對(duì)象在內(nèi)存中的占用空間大小情況,其他的引用類型可以參考分析計(jì)算過程來(lái)計(jì)算內(nèi)存的占用情況。

關(guān)于padding

思考這樣一個(gè)問題,是不是padding都加到對(duì)象的后面呢,如果對(duì)象頭占12個(gè)字節(jié),對(duì)象中只有1個(gè)long類型的變量,那么該long類型的變量的偏移起始地址是在12嗎?用下面一段代碼測(cè)試一下:

@SuppressWarnings("ALL")
public class PaddingTest {

    long a;

    private static Unsafe UNSAFE;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws NoSuchFieldException {
        System.out.println(UNSAFE.objectFieldOffset(PaddingTest.class.getDeclaredField("a")));
    }

}

這里使用Unsafe類來(lái)查看變量的偏移地址,運(yùn)行后結(jié)果如下:

16

如果是換成int類型的變量呢?結(jié)果是12。

現(xiàn)在一般的CPU一次直接操作的數(shù)據(jù)可以到64位,也就是8個(gè)字節(jié),那么字長(zhǎng)就是64,而long類型本身就是占64位,如果這時(shí)偏移地址是12,那么需要分兩次讀取該數(shù)據(jù),而如果偏移地址從16開始只需要通過一次讀取即可。int類型的數(shù)據(jù)占用4個(gè)字節(jié),所以可以從12開始。

把上面的代碼修改一下:

@SuppressWarnings("ALL")
public class PaddingTest {

    long a;

    byte b;

    byte c;

    private static Unsafe UNSAFE;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws NoSuchFieldException {
        System.out.println(UNSAFE.objectFieldOffset(PaddingTest.class.getDeclaredField("a")));
        System.out.println(UNSAFE.objectFieldOffset(PaddingTest.class.getDeclaredField("b")));
        System.out.println(UNSAFE.objectFieldOffset(PaddingTest.class.getDeclaredField("c")));
    }

}

運(yùn)行結(jié)果如下:

16
12
13

在本例中,如果變量的大小小于等于4個(gè)字節(jié),那么在分配內(nèi)存的時(shí)候會(huì)先優(yōu)先分配,因?yàn)檫@樣可以減少padding,比如這里的b和c變量;如果這時(shí)達(dá)到了16個(gè)字節(jié),那么其他的變量按照類型所占內(nèi)存的大小降序分配。

再次修改代碼:

/**
 * VM options: -javaagent:D:\source-files\classmexer.jar
 */
@SuppressWarnings("ALL")
public class PaddingTest {

    boolean a;
    byte b;

    short c;
    char d;

    int e;
    float f;

    long g;
    double h;

    private static Unsafe UNSAFE;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws NoSuchFieldException {
        System.out.println("field a --> "+ UNSAFE.objectFieldOffset(PaddingTest.class.getDeclaredField("a")));
        System.out.println("field b --> "+ UNSAFE.objectFieldOffset(PaddingTest.class.getDeclaredField("b")));
        System.out.println("field c --> "+ UNSAFE.objectFieldOffset(PaddingTest.class.getDeclaredField("c")));
        System.out.println("field d --> "+ UNSAFE.objectFieldOffset(PaddingTest.class.getDeclaredField("d")));
        System.out.println("field e --> "+ UNSAFE.objectFieldOffset(PaddingTest.class.getDeclaredField("e")));
        System.out.println("field f --> "+ UNSAFE.objectFieldOffset(PaddingTest.class.getDeclaredField("f")));
        System.out.println("field g --> "+ UNSAFE.objectFieldOffset(PaddingTest.class.getDeclaredField("g")));
        System.out.println("field h --> "+ UNSAFE.objectFieldOffset(PaddingTest.class.getDeclaredField("h")));

        PaddingTest paddingTest = new PaddingTest();

        System.out.println("Shallow Size: "+ MemoryUtil.memoryUsageOf(paddingTest));
        System.out.println("Retained Size: " + MemoryUtil.deepMemoryUsageOf(paddingTest));
    }

}

結(jié)果如下:

field a --> 40
field b --> 41
field c --> 36
field d --> 38
field e --> 12
field f --> 32
field g --> 16
field h --> 24
Shallow Size: 48
Retained Size: 48

可以看到,先分配的是int類型的變量e,因?yàn)樗檬?個(gè)字節(jié),其余的都是先從g和h變量開始分配的,因?yàn)檫@兩個(gè)變量是long類型和double類型的,占64位,最后分配的是a和b,它們只占一個(gè)字節(jié)。

如果分配到最后,這時(shí)字節(jié)數(shù)不是8的倍數(shù),則需要padding。這里實(shí)際的大小是42字節(jié),所以padding6字節(jié),最終占用48字節(jié)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 12,537評(píng)論 6 13
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,853評(píng)論 18 399
  • Redis的內(nèi)存優(yōu)化 聲明:本文內(nèi)容來(lái)自《Redis開發(fā)與運(yùn)維》一書第八章,如轉(zhuǎn)載請(qǐng)聲明。 Redis所有的數(shù)據(jù)都...
    meng_philip123閱讀 19,081評(píng)論 2 29
  • 今天以另一種方式上課,每個(gè)孩子需要得滿5個(gè)勾就進(jìn)入純玩的狀態(tài)!結(jié)果發(fā)現(xiàn)孩子們都仿佛像打了雞血一樣認(rèn)真得...,為了...
    明期特RB當(dāng)家花旦閱讀 236評(píng)論 0 0
  • 以前上高中沒覺得滿課有什么不舒服,反而覺得很充實(shí)討厭的星期五它天經(jīng)地義??墒亲詮牟饺氪髮W(xué),感覺好多課特別是周二周五...
    丫丫孩紙啊閱讀 476評(píng)論 2 2

友情鏈接更多精彩內(nèi)容