Alokasi memori JVM

Halo semuanya! Kami ingin bertepatan dengan terjemahan materi hari ini dengan peluncuran utas baru dalam kursus Java Developer , yang akan dimulai besok. Baiklah, mari kita mulai.

JVM bisa menjadi binatang yang kompleks. Untungnya, sebagian besar kerumitan ini tersembunyi di bawah tenda, dan kami, sebagai pengembang aplikasi dan bertanggung jawab atas penyebaran, sering kali tidak perlu terlalu khawatir tentang hal itu. Meskipun karena semakin populernya teknologi penyebaran aplikasi dalam wadah, ada baiknya memperhatikan alokasi memori di JVM.



Dua macam memori

JVM membagi memori menjadi dua kategori utama: heap dan non-heap. Tumpukan adalah bagian dari memori JVM yang paling akrab dengan para pengembang. Objek yang dibuat oleh aplikasi disimpan di sini. Mereka tetap di sana sampai mereka dipindahkan oleh pemulung. Biasanya, ukuran tumpukan yang digunakan aplikasi bervariasi tergantung pada beban saat ini.

Memori out-of-heap dibagi menjadi beberapa area. Di HotSpot, Anda dapat menggunakan mekanisme Native memory tracking (NMT) untuk menjelajahi area memori ini. Harap dicatat bahwa meskipun NMT tidak melacak penggunaan semua memori asli ( misalnya, alokasi memori asli oleh kode pihak ketiga tidak dipantau ), kemampuannya cukup untuk sebagian besar aplikasi Musim Semi yang khas. Untuk menggunakan NMT, jalankan aplikasi dengan -XX:NativeMemoryTracking=summary dan gunakan ringkasan jcmd VM.native_memory melihat informasi pada memori yang digunakan.

Mari kita lihat menggunakan NMT sebagai contoh dari teman lama kita Petclinic . Diagram di bawah ini menunjukkan penggunaan memori JVM menurut data NMT (dikurangi overhead NMT-nya sendiri) saat memulai Petclinic dengan ukuran heap maksimum 48 MB ( -Xmx48M ):



Seperti yang Anda lihat, memori di luar tumpukan menyumbang sebagian besar memori JVM yang digunakan, dan memori tumpukan hanya seperenam dari total. Dalam hal ini, kira-kira 44 MB (di mana 33 MB digunakan segera setelah pengumpulan sampah). Kehabisan memori habis menggunakan memori total sebesar 223 MB.

Area memori asli

Ruang kelas terkompresi : digunakan untuk menyimpan informasi tentang kelas yang dimuat. Terbatas pada parameter MaxMetaspaceSize . Fungsi dari jumlah kelas yang telah dimuat.

Catatan Penerjemah

Untuk beberapa alasan, penulis menulis tentang ruang kelas terkompresi, dan bukan tentang seluruh area kelas. Area ruang kelas terkompresi adalah bagian dari area kelas, dan parameter MaxMetaspaceSize membatasi ukuran seluruh area kelas, bukan hanya ruang kelas terkompresi. Untuk membatasi "ruang kelas terkompresi", parameter CompressedClassSpaceSize digunakan.

Dari sini :
Jika UseCompressedOops diaktifkan dan UseCompressedClassesPointers digunakan, maka dua area berbeda dari memori asli digunakan untuk metadata kelas ...
Suatu wilayah dialokasikan untuk pointer kelas terkompresi ini (offset 32-bit). Ukuran wilayah dapat diatur dengan CompressedClassSpaceSize dan 1 gigabyte (GB) secara default ...
MaxMetaspaceSize berlaku untuk jumlah ruang kelas terkompresi yang dikomit dan ruang untuk metadata kelas lainnya

Jika parameter UseCompressedOops dan UseCompressedOops digunakan, maka dua area berbeda dari memori asli digunakan untuk metadata kelas ...

Untuk pointer terkompresi, area memori dialokasikan (offset 32-bit). Ukuran area ini dapat diatur oleh CompressedClassSpaceSize dan secara default 1 GB ...
Parameter MaxMetaspaceSize mengacu pada jumlah area pointer terkompresi dan area untuk metadata kelas lainnya.


  • Utas: Memori yang digunakan oleh utas di JVM. Fungsi dari jumlah utas yang berjalan.
  • Cache kode: Memori yang digunakan oleh JIT untuk menjalankannya. Fungsi dari jumlah kelas yang telah dimuat. Terbatas untuk ReservedCodeCacheSize . Anda dapat mengurangi pengaturan JIT, misalnya, dengan menonaktifkan kompilasi berjenjang.
  • GC (pengumpul sampah): menyimpan data yang digunakan oleh pengumpul sampah. Tergantung pengumpul sampah yang digunakan.
  • Simbol: menyimpan karakter seperti nama bidang, tanda tangan metode, dan string yang diinternir. Penggunaan memori karakter yang berlebihan dapat mengindikasikan bahwa garis terlalu diinternir.
  • Internal: menyimpan data internal lain yang tidak termasuk dalam area lain mana pun.

Perbedaan

Dibandingkan dengan heap, memori off-heap berubah lebih sedikit saat memuat. Segera setelah aplikasi memuat semua kelas yang akan digunakan dan JIT benar-benar hangat, semuanya akan menjadi stabil. Untuk melihat penurunan penggunaan ruang kelas terkompresi , pemuat kelas yang memuat kelas harus dihapus oleh pengumpul sampah. Ini biasa di masa lalu ketika aplikasi dikerahkan dalam wadah servlet atau server aplikasi (pemuat kelas aplikasi dihapus oleh pengumpul sampah ketika aplikasi dihapus dari server aplikasi), tetapi ini jarang terjadi dengan pendekatan modern untuk penyebaran aplikasi.

Konfigurasikan JVM

Mengkonfigurasi JVM agar efisien menggunakan RAM yang tersedia tidaklah mudah. Jika Anda menjalankan JVM dengan parameter -Xmx16M dan berharap tidak lebih dari 16 MB memori yang akan digunakan, maka Anda akan mendapatkan kejutan yang tidak menyenangkan.

Area memori JVM yang menarik adalah cache kode JIT. Secara default, HotSpot JVM akan menggunakan hingga 240 MB. Jika cache kode terlalu kecil, JIT mungkin tidak memiliki cukup ruang untuk menyimpan datanya, dan sebagai hasilnya, kinerja akan berkurang. Jika cache terlalu besar, maka memori mungkin terbuang sia-sia. Saat menentukan ukuran cache, penting untuk mempertimbangkan pengaruhnya terhadap penggunaan memori dan kinerja.

Saat berjalan dalam wadah Docker, Java versi terbaru sekarang menyadari keterbatasan memori wadah dan mencoba mengubah ukuran memori JVM. Sayangnya, banyak memori sering dialokasikan di luar heap dan tidak cukup di heap. Misalkan Anda memiliki aplikasi yang berjalan dalam wadah dengan 2 prosesor dan 512 MB memori yang tersedia. Anda ingin menangani lebih banyak beban kerja dan menambah jumlah prosesor menjadi 4 dan memori menjadi 1 GB. Seperti yang kita bahas di atas, ukuran tumpukan biasanya bervariasi dengan beban, dan memori di luar tumpukan berubah kurang signifikan. Oleh karena itu, kami berharap bahwa sebagian besar tambahan 512 MB akan dialokasikan ke heap untuk menangani peningkatan beban. Sayangnya, secara default, JVM tidak akan melakukan ini dan akan mendistribusikan memori tambahan lebih atau kurang secara merata antara memori pada heap dan off heap.

Untungnya, tim CloudFoundry memiliki pengetahuan luas tentang alokasi memori di JVM. Jika Anda mengunduh aplikasi ke CloudFoundry, maka paket build akan secara otomatis menerapkan pengetahuan ini kepada Anda. Jika Anda tidak menggunakan CloudFoudry atau ingin memahami lebih lanjut tentang cara mengkonfigurasi JVM, disarankan untuk membaca deskripsi dari versi ketiga kalkulator memori Java buildpack .

Apa artinya ini untuk Spring

Tim Spring menghabiskan banyak waktu untuk berpikir tentang kinerja dan penggunaan memori, mengingat kemungkinan menggunakan memori baik di heap dan di heap. Salah satu cara untuk membatasi penggunaan memori di luar tumpukan adalah dengan membuat bagian-bagian kerangka kerja sekompleks mungkin. Contoh dari ini adalah menggunakan Refleksi untuk membuat dan menyuntikkan dependensi ke dalam kacang aplikasi Anda. Melalui penggunaan Refleksi, jumlah kode kerangka kerja yang Anda gunakan tetap konstan, terlepas dari jumlah kacang dalam aplikasi Anda. Untuk mengoptimalkan waktu startup, kami menggunakan cache di heap, membersihkan cache ini setelah peluncuran selesai. Memori tumpukan dapat dengan mudah dibersihkan oleh pengumpul sampah untuk menyediakan lebih banyak memori yang tersedia untuk aplikasi Anda.

Secara tradisional, kami menunggu komentar Anda tentang materi tersebut.

Source: https://habr.com/ru/post/id445312/


All Articles