Di mana kumpulan konstan String Java berada, heap atau stack?

104

Saya tahu konsep kumpulan konstanta dan kumpulan konstanta String yang digunakan oleh JVM untuk menangani literal String. Tapi saya tidak tahu jenis memori yang digunakan oleh JVM untuk menyimpan literal konstan String. Tumpukan atau tumpukan? Karena ini literal yang tidak terkait dengan contoh apa pun, saya akan berasumsi bahwa itu akan disimpan dalam tumpukan. Tetapi jika tidak dirujuk oleh contoh apa pun, literal harus dikumpulkan oleh GC run (perbaiki saya jika saya salah), jadi bagaimana penanganannya jika disimpan dalam tumpukan?

Rengasami Ramanujam
sumber
11
Bagaimana sebuah pool dapat disimpan di stack? tahukah anda konsep tumpukan?
The Scrum Meister
1
Hai Scrum Meister, saya berusaha mengatakan bahwa tidak mungkin. Maaf atas konvensi yang salah. Mengenai GC Baru saja saya mengetahui. Terima kasih untuk itu
Rengasami Ramanujam
@TheScrumMeister - pada kenyataannya, dalam beberapa keadaan, mereka dapat dikumpulkan dari sampah. The "deal breaker" adalah bahwa objek kode untuk setiap kelas yang menyebutkan literal string akan memiliki referensi ke objek String yang mewakili literal.
Stephen C

Jawaban:

74

Jawabannya secara teknis tidak keduanya. Menurut Spesifikasi Mesin Virtual Java, area untuk menyimpan literal string berada dalam kumpulan konstan runtime . Area memori kumpulan konstan waktu proses dialokasikan pada basis per kelas atau per antarmuka, jadi tidak terikat ke instance objek sama sekali. Kumpulan konstan runtime adalah bagian dari area metode yang "menyimpan struktur per kelas seperti kumpulan konstan runtime, data bidang dan metode, dan kode untuk metode dan konstruktor, termasuk metode khusus yang digunakan di kelas dan inisialisasi instance dan antarmuka. ketik inisialisasi ". Spesifikasi VM mengatakan bahwa meskipun area metode secara logis adalah bagian dari heap, itu tidak menentukan bahwa memori yang dialokasikan di area metode tunduk pada pengumpulan sampah atau perilaku lain yang akan dikaitkan dengan struktur data normal yang dialokasikan ke heap.

Duane Moore
sumber
8
Sebenarnya, saat kelas dimuat di VM, konstanta string akan disalin ke heap, ke kumpulan string seluruh VM (di permgen, seperti yang dikatakan Stephen C), karena literal string yang sama di kelas yang berbeda harus menjadi objek String yang sama (oleh JLS).
Paŭlo Ebermann
1
Terima kasih atas jawaban Anda. Saya mengerti banyak dengan diskusi ini. Senang bisa mengenal kalian :)
Rengasami Ramanujam
4
Paŭlo, hal itu berlaku untuk mesin virtual Sun, tetapi belum tentu benar untuk semua implementasi JVM. Seperti yang disebutkan oleh spesifikasi JVM, meskipun kumpulan konstanta waktu proses dan area metode secara logis merupakan bagian dari heap, keduanya tidak harus memiliki perilaku yang sama. Hanya perbedaan semantik kecil, sungguh :)
Duane Moore
54

Seperti yang dijelaskan oleh jawaban ini , lokasi pasti dari kumpulan string tidak ditentukan dan dapat bervariasi dari satu implementasi JVM ke implementasi JVM lainnya.

Menarik untuk diperhatikan bahwa hingga Java 7, kumpulan tersebut berada di ruang permgen dari heap di hotspot JVM tetapi telah dipindahkan ke bagian utama dari heap sejak Java 7 :

Area : HotSpot
Sinopsis : Di JDK 7, string yang diinternir tidak lagi dialokasikan di generasi permanen heap Java, tetapi dialokasikan di bagian utama heap Java (dikenal sebagai generasi muda dan tua), bersama dengan yang lainnya objek yang dibuat oleh aplikasi. Perubahan ini akan menghasilkan lebih banyak data yang berada di heap Java utama, dan lebih sedikit data di generasi permanen, sehingga mungkin memerlukan penyesuaian ukuran heap. Sebagian besar aplikasi hanya akan melihat perbedaan yang relatif kecil dalam penggunaan heap karena perubahan ini, tetapi aplikasi yang lebih besar yang memuat banyak kelas atau menggunakan metode String.intern () secara berlebihan akan melihat perbedaan yang lebih signifikan. RFE: 6962931

Dan di Java 8 Hotspot, Permanent Generation telah dihapus seluruhnya.

assylias
sumber
30

Literal string tidak disimpan di tumpukan. Tidak pernah. Faktanya, tidak ada objek yang disimpan di tumpukan.

Literal string (atau lebih tepatnya, objek String yang mewakilinya) secara historis disimpan dalam Heap yang disebut heap "permgen". (Permgen adalah kependekan dari generasi permanen.)

Dalam keadaan normal, literal String dan banyak hal lainnya di heap permgen dapat dijangkau "secara permanen", dan tidak dikumpulkan dari sampah. (Misalnya, string literal selalu dapat dijangkau dari objek kode yang menggunakannya.) Namun, Anda dapat mengonfigurasi JVM untuk mencoba menemukan dan mengumpulkan kelas yang dimuat secara dinamis yang tidak lagi diperlukan, dan ini dapat menyebabkan literal String menjadi sampah yang dikumpulkan .

KLARIFIKASI # 1 - Saya tidak mengatakan bahwa Permgen tidak mendapatkan GC'ed. Ya, biasanya ketika JVM memutuskan untuk menjalankan GC Penuh. Maksud saya adalah bahwa string literal akan dapat dijangkau selama kode yang menggunakannya dapat dijangkau, dan kode akan dapat dijangkau selama classloader kode tersebut dapat dijangkau, dan untuk classloader default, itu berarti "selamanya".

KLARIFIKASI # 2 - Faktanya, Java 7 dan yang lebih baru menggunakan heap biasa untuk menampung kumpulan string. Jadi, objek String yang mewakili literal String dan string internal sebenarnya berada di heap reguler. (Lihat Jawaban @ assylias untuk detailnya.)


Tetapi saya masih mencoba mencari garis tipis antara penyimpanan string literal dan string yang dibuat dengan new.

Tidak ada "garis tipis". Ini sangat sederhana:

  • String objek yang mewakili / sesuai dengan literal string disimpan di kumpulan string.
  • Stringobjek yang dibuat oleh String::internpanggilan ditahan di kumpulan string.
  • Semua Stringobjek lain TIDAK diadakan di kumpulan string.

Lalu ada pertanyaan terpisah di mana kumpulan string "disimpan". Sebelum Java 7 itu adalah permgen heap. Dari Java 7 dan seterusnya, ini adalah heap utama.

Stephen C
sumber
23

Penggabungan string

Penggabungan string (terkadang juga disebut sebagai kanonikalisasi string) adalah proses mengganti beberapa objek String dengan nilai yang sama tetapi identitas berbeda dengan satu objek String bersama. Anda dapat mencapai tujuan ini dengan menyimpan Peta Anda sendiri (dengan kemungkinan referensi lunak atau lemah tergantung pada kebutuhan Anda) dan menggunakan nilai peta sebagai nilai dikanonikalisasi. Atau Anda bisa menggunakan metode String.intern () yang disediakan untuk Anda oleh JDK.

Pada saat Java 6 menggunakan String.intern () dilarang oleh banyak standar karena kemungkinan besar untuk mendapatkan OutOfMemoryException jika penggabungan tidak terkendali. Implementasi Oracle Java 7 dari string pooling telah banyak berubah. Anda dapat mencari detailnya di http://bugs.sun.com/view_bug.do?bug_id=6962931 dan http://bugs.sun.com/view_bug.do?bug_id=6962930 .

String.intern () di Java 6

Di masa lalu yang baik itu semua string yang diinternir disimpan di PermGen - bagian ukuran tetap dari heap yang terutama digunakan untuk menyimpan kelas yang dimuat dan kumpulan string. Selain string yang diinternir secara eksplisit, kumpulan string PermGen juga berisi semua string literal yang sebelumnya digunakan dalam program Anda (kata penting di sini digunakan - jika kelas atau metode tidak pernah dimuat / dipanggil, konstanta apa pun yang ditentukan di dalamnya tidak akan dimuat).

Masalah terbesar dengan kumpulan string seperti itu di Java 6 adalah lokasinya - PermGen. PermGen memiliki ukuran tetap dan tidak dapat diperluas saat runtime. Anda dapat mengaturnya menggunakan opsi -XX: MaxPermSize = 96m. Sejauh yang saya tahu, ukuran PermGen default bervariasi antara 32M dan 96M tergantung pada platformnya. Anda dapat meningkatkan ukurannya, tetapi ukurannya akan tetap. Batasan seperti itu membutuhkan penggunaan String.intern yang sangat hati-hati - Anda sebaiknya tidak menyimpan input pengguna yang tidak terkontrol menggunakan metode ini. Itulah mengapa pengumpulan string pada saat Java 6 sebagian besar diterapkan di peta yang dikelola secara manual.

String.intern () di Java 7

Insinyur Oracle membuat perubahan yang sangat penting pada logika penggabungan string di Java 7 - kumpulan string dipindahkan ke heap. Ini berarti Anda tidak lagi dibatasi oleh area memori ukuran tetap yang terpisah. Semua string sekarang berada di heap, seperti kebanyakan objek biasa lainnya, yang memungkinkan Anda mengelola hanya ukuran heap sambil menyesuaikan aplikasi Anda. Secara teknis, ini saja bisa menjadi alasan yang cukup untuk mempertimbangkan kembali menggunakan String.intern () di program Java 7 Anda. Tapi ada alasan lain.

Nilai kumpulan string adalah sampah yang dikumpulkan

Ya, semua string dalam kumpulan string JVM memenuhi syarat untuk pengumpulan sampah jika tidak ada referensi ke sana dari root program Anda. Ini berlaku untuk semua versi Java yang dibahas. Artinya jika string yang diinternir keluar dari cakupan dan tidak ada referensi lain untuk itu - string tersebut akan dikumpulkan dari kumpulan string JVM.

Karena memenuhi syarat untuk pengumpulan sampah dan berada di heap, kumpulan string JVM tampaknya menjadi tempat yang tepat untuk semua string Anda, bukan? Secara teori itu benar - string yang tidak digunakan akan dikumpulkan dari pool, string yang digunakan akan memungkinkan Anda untuk menghemat memori jika kemudian Anda mendapatkan string yang sama dari input. Tampaknya menjadi strategi penghematan memori yang sempurna? Hampir jadi. Anda harus tahu bagaimana kumpulan string diimplementasikan sebelum membuat keputusan apa pun.

sumber.

Mencoba
sumber
11

Seperti jawaban lain menjelaskan Memori di Java dibagi menjadi dua bagian

1. Tumpukan: Satu tumpukan dibuat per utas dan ia menyimpan bingkai tumpukan yang lagi-lagi menyimpan variabel lokal dan jika variabel adalah tipe referensi, maka variabel tersebut merujuk ke lokasi memori di tumpukan untuk objek sebenarnya.

2. Heap: Semua jenis objek hanya akan dibuat di heap.

Memori heap sekali lagi dibagi menjadi 3 bagian

1. Generasi Muda: Menyimpan benda-benda yang memiliki umur pendek, Generasi Muda sendiri dapat dibagi menjadi dua kategori Eden Space dan Survivor Space .

2. Generasi Lama: Menyimpan objek yang selamat dari banyak siklus pengumpulan sampah dan masih menjadi referensi.

3. Generasi Permanen: Menyimpan metadata tentang program misalnya kumpulan konstan runtime.

Kumpulan konstan string milik area generasi permanen memori Heap.

Kita dapat melihat kumpulan konstan runtime untuk kode kita di bytecode dengan menggunakan javap -verbose class_nameyang akan menunjukkan kepada kita referensi metode (#Methodref), Objek kelas (#Class), string literals (#String)

runtime-constant-pool

Anda dapat membaca lebih lanjut tentang itu di artikel saya Bagaimana JVM Menangani Metode Overloading dan Overriding Secara Internal .

Naresh Joshi
sumber
Harap ungkapkan afiliasi apa pun dan jangan gunakan situs ini sebagai cara untuk mempromosikan situs Anda melalui posting. Lihat Bagaimana cara menulis jawaban yang baik? .
9

Untuk jawaban-jawaban hebat yang sudah disertakan di sini, saya ingin menambahkan sesuatu yang hilang dalam perspektif saya - sebuah ilustrasi.

Karena Anda sudah, JVM membagi memori yang dialokasikan ke program Java menjadi dua bagian. satu tumpukan dan satu lagi tumpukan . Stack digunakan untuk tujuan eksekusi dan heap digunakan untuk tujuan penyimpanan. Dalam memori heap tersebut, JVM mengalokasikan beberapa memori yang secara khusus dimaksudkan untuk string literal. Bagian dari memori heap ini disebut kumpulan konstanta string .

Jadi misalnya, jika Anda melakukan init objek berikut:

String s1 = "abc"; 
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);

String literal s1dan s2akan pergi ke kumpulan konstan string, objek obj1, obj2, obj3 ke heap. Semuanya, akan direferensikan dari Stack.

Juga, harap dicatat bahwa "abc" akan muncul di heap dan di kumpulan konstan string. Mengapa String s1 = "abc"dan String obj1 = new String("abc")akan diciptakan dengan cara ini? Itu karena String obj1 = new String("abc")secara eksplisit membuat instance baru dan berbeda secara referensial dari objek String dan String s1 = "abc"dapat menggunakan kembali instance dari kumpulan konstan string jika tersedia. Untuk penjelasan yang lebih terperinci: https://stackoverflow.com/a/3298542/2811258

masukkan deskripsi gambar di sini

Johnny
sumber
Dalam diagram yang diberikan, di mana literal "def" dan "456" ada. Dan bagaimana ini direferensikan?
Satyendra
Terima kasih atas komentar Anda @Satyendra, ilustrasi dan jawabannya sudah saya update.
Johnny
@Stas mengapa objek String lain "abc" dibuat .. harus menggunakan referensi obj1 untuk menunjukkan literal kan?
Itu karena String obj1 = new String ("abc") secara eksplisit membuat instance baru dan berbeda secara referensial dari objek String dan String s1 = "abc" dapat menggunakan kembali instance dari kumpulan konstan string jika tersedia. Untuk penjelasan yang lebih terperinci: stackoverflow.com/a/3298542/2811258
Johnny