Pertanyaan Mengapa Sun JVM terus mengkonsumsi lebih banyak memori RSS bahkan ketika tumpukan, ukuran dll stabil?


Selama setahun terakhir saya telah membuat peningkatan besar dalam aplikasi Java yang saya gunakan - pengurangan 66% yang solid. Dalam mengejar itu, saya telah memantau berbagai metrik, seperti ukuran tumpukan Java, cpu, Java non-heap, dll. Melalui SNMP.

Baru-baru ini, saya telah memantau berapa banyak memori nyata (RSS, set penduduk) oleh JVM dan saya agak terkejut. Memori nyata yang dikonsumsi oleh JVM tampaknya benar-benar independen dari ukuran tumpukan aplikasi saya, non-heap, ruang eden, jumlah ulir, dll.

Ukuran Heap yang diukur dengan Java SNMP Java Heap Used Graph http://lanai.dietpizza.ch/images/jvm-heap-used.png

Memori Nyata dalam KB. (Misalnya: 1 MB KB = 1 GB) Java Heap Used Graph http://lanai.dietpizza.ch/images/jvm-rss.png

(Tiga dips dalam grafik heap sesuai dengan pembaruan aplikasi / restart.)

Ini adalah masalah bagi saya karena semua memori ekstra yang JVM konsumsi adalah 'mencuri' memori yang dapat digunakan oleh OS untuk file caching. Bahkan, setelah nilai RSS mencapai ~ 2,5-3GB, saya mulai melihat waktu respons yang lebih lambat dan utilisasi CPU yang lebih tinggi dari aplikasi saya, sebagian besar dilakukan untuk IO menunggu. Karena beberapa titik paging ke partisi swap dimulai. Ini semua sangat tidak diinginkan.

Jadi, pertanyaan saya:

  • Kenapa ini terjadi? Apa yang sedang terjadi "Dibawah tenda"?
  • Apa yang dapat saya lakukan untuk menjaga konsumsi memori nyata JVM?

Detail yang mengerikan:

  • RHEL4 64-bit (Linux - 2.6.9-78.0.5.ELsmp # 1 SMP Wed Sep 24 ... 2008 x86_64 ... GNU / Linux)
  • Java 6 (build 1.6.0_07-b06)
  • Tomcat 6
  • Aplikasi (streaming video HTTP berdasarkan permintaan)
    • Tinggi I / O melalui java.nio FileChannels
    • Ratusan hingga ribuan utas benang
    • Penggunaan basis data rendah
    • Musim semi, Hibernasi

Parameter JVM yang relevan:

-Xms128m  
-Xmx640m  
-XX:+UseConcMarkSweepGC  
-XX:+AlwaysActAsServerClassMachine  
-XX:+CMSIncrementalMode    

-XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps  
-XX:+PrintGCApplicationStoppedTime  
-XX:+CMSLoopWarn  
-XX:+HeapDumpOnOutOfMemoryError 

Bagaimana saya mengukur RSS:

ps x -o command,rss | grep java | grep latest | cut -b 17-

Ini masuk ke file teks dan dibaca ke dalam database RRD sistem pemantauan saya pada interval reguler. Perhatikan bahwa ps output Kilo Bytes.


Masalah & Solusis:

Sementara pada akhirnya memang begitu ATorrasJawaban yang terbukti pada akhirnya benar, itu kdgregory yang membimbing saya ke jalur diagnostik yang benar dengan penggunaan pmap. (Pilihlah kedua jawaban mereka!) Inilah yang sedang terjadi:

Hal yang saya tahu pasti:

  1. Aplikasi saya mencatat dan menampilkan data dengan JRobin 1.4, sesuatu yang saya masukkan ke dalam aplikasi saya lebih dari tiga tahun yang lalu.
  2. Contoh aplikasi tersibuk yang saat ini dibuat
    1. Lebih dari 1000 beberapa file database JRobin baru (masing-masing sekitar 1,3MB) dalam waktu satu jam setelah memulai
    2. ~ 100 + setiap hari setelah start-up
  3. Aplikasi ini memperbarui objek basis data JRobin ini setiap 15 detik, jika ada sesuatu untuk ditulis.
  4. Dalam konfigurasi standar JRobin:
    1. menggunakan a java.nioakses back-end berbasis file. Peta back-end ini MappedByteBufferske file itu sendiri.
    2. setiap lima menit, panggilan benang daemon JRobin MappedByteBuffer.force() pada setiap basis data MBB yang mendasari JRobin
  5. pmap terdaftar:
    1. 6.500 pemetaan
    2. 5500 diantaranya adalah file database JRobin 1.3MB, yang bekerja hingga ~ 7.1GB

Poin terakhir adalah saya "Eureka!" saat.

Tindakan korektif saya:

  1. Pertimbangkan memperbarui ke JRobinLite 1.5.2 terbaru yang ternyata lebih baik
  2. Menerapkan penanganan sumber daya yang tepat pada database JRobin. Saat ini, setelah aplikasi saya membuat database dan kemudian tidak pernah membuangnya setelah database tidak lagi digunakan secara aktif.
  3. Bereksperimen dengan memindahkan MappedByteBuffer.force() ke acara pembaruan basis data, dan bukan pengatur waktu berkala. Akankah masalah itu hilang dengan sendirinya?
  4. Segera, ubah back-end JRobin ke implementasi java.io - perubahan garis lini. Ini akan lebih lambat, tetapi mungkin bukan masalah. Berikut ini adalah grafik yang menunjukkan dampak langsung dari perubahan ini.

Memori RSS Jawa menggunakan grafik http://lanai.dietpizza.ch/images/stackoverflow-rss-problem-fixed.png

Pertanyaan yang saya mungkin atau mungkin tidak punya waktu untuk mencari tahu:

  • Apa yang terjadi di dalam JVM dengan MappedByteBuffer.force()? Jika tidak ada yang berubah, apakah masih menulis seluruh file? Bagian dari file? Apakah itu memuatnya dulu?
  • Apakah ada sejumlah MBB selalu di RSS setiap saat? (RSS kira-kira setengah ukuran total MBB yang dialokasikan. Kebetulan? Saya kira tidak.)
  • Jika saya memindahkan MappedByteBuffer.force() ke acara pembaruan basis data, dan bukan pengatur waktu berkala, akankah masalah secara ajaib hilang?
  • Mengapa kemiringan RSS begitu teratur? Itu tidak berkorelasi dengan salah satu metrik beban aplikasi.

32
2017-10-23 11:50


asal


Jawaban:


Hanya sebuah ide: Buffer NIO ditempatkan di luar JVM.

EDIT: Per 2016, ini layak dipertimbangkan komentar @Lari Hotari [ Mengapa Sun JVM terus mengkonsumsi lebih banyak memori RSS bahkan ketika tumpukan, ukuran dll stabil? ] karena kembali ke 2009, RHEL4 memiliki glibc <2.10 (~ 2.3)

Salam.


18
2017-10-23 12:19



RSS mewakili halaman yang aktif digunakan - untuk Java, itu terutama objek hidup di heap, dan struktur data internal di JVM. Tidak banyak yang dapat Anda lakukan untuk mengurangi ukurannya kecuali menggunakan lebih sedikit objek atau melakukan pemrosesan lebih sedikit.

Dalam kasus Anda, saya tidak berpikir ini masalah. Grafik muncul untuk menunjukkan 3 meg dikonsumsi, bukan 3 pertunjukan saat Anda menulis dalam teks. Itu sangat kecil, dan tidak mungkin menyebabkan paging.

Jadi apa lagi yang terjadi di sistem Anda? Apakah ini situasi di mana Anda memiliki banyak server Tomcat, masing-masing mengonsumsi 3M RSS? Anda melempar banyak bendera GC, apakah mereka menunjukkan bahwa proses menghabiskan sebagian besar waktunya di GC? Apakah Anda memiliki database yang berjalan di mesin yang sama?

Edit sebagai tanggapan atas komentar

Mengenai ukuran RSS 3M - ya, itu tampak terlalu rendah untuk proses Tomcat (saya memeriksa kotak saya, dan memiliki satu di 89M yang belum aktif untuk sementara waktu). Namun, saya tidak terlalu berharap untuk menjadi> ukuran heap, dan saya tentu tidak mengharapkannya menjadi hampir 5 kali ukuran tumpukan (Anda menggunakan -Xmx640) - itu harus paling buruk ukurannya heap + beberapa per aplikasi konstan.

Yang menyebabkan saya mencurigai nomor Anda. Jadi, alih-alih grafik dari waktu ke waktu, jalankan hal berikut untuk mendapatkan cuplikan (ganti 7429 dengan ID proses apa pun yang Anda gunakan):

ps -p 7429 -o pcpu,cutime,cstime,cmin_flt,cmaj_flt,rss,size,vsize

(Edit oleh Stu agar kami dapat memformat hasil untuk permintaan di atas untuk info ps :)

[stu@server ~]$ ps -p 12720 -o pcpu,cutime,cstime,cmin_flt,cmaj_flt,rss,size,vsize
%CPU - - - -  RSS SZ  VSZ
28.8 - - - - 3262316 1333832 8725584

Edit untuk menjelaskan angka-angka ini untuk anak cucu

RSS, sebagaimana dicatat, adalah ukuran set penduduk: halaman dalam memori fisik. SZ menyimpan jumlah halaman yang dapat ditulis oleh proses (biaya komit); halaman manual menggambarkan nilai ini sebagai "sangat kasar". VSZ menyimpan ukuran peta memori virtual untuk proses: halaman yang dapat ditulis dan halaman bersama.

Biasanya, VSZ sedikit> SZ, dan sangat banyak> RSS. Output ini menunjukkan situasi yang sangat tidak biasa.

Elaborasi tentang mengapa satu-satunya solusi adalah mengurangi objek

RSS mewakili jumlah halaman yang ada dalam RAM - halaman yang diakses secara aktif. Dengan Java, pengumpul sampah secara berkala akan menelusuri seluruh grafik objek. Jika grafik objek ini menempati sebagian besar ruang heap, maka kolektor akan menyentuh setiap halaman di heap, membutuhkan semua halaman tersebut untuk menjadi penduduk memori. GC sangat baik tentang pemadatan tumpukan setelah setiap koleksi utama, jadi jika Anda menjalankan dengan tumpukan sebagian, ada sebagian besar halaman seharusnya tidak perlu dalam RAM.

Dan beberapa opsi lain

Saya perhatikan bahwa Anda menyebutkan memiliki ratusan hingga ribuan utas benang. Tumpukan untuk utas ini juga akan ditambahkan ke RSS, meskipun seharusnya tidak banyak. Dengan asumsi bahwa utas memiliki kedalaman panggilan yang dangkal (tipikal untuk rangkaian penangan server aplikasi), masing-masing hanya boleh mengkonsumsi satu atau dua halaman memori fisik, meskipun ada biaya komit setengah meg untuk masing-masingnya.


14
2017-10-23 12:01



Kenapa ini terjadi? Apa yang sedang terjadi "di bawah tenda"?

JVM menggunakan lebih banyak memori daripada hanya tumpukan. Sebagai contoh metode Java, tumpukan benang dan pegangan asli dialokasikan dalam memori terpisah dari heap, serta struktur data internal JVM.

Dalam kasus Anda, kemungkinan penyebab masalah mungkin: NIO (sudah disebutkan), JNI (sudah disebutkan), pembuatan untaian berlebih.

Tentang JNI, Anda menulis bahwa aplikasi tidak menggunakan JNI tetapi ... Apa jenis driver JDBC yang Anda gunakan? Mungkinkah itu tipe 2, dan bocor? Ini sangat tidak mungkin karena Anda mengatakan penggunaan basis data rendah.

Tentang penciptaan thread yang berlebihan, setiap thread mendapatkan tumpukannya sendiri yang mungkin cukup besar. Ukuran tumpukan sebenarnya bergantung pada VM, OS, dan arsitektur, mis. untuk JRockit itu 256K di Linux x64, saya tidak menemukan referensi dalam dokumentasi Sun untuk VM Sun. Ini berdampak langsung pada memori ulir (memori ulir = jumlah tumpukan ulir * jumlah utas). Dan jika Anda membuat dan menghancurkan banyak utas, memori mungkin tidak digunakan kembali.

Apa yang dapat saya lakukan untuk menjaga konsumsi memori nyata JVM?

Sejujurnya, ratusan hingga ribuan benang tampaknya sangat besar bagi saya. Yang mengatakan, jika Anda benar-benar membutuhkan banyak thread, ukuran tumpukan ulir dapat dikonfigurasi melalui -Xss pilihan. Ini dapat mengurangi konsumsi memori. Tapi saya rasa ini tidak akan menyelesaikan seluruh masalah. Saya cenderung berpikir bahwa ada kebocoran di suatu tempat ketika saya melihat grafik memori yang sebenarnya.


3
2017-10-23 14:35



Pengumpul sampah saat ini di Jawa terkenal karena tidak merilis memori yang dialokasikan, meskipun memori tidak diperlukan lagi. Namun cukup aneh, bahwa ukuran RSS Anda meningkat menjadi> 3GB meskipun ukuran heap Anda dibatasi hingga 640 MB. Apakah Anda menggunakan kode asli di aplikasi Anda atau apakah Anda memiliki paket optimasi kinerja asli untuk Tomcat diaktifkan? Dalam hal ini, Anda tentu saja memiliki kebocoran memori asli dalam kode Anda atau di Tomcat.

Dengan Java 6u14, Sun memperkenalkan pengumpul sampah "Sampah-Pertama" baru, yang mampu melepaskan memori kembali ke sistem operasi jika tidak diperlukan lagi. Ini masih dikategorikan sebagai eksperimental dan tidak diaktifkan secara default, tetapi jika itu adalah pilihan yang layak untuk Anda, saya akan mencoba untuk meng-upgrade ke rilis Java 6 terbaru dan mengaktifkan garbage collector baru dengan argumen baris perintah "-XX: + UnlockExperimentalVMOptions - XX: + UseG1GC ". Mungkin memecahkan masalah Anda.


1
2017-10-23 12:19