1. Pendahuluan
Java adalah platform cross-hardware bahasa pemrograman tingkat tinggi yang berorientasi objek. Program Java dijalankan di Java Virtual Machines (JVM) dan mengelola memori oleh JVM. Ini adalah perbedaan terbesar dari C ++. Meskipun memori dikelola oleh JVM, kita juga harus memahami bagaimana JVM mengelola memori. Tidak hanya ada satu JVM, dan mungkin ada lusinan mesin virtual saat ini, tetapi desain mesin virtual yang sesuai dengan spesifikasi harus mengikuti "spesifikasi mesin virtual Java". Artikel ini didasarkan pada deskripsi mesin virtual hotspot, dan akan disebutkan jika ada perbedaan dengan mesin virtual lainnya. Artikel ini terutama menjelaskan bagaimana memori didistribusikan dalam JVM, bagaimana objek program Java disimpan dan diakses, dan kemungkinan pengecualian di berbagai bidang memori.
2. Distribusi memori (wilayah) di JVM
Saat menjalankan program Java, JVM membagi memori menjadi beberapa bidang data yang berbeda untuk manajemen. Area -area ini memiliki fungsi, waktu penciptaan, dan penghancuran yang berbeda. Beberapa area dialokasikan ketika proses JVM dimulai, sementara yang lain terkait dengan siklus hidup utas pengguna (utas program itu sendiri). Menurut spesifikasi JVM, area memori yang dikelola oleh JVM dibagi menjadi area data runtime berikut:
1. Tumpukan Mesin Virtual
Area memori ini bersifat pribadi oleh utas dan dibuat saat utas dimulai dan dihancurkan saat dihancurkan. Model memori untuk eksekusi metode Java yang dijelaskan oleh tumpukan mesin virtual: Setiap metode akan membuat bingkai tumpukan (bingkai stack) pada awal eksekusi, yang digunakan untuk menyimpan tabel variabel lokal, tumpukan operan, tautan dinamis, keluar metode, dll. Eksekusi dan pengembalian setiap metode diselesaikan, dan ada bingkai tumpukan pada mesin virtual.
Seperti namanya, tabel variabel lokal adalah area memori yang menyimpan variabel lokal: ia menyimpan tipe data dasar (8 tipe data dasar Java), jenis referensi, dan alamat pengembalian yang dapat ditemukan selama periode kompiler; Jenis panjang dan ganda yang menempati 64 bit akan menempati 2 ruang variabel lokal, dan tipe data lainnya hanya menempati 1; Karena ukuran jenis ditentukan dan jumlah variabel dapat diketahui selama periode kompilasi, tabel variabel lokal memiliki ukuran yang diketahui saat dibuat. Bagian dari ruang memori ini dapat dialokasikan selama periode kompilasi, dan tidak perlu memodifikasi ukuran tabel variabel lokal selama metode berjalan.
Dalam spesifikasi mesin virtual, dua pengecualian ditentukan untuk area memori ini:
1. Jika kedalaman tumpukan yang diminta oleh utas lebih besar dari kedalaman yang diizinkan (?), Pengecualian StackOverflowError akan dilemparkan;
2. Jika mesin virtual dapat berkembang secara dinamis, ketika ekspansi tidak dapat berlaku untuk memori yang cukup, pengecualian OutOfMemory akan dilemparkan;
2. Metode Lokal Tumpukan
Tumpukan metode lokal juga adalah thread-private, dan fungsinya hampir sama dengan tumpukan mesin virtual: tumpukan mesin virtual menyediakan layanan tumpukan masuk dan keluar untuk eksekusi metode Java, sementara stack metode lokal menyediakan layanan untuk mesin virtual untuk menjalankan metode asli.
Dalam spesifikasi mesin virtual, tidak ada peraturan wajib tentang metode implementasi tumpukan metode lokal, dan dapat diimplementasikan secara bebas oleh mesin virtual tertentu; Mesin virtual hotspot secara langsung menggabungkan tumpukan mesin virtual dan metode metode lokal menjadi satu; Untuk mesin virtual lainnya untuk mengimplementasikan metode ini, pembaca dapat meminta informasi yang relevan jika mereka tertarik;
Seperti tumpukan mesin virtual, tumpukan metode lokal juga akan melempar StackOverflowError和OutOfMemory .
3. Kalkulator Program
Kalkulator program juga merupakan area memori pribadi utas. Ini dapat dianggap sebagai indikator nomor baris (menunjuk ke instruksi) untuk utas untuk menjalankan bytecode. Ketika Java dieksekusi, ia mendapatkan instruksi berikutnya untuk dieksekusi dengan mengubah nilai penghitung. Perintah eksekusi cabang, loop, lompatan, penanganan pengecualian, pemulihan benang, dll. Semua bergantung pada konter ini untuk menyelesaikannya. Multithreading mesin virtual dicapai dengan beralih secara bergantian dan mengalokasikan waktu eksekusi prosesor. Prosesor (inti untuk prosesor multi-core) hanya dapat menjalankan satu perintah pada satu waktu. Oleh karena itu, setelah utas melakukan switching, ia perlu dikembalikan ke posisi eksekusi yang benar. Setiap utas memiliki kalkulator program independen.
Saat mengeksekusi metode Java, catatan kalkulator program (poin ke) alamat instruksi bytecode yang dijalankan oleh utas saat ini. Jika metode asli dieksekusi, nilai kalkulator ini tidak ditentukan. Ini karena model utas mesin virtual hotspot adalah model utas asli, yaitu, masing -masing utas Java secara langsung memetakan utas OS (sistem operasi). Saat mengeksekusi metode asli, ia dieksekusi secara langsung oleh OS. Nilai penghitung mesin virtual ini tidak berguna; Karena kalkulator ini adalah area memori dengan ruang yang sangat kecil, pribadi, dan tidak memerlukan ekspansi. Ini adalah satu -satunya area dalam spesifikasi mesin virtual yang tidak menentukan pengecualian OutOfMemoryError .
4. Heap Memory (Heap)
Java Heap adalah area memori yang dibagikan berdasarkan utas. Dapat dikatakan bahwa itu adalah area memori terbesar yang dikelola oleh mesin virtual dan dibuat ketika mesin virtual dimulai. Memori heap java terutama menyimpan instance objek, dan hampir semua instance objek (termasuk array) disimpan di sini. Oleh karena itu, ini juga merupakan area memori utama pengumpulan sampah (GC). Konten tentang GC tidak akan dijelaskan di sini;
Menurut spesifikasi mesin virtual, memori java heap dapat dalam memori fisik yang tidak terputus. Selama secara logis kontinu dan tidak ada batasan ekspansi ruang, itu bisa berupa ukuran tetap atau pohon yang diperluas. Jika memori heap tidak memiliki ruang yang cukup untuk menyelesaikan alokasi instan dan tidak dapat diperluas, pengecualian OutOfMemoryError akan dilemparkan.
5. Area Metode
Area metode ini adalah area memori yang dibagikan berdasarkan utas, seperti memori heap, ia menyimpan informasi jenis, konstanta, variabel statis, kode yang dikompilasi selama periode kompilasi instan dan data lainnya. Spesifikasi mesin virtual tidak memiliki terlalu banyak batasan pada implementasi area metode, dan seperti heap memori, itu tidak memerlukan ruang memori fisik yang berkelanjutan, ukurannya dapat diperbaiki atau dapat diskalakan, dan juga dapat dipilih untuk tidak mengimplementasikan pengumpulan sampah; Ketika area metode tidak dapat memenuhi persyaratan alokasi memori, pengecualian OutOfMemoryError akan dilemparkan.
6. Memori Langsung
Memori langsung bukan bagian dari memori yang dikelola mesin virtual, tetapi bagian memori ini masih sering digunakan; Ketika program Java menggunakan metode asli (seperti NIO, NIO, tidak ada deskripsi yang diberikan di sini), memori dapat dialokasikan secara langsung di luar heap, tetapi ruang memori total terbatas, dan akan ada memori yang tidak mencukupi, dan pengecualian OutOfMemoryError juga akan dilemparkan.
2. Akses penyimpanan objek instance
Poin pertama di atas memiliki deskripsi umum memori di setiap area mesin virtual. Untuk setiap area, ada masalah dengan bagaimana data dibuat, ditata dan diakses. Mari kita gunakan memori heap yang paling umum digunakan sebagai contoh untuk membicarakan tiga aspek ini berdasarkan hotspot.
1. Pembuatan objek instance
Ketika mesin virtual mengeksekusi instruksi baru, pertama -tama, ia pertama -tama menempatkan referensi simbol kelas dari objek pembuatan dari kumpulan konstan, dan menilai apakah kelas telah dimuat dan diinisialisasi. Jika tidak dimuat, proses inisialisasi beban kelas akan dieksekusi (deskripsi tidak akan dibuat di sini tentang pemuatan kelas). Jika kelas ini tidak dapat ditemukan, pengecualian ClassNotFoundException umum akan dilemparkan;
Setelah pengecekan pemuatan kelas, memori fisik (Heap Memory) sebenarnya dialokasikan untuk objek. Ruang memori yang diperlukan oleh objek ditentukan oleh kelas yang sesuai. Setelah pemuatan kelas, ruang memori yang dibutuhkan oleh objek kelas ini diperbaiki; Mengalokasikan ruang memori untuk objek setara dengan membagi bagian dari tumpukan dan mengalokasikannya ke objek ini;
Menurut apakah ruang memori kontinu (dialokasikan dan tidak dialokasikan dibagi menjadi dua bagian lengkap) dibagi menjadi dua cara untuk mengalokasikan memori:
1. Memori kontinu: Pointer digunakan sebagai titik pemisah antara memori yang dialokasikan dan yang tidak dialokasikan. Alokasi memori objek hanya membutuhkan penunjuk untuk memindahkan ukuran ruang ke segmen memori yang tidak dialokasikan; Metode ini disebut "tabrakan pointer".
2. Memori Discontinuous: Mesin virtual perlu memelihara (mencatat) daftar yang merekam blok memori tersebut di tumpukan yang tidak dialokasikan. Saat mengalokasikan memori objek, pilih area memori dengan ukuran yang sesuai untuk mengalokasikannya ke objek, dan perbarui daftar ini; Metode ini disebut "Daftar Bebas".
Alokasi memori objek juga akan menghadapi masalah konkurensi. Mesin virtual menggunakan dua solusi untuk menyelesaikan masalah keamanan utas ini: Pertama, gunakan CAS (bandingkan dan setel)+ untuk mengidentifikasi dan mencoba lagi untuk memastikan atomisitas operasi alokasi; Kedua, alokasi memori dibagi menjadi ruang yang berbeda sesuai dengan utas, yaitu, masing-masing utas dialokasikan sebelumnya sepotong memori benang-privat dalam tumpukan, yang disebut buffer alokasi utas lokal (TLAB); Ketika utas itu ingin mengalokasikan memori, secara langsung dialokasikan dari TLAB. Hanya ketika TLAB utas dialokasikan setelah kembali, operasi sinkron dapat dialokasikan dari tumpukan. Solusi ini secara efektif mengurangi konkurensi memori tumpukan alokasi objek antara utas; Apakah mesin virtual menggunakan TLAB diatur melalui parameter JVM -xx: +/- usetlab.
Setelah menyelesaikan alokasi memori, selain informasi header objek, mesin virtual menginisialisasi ruang memori yang dialokasikan ke nilai nol untuk memastikan bahwa bidang instance objek dapat langsung digunakan ke nilai nol yang sesuai dengan tipe data tanpa menetapkan nilai; Kemudian, jalankan metode init untuk menyelesaikan inisialisasi sesuai dengan kode sebelum pembuatan objek instan selesai;
2. Tata letak objek dalam memori
Di mesin virtual hotspot, objek dibagi menjadi tiga bagian dalam memori: header objek, data instance, dan penyelarasan dan pengisian:
Header objek dibagi menjadi dua bagian: bagian dari itu menyimpan data runtime objek, termasuk kode hash, usia pembuatan pengumpulan sampah, status kunci objek, kunci penahan ulir, ID utas yang bias, cap waktu yang bias, dll.; Dalam mesin virtual 32-bit dan 64-bit, bagian data ini masing-masing menempati 32-bit dan 64-bit; Karena ada banyak data runtime, 32-bit atau 64-bit tidak cukup untuk sepenuhnya menyimpan semua data, sehingga bagian ini dirancang untuk menyimpan data runtime dalam format yang tidak ditetapkan, tetapi menggunakan bit yang berbeda untuk menyimpan data sesuai dengan keadaan objek; Bagian lain menyimpan penunjuk tipe objek, menunjuk ke kelas objek ini, tetapi ini tidak perlu, dan metadata kelas objek tidak perlu ditentukan menggunakan bagian penyimpanan ini (akan dibahas di bawah);
Data contoh adalah konten dari berbagai jenis data yang ditentukan oleh objek, dan data yang ditentukan oleh program -program ini tidak disimpan dalam urutan yang ditentukan. Mereka ditentukan dalam urutan kebijakan dan definisi alokasi mesin virtual: panjang/ganda, int, pendek/char, byte/boolean, oOP (objek biasa ponint) , dapat dilihat bahwa kebijakan dialokasikan sesuai dengan jumlah placeholder dari jenis, dan jenis yang sama akan mengalokasikan memori bersama; dan, di bawah kepuasan kondisi ini, urutan variabel kelas induk didahului oleh subkelas;
Bagian pengisian objek tidak selalu ada. Ini hanya berperan dalam penyelarasan placeholder. Dalam Hotspot Virtual Machine Memory Management dikelola dalam satuan 8 byte. Oleh karena itu, ketika memori dialokasikan, ukuran objek bukan kelipatan 8, dan pengisian perataan selesai;
3. Akses Objek <BR /> Dalam program Java, kami membuat objek, dan bahkan kami mendapatkan variabel tipe referensi, yang melaluinya kami benar -benar mengoperasikan instance dalam memori heap; Dalam spesifikasi mesin virtual, hanya ditetapkan bahwa tipe referensi adalah referensi yang menunjuk ke objek, dan tidak menentukan bagaimana referensi ini menempatkan dan mengakses instance di heap; Saat ini, di mesin virtual utama, ada dua cara utama untuk mengimplementasikan akses objek:
1. Metode pegangan: Suatu wilayah dibagi menjadi banyak memori sebagai kumpulan pegangan. Variabel referensi menyimpan alamat pegangan objek, dan pegangan menyimpan informasi alamat spesifik dari objek sampel dan jenis objek. Oleh karena itu, header objek tidak dapat berisi jenis objek:
2. Akses Langsung ke Pointer: Jenis referensi secara langsung menyimpan informasi alamat objek instan di heap, tetapi ini mensyaratkan bahwa tata letak objek instan harus berisi jenis objek:
Dua metode akses ini memiliki keunggulannya sendiri: Ketika alamat objek diubah (penyortiran memori, pengumpulan sampah), objek akses pegangan, variabel referensi tidak perlu diubah, tetapi hanya nilai alamat objek dalam pegangan diubah; Saat menggunakan metode akses langsung pointer, semua referensi dari objek ini perlu dimodifikasi; Tetapi metode pointer dapat mengurangi satu operasi pengalamatan, dan dalam hal sejumlah besar akses objek, keuntungan dari metode ini lebih jelas; Mesin virtual Hotspot menggunakan metode akses langsung pointer ini.
3. Pengecualian Memori Runtime
Ada dua pengecualian utama yang mungkin terjadi ketika berjalan dalam program Java: OutofmemoryError dan Stackoverflowerror; Apa yang akan terjadi di area memori itu? Seperti yang disebutkan secara singkat sebelumnya, kecuali untuk penghitung program, area memori lainnya akan terjadi; Bagian ini terutama menunjukkan pengecualian di setiap area memori melalui kode instan, dan banyak parameter startup mesin virtual yang umum digunakan akan digunakan untuk lebih menjelaskan situasi. (Cara menjalankan program dengan parameter tidak dijelaskan di sini)
1. Java Heap memori overflow
Heap memori overflow terjadi ketika objek dibuat setelah kapasitas tumpukan mencapai kapasitas tumpukan maksimum. Dalam program ini, objek dibuat terus menerus dan objek -objek ini dijamin tidak dikumpulkan sampah:
/** * Parameter mesin virtual: * -xms20m Kapasitas tumpukan minimum * -xmx20m Kapasitas tumpukan maksimum * @author hwz * */headoutofmemoryError {public static void main (string [] args) {// menggunakan wadah untuk menyimpan objek untuk memastikan bahwa objek bukanlah tempat yang dikumpulkan <] ArrayList <HeadoutOfMemoryError> (); while (true) {// terus -menerus membuat objek dan menambahkannya ke wadah listtoHoldObj.add (headoutofmemoryError baru ()); }}} Anda dapat menambahkan parameter mesin virtual :-XX:HeapDumpOnOutOfMemoryError . Saat mengirim pengecualian oom, biarkan mesin virtual membuang file snapshot dari tumpukan saat ini. Anda dapat menggunakan masalah Pengecualian Segmentasi Word File ini di masa mendatang. Ini tidak akan dijelaskan secara rinci. Saya akan menulis blog untuk menjelaskan secara rinci menggunakan alat MAT untuk menganalisis masalah memori.
2. Tumpukan Mesin Virtual dan Stack Metode Lokal meluap
Di mesin virtual hotspot, kedua tumpukan metode ini tidak diimplementasikan bersama. Menurut spesifikasi mesin virtual, kedua pengecualian ini akan terjadi di dua area memori ini:
1. Jika utas meminta kedalaman tumpukan lebih besar dari kedalaman maksimum yang diizinkan oleh mesin virtual, lempar pengecualian stackoverflowerror;
2. Jika mesin virtual tidak dapat berlaku untuk ruang memori yang besar saat memperluas ruang tumpukan, pengecualian outofmemoryerror akan dilemparkan;
Sebenarnya ada tumpang tindih antara kedua situasi ini: ketika ruang tumpukan tidak dapat dialokasikan, apakah tidak mungkin untuk membedakan apakah memori terlalu kecil atau kedalaman tumpukan yang digunakan terlalu besar.
Gunakan dua cara untuk menguji kode
1. Gunakan parameter -XSS untuk mengurangi ukuran tumpukan, panggil metode secara rekursif tanpa batas, dan tingkatkan kedalaman tumpukan secara tak terbatas:
/** * Parameter mesin virtual: <br> * -xss128k kapasitas tumpukan * @author hwz * */kelas publik stackoverflowerror {private int stackdeep = 1; / *** rekursi tak terbatas, memperbesar kedalaman tumpukan panggilan*/ public void recursiveInvoke () {stackdeep ++; RecursiveInvoke (); } public static void main (string [] args) {stackoverflowerror soe = stackoverflowerror () baru; coba {soe.recursiveInvoke (); } catch (Throwable e) {System.out.println ("Stack Deep =" + soe.stackdeep); lempar e; }}} Sejumlah besar variabel lokal didefinisikan dalam metode ini, panjang tabel variabel lokal dalam tumpukan metode juga disebut tak terbatas secara rekursif:
/** * @author hwz * */kelas publik stackoomeError {private int stackdeep = 1; / *** Tentukan sejumlah besar variabel lokal, tingkatkan tabel variabel lokal dalam rekursi tumpukan* tak terbatas, tingkatkan kedalaman tumpukan panggilan*/ public void recursiveInvoke () {double i; I2 ganda; //........ sejumlah besar definisi variabel dihilangkan di sini Stackdeep ++; RecursiveInvoke (); } public static void main (string [] args) {stackoomeError soe = new stackoomeError (); coba {soe.recursiveInvoke (); } catch (Throwable e) {System.out.println ("Stack Deep =" + soe.stackdeep); lempar e; }}}Tes kode di atas menunjukkan bahwa tidak peduli apakah tumpukan bingkai terlalu besar atau kapasitas mesin virtual terlalu kecil, ketika memori tidak dapat dialokasikan, semua stackoverflowerror dilemparkan;
3. Metode Area dan Runtime Constant Pool Overflow
Di sini pertama -tama kita akan menjelaskan metode intern string: Jika kumpulan string konstan sudah berisi string yang sama dengan objek string ini, itu akan mengembalikan objek string yang mewakili string ini. Jika tidak, tambahkan objek string ini ke kumpulan konstan dan kembalikan referensi ke objek string ini; Melalui metode ini, ia akan terus menambahkan objek string ke kumpulan konstan, menghasilkan overflow:
/** * Parameter Mesin Virtual: <br> * -xx: Permsize = 10m Ukuran Area Permanen * -xx: MaxperMSize = 10m Area Permanen Kapasitas Maksimum * @Author Hwz * */Kelas Publik RuntimeconstancePooloom {Public Static Main (String [] args) {// Penggunaan Contacing = Public Obyrage ArrayList <String> (); // Gunakan metode string.intern untuk menambahkan objek kumpulan konstan untuk (int i = 1; true; i ++) {list.add (string.valueof (i) .intern ()); }}}Namun, kode pengujian ini tidak meluap selama runtime constant pool di JDK1.7, tetapi itu akan terjadi di JDK1.6. Untuk alasan ini, tulis kode tes lain untuk memverifikasi masalah ini:
/** * Metode String.Intern diuji di bawah JDK yang berbeda * @Author hwz * */kelas publik StringinternTest {public static void main (string [] args) {string str1 = stringBuilder baru ("test"). Append ("01"). Tostring (); System.out.println (str1.intern () == str1); String str2 = new stringBuilder ("test"). Append ("02"). ToString (); System.out.println (str2.intern () == str2); }} Hasil berjalan di bawah JDK1.6 adalah: false, false;
Hasil berjalan di bawah JDK1.7 adalah: Benar, Benar;
Ternyata dalam JDK1.6, metode magang () menyalin instance string pertama yang ditemui ke generasi permanen, yang pada gilirannya merupakan referensi ke instance dalam generasi permanen, dan instance string yang dibuat oleh StringBuilder berada di tumpukan, sehingga tidak sama;
Dalam JDK1.7, metode magang () tidak menyalin instance, tetapi hanya mencatat referensi instance pertama yang muncul di kumpulan konstan. Oleh karena itu, referensi yang dikembalikan oleh magang sama dengan instance yang dibuat oleh StringBuilder, sehingga mengembalikan true;
Oleh karena itu, kode uji untuk overflow pool konstan tidak akan memiliki pengecualian overflow pool konstan, tetapi mungkin memiliki pengecualian overflow memori heap yang tidak mencukupi setelah berjalan terus menerus;
Maka Anda perlu menguji luapan area metode, terus menambahkan hal -hal ke area metode, seperti nama kelas, pengubah akses, kumpulan konstan, dll. Kita dapat membiarkan program memuat sejumlah besar kelas untuk terus mengisi area metode, yang mengarah ke overflow. Kami menggunakan CGLIB untuk secara langsung memanipulasi bytecode untuk menghasilkan sejumlah besar kelas dinamis:
/** * Metode area memori overflow kelas tes * @author hwz * */kelas publik methodareoom {public static void main (string [] args) {// Gunakan gclib untuk membuat subkelas tanpa batas sementara (true) {penambah penambah = penambah baru (); Enhancer.setsuperclass (maoomclass.class); Enhancer.setusecache (false); Enhancer.setCallback (MethodInterceptor baru () {@Override Public Object Intercept (Object Obj, Metode Metode, Objek [] args, MethodProxy Proxy) melempar Throwable {return proxy.invokeSuper (OBJ, args);}}); Enhancer.create (); }} kelas statis maoomclass {}} Melalui pengamatan VisualVM, kita dapat melihat bahwa jumlah kelas yang dimuat JVM meningkat dalam garis lurus dengan penggunaan Pergen:
4. Lapangan memori langsung
Ukuran memori langsung dapat diatur melalui parameter mesin virtual : -xx: maxDirectMemorySize . Untuk membuat overflow memori langsung, Anda hanya perlu terus berlaku untuk memori langsung. Berikut ini sama dengan tes cache memori langsung di Java Nio:
/** * Parameter mesin virtual: <br> * -xx: maxdirectMemorySize = 30m ukuran memori langsung * @author hwz * */kelas publik DirectMeMoryoom {public static void main (string [] args) {list <buffer> buffer = new arraylist <buffer> (); int i = 0; while (true) {// cetak sistem saat ini.out.println (++ i); // Konsumsi memori langsung dengan terus menerapkan konsumsi memori buffer langsung di buffer cache. // Akuntansi 1m setiap kali}}} Dalam loop, setiap kali memori langsung 1m diterapkan, memori langsung maksimum diatur ke 30m, dan pengecualian dilemparkan ketika program berjalan 31 kali: java.lang.OutOfMemoryError: Direct buffer memory
4. Ringkasan
Di atas adalah semua konten artikel ini. Artikel ini terutama menjelaskan struktur tata letak memori, penyimpanan objek, dan pengecualian memori yang dapat terjadi di berbagai area memori di JVM; Buku referensi utama "Pemahaman mendalam tentang Java Virtual Machine (Edisi Kedua)". Jika ada keraguan, silakan tunjukkan di komentar; Terima kasih atas dukungan Anda untuk wulin.com.