Model memori Java, yang disebut JMM, adalah jaminan terpadu untuk serangkaian platform mesin virtual Java ke platform spesifik yang tidak terkait untuk visibilitas memori dan apakah itu dapat dipesan ulang di lingkungan multi-threaded yang disediakan oleh pengembang. (Mungkin ada ambigu dalam hal istilah dan distribusi memori runtime Java, yang mengacu pada area memori seperti tumpukan, area metode, tumpukan benang, dll.).
Ada banyak gaya pemrograman bersamaan. Selain CSP (proses berurutan komunikasi), aktor dan model lainnya, yang paling akrab harus menjadi model memori bersama berdasarkan utas dan kunci. Dalam pemrograman multi-threaded, tiga jenis masalah konkurensi perlu diperhatikan:
・ Atomisitas ・ Visibilitas ・ Pesan ulang
Atomisitas melibatkan apakah utas lain dapat melihat keadaan menengah atau mengganggu saat utas melakukan operasi komposit. Biasanya, ini adalah masalah i ++. Dua utas melakukan operasi ++ pada memori heap bersama secara bersamaan. Implementasi Operasi ++ dalam JVM, Runtime, dan CPU mungkin merupakan operasi gabungan. Misalnya, dari perspektif instruksi JVM, itu adalah untuk membaca nilai I dari heap memori ke tumpukan operan, tambahkan satu, dan tulis kembali ke heap memori i. Selama operasi ini, jika tidak ada sinkronisasi yang benar, utas lain juga dapat melaksanakannya pada saat yang sama, yang dapat menyebabkan kehilangan data dan masalah lainnya. Masalah atomisitas umum, juga dikenal sebagai kondisi kompetitif, dinilai berdasarkan kemungkinan hasil kegagalan, seperti membaca-modifikasi-menulis. Visibilitas dan pemesanan ulang masalah keduanya berasal dari optimasi sistem.
Karena kecepatan eksekusi CPU dan kecepatan akses memori secara serius tidak cocok, untuk mengoptimalkan kinerja, berdasarkan prinsip-prinsip lokalisasi seperti lokalitas waktu dan lokalitas spasial, CPU telah menambahkan cache multi-lapisan antara memori. Ketika perlu untuk mengambil data, CPU pertama -tama akan pergi ke cache untuk mengetahui apakah cache yang sesuai ada. Jika ada, itu akan dikembalikan secara langsung. Jika tidak ada, itu akan diambil ke dalam memori dan disimpan dalam cache. Sekarang prosesor yang lebih multi-core telah menjadi standar, setiap prosesor memiliki cache sendiri, yang melibatkan masalah konsistensi cache. CPU memiliki model konsistensi dengan kekuatan dan kelemahan yang berbeda. Konsistensi terkuat adalah keamanan tertinggi, dan juga sesuai dengan mode pemikiran berurutan kami. Namun, dalam hal kinerja, akan ada banyak overhead karena kebutuhan untuk komunikasi yang terkoordinasi antara CPU yang berbeda.
Diagram struktur cache CPU yang khas adalah sebagai berikut
Siklus instruksi CPU biasanya pengambilan instruksi, penguraian instruksi untuk membaca data, menjalankan instruksi, dan menulis data kembali ke register atau memori. Saat menjalankan instruksi dalam serial, data yang dibaca dan disimpan memakan waktu lama, sehingga CPU umumnya menggunakan pipa instruksi untuk menjalankan beberapa instruksi pada saat yang sama untuk meningkatkan throughput keseluruhan, seperti pipa pabrik.
Kecepatan membaca data dan menulis kembali data ke memori tidak pada urutan besarnya yang sama daripada mengeksekusi instruksi, sehingga CPU menggunakan register dan cache sebagai cache dan buffer. Saat membaca data dari memori, itu akan membaca garis cache (mirip dengan pembacaan disk dan membaca blok). Modul yang menulis data kembali akan menempatkan permintaan penyimpanan ke buffer toko ketika data lama tidak ada di cache dan terus menjalankan tahap berikutnya dari siklus instruksi. Jika ada di cache, cache akan diperbarui, dan data dalam cache akan menyiram ke memori sesuai dengan kebijakan tertentu.
Public Class MemoryModel {private int count; berhenti boolean pribadi; public void initcountandStop () {count = 1; hentikan = false; } public void doloop () {while (! Stop) {count ++; }} public void printresult () {System.out.println (count); System.out.println (berhenti); }}Saat menjalankan kode di atas, kita mungkin berpikir bahwa Count = 1 akan dieksekusi sebelum berhenti = false. Ini benar dalam keadaan ideal yang ditunjukkan dalam diagram eksekusi CPU di atas, tetapi tidak benar ketika mempertimbangkan register dan buffering cache. Misalnya, berhenti di cache tetapi hitungan tidak ada, maka berhenti dapat diperbarui dan buffer write count disegarkan ke memori sebelum menulis kembali.
Selain itu, CPU dan kompiler (biasanya merujuk ke JIT untuk Java) dapat memodifikasi perintah eksekusi instruksi. Misalnya, dalam kode di atas, hitung = 1 dan stop = false tidak memiliki dependensi, sehingga CPU dan kompiler dapat memodifikasi urutan keduanya. Dalam tampilan program satu threaded, hasilnya sama. Ini juga merupakan as-if-serial yang harus dipastikan oleh CPU dan kompiler (terlepas dari bagaimana urutan eksekusi dimodifikasi, hasil eksekusi dari satu threaded tetap tidak berubah). Karena sebagian besar eksekusi program adalah satu utas, optimasi semacam itu dapat diterima dan membawa peningkatan kinerja yang hebat. Namun, dalam kasus multithreading, hasil yang tidak terduga dapat terjadi tanpa operasi sinkronisasi yang diperlukan. Misalnya, setelah utas T1 menjalankan metode initcountandstop, Thread T2 mengeksekusi printresult, yang mungkin 0, false, 1, false, atau 0, true. Jika Thread T1 mengeksekusi doloop () pertama dan utas T2 mengeksekusi initcountandstop satu detik, maka T1 dapat melompat keluar dari loop, atau mungkin tidak pernah melihat modifikasi stop karena optimasi kompiler.
Karena berbagai masalah dalam situasi multi-threading di atas, urutan program dalam multi-threading tidak lagi menjadi urutan eksekusi dan menghasilkan mekanisme yang mendasarinya. Bahasa pemrograman perlu memberikan jaminan pengembang. Secara sederhana, jaminan ini adalah ketika modifikasi utas akan terlihat oleh utas lain. Oleh karena itu, bahasa Java mengusulkan javamemorymodel, yaitu model memori Java, yang membutuhkan implementasi sesuai dengan konvensi model ini. Java menyediakan mekanisme seperti volatile, disinkronkan, dan final untuk membantu pengembang memastikan kebenaran program multi-threaded pada semua platform prosesor.
Sebelum JDK1.5, model memori Java memiliki masalah serius. Misalnya, dalam model memori lama, utas mungkin melihat nilai default bidang akhir setelah konstruktor selesai, dan penulisan bidang volatil dapat dipesan ulang dengan bacaan dan penulisan bidang yang tidak mudah menguap.
Jadi di JDK1.5, model memori baru diusulkan melalui JSR133 untuk memperbaiki masalah sebelumnya.
Aturan pemesanan ulang
Mengubah dan monitor kunci
| Apakah mungkin untuk memesan ulang | Operasi kedua | Operasi kedua | Operasi kedua |
|---|---|---|---|
| Operasi pertama | Bacaan normal/penulisan biasa | Baca/monitor yang mudah menguap masuk | Exit Write/Monitor yang mudah menguap |
| Bacaan normal/penulisan biasa | TIDAK | ||
| Voaltile Read/Monitor Enter | TIDAK | TIDAK | TIDAK |
| Exit Write/Monitor yang mudah menguap | TIDAK | TIDAK |
Bacaan normal mengacu pada array muatan getfield, getstatic, dan non-volatile, dan bacaan normal mengacu pada arraystore dari putfield, putstatic, dan non-volatile array.
Bacaan dan penulisan bidang yang mudah menguap masing -masing adalah getfield, getstatic, putfield, putstatic.
Monitorenter adalah untuk memasuki blok sinkronisasi atau metode sinkronisasi, monitorexist mengacu keluar dari blok sinkronisasi atau metode sinkronisasi.
Tidak ada di tabel di atas mengacu pada dua operasi yang tidak memungkinkan pemesanan ulang. Misalnya (penulisan normal, penulisan volatile) mengacu pada pemesanan ulang bidang yang tidak mudah menguap dan penulisan ulang penulisan bidang volatil berikutnya. Ketika tidak ada tidak, itu berarti bahwa pemesanan ulang diizinkan, tetapi JVM perlu memastikan keamanan minimum - nilai yang dibaca adalah nilai default atau ditulis oleh utas lain (64 -bit ganda dan panjang operasi dan penulisan adalah kasus khusus. Ketika tidak ada modifikasi yang tidak stabil, tidak dijamin bahwa pembacaan dan penulisan adalah atom, dan lapisan yang mendasarinya dapat dipisahkan.
Bidang terakhir
Ada dua aturan khusus tambahan untuk bidang akhir
Baik penulisan bidang akhir (dalam konstruktor) maupun penulisan referensi objek bidang akhir itu sendiri dapat direkrut dengan penulisan berikutnya dari objek yang memegang bidang akhir (di luar konstruktor). Misalnya, pernyataan berikut tidak dapat dipesan ulang
x.finalfield = v; ...; sharedref = x;
Beban pertama bidang akhir tidak dapat dipesan ulang dengan penulisan objek yang memegang bidang terakhir. Misalnya, pernyataan berikut tidak memungkinkan pemesanan ulang.
x = sharedref; ...; i = x.finalfield
Penghalang memori
Semua prosesor mendukung hambatan atau pagar memori tertentu untuk mengontrol visibilitas penataan ulang dan data antara prosesor yang berbeda. Misalnya, ketika CPU menulis data kembali, itu akan menempatkan permintaan toko ke buffer tulis dan menunggu untuk memori ke dalam memori. Permintaan toko ini dapat dicegah agar tidak dipesan ulang dengan permintaan lain dengan memasukkan penghalang untuk memastikan visibilitas data. Anda dapat menggunakan contoh kehidupan untuk membandingkan penghalang. Misalnya, ketika mengambil lift kemiringan di kereta bawah tanah, semua orang memasuki lift secara berurutan, tetapi beberapa orang akan berkeliling dari kiri, sehingga pesanan ketika meninggalkan lift berbeda. Jika seseorang membawa bagasi besar yang diblokir (penghalang), orang -orang di belakang tidak bisa berkeliling :). Selain itu, penghalang di sini dan penghalang tulis yang digunakan dalam GC adalah konsep yang berbeda.
Klasifikasi hambatan memori
Hampir semua prosesor mendukung instruksi penghalang dari biji -bijian kasar tertentu, biasanya disebut pagar (pagar, pagar), yang dapat memastikan bahwa instruksi beban dan penyimpanan yang dimulai sebelum pagar dapat secara ketat sesuai dengan beban dan simpan setelah pagar. Biasanya, itu akan dibagi menjadi empat jenis hambatan berikut sesuai dengan tujuannya.
Hambatan beban
Beban1; Beban; Muatan2;
Pastikan data load1 dimuat sebelum beban2 dan setelah beban
Hambatan Storestore
Store1; Storestore; Store2
Pastikan bahwa data di STORE1 terlihat oleh prosesor lain sebelum Store2 dan setelah.
Hambatan LoadStore
Beban1; Loadstore; Store2
Pastikan bahwa data Load1 dimuat sebelum Store2 dan setelah data flush
Hambatan StoreLoad
Store1; StoreLoad; LOAD2
Pastikan bahwa data di STORE1 terlihat di depan prosesor lain (seperti pembilasan ke memori) sebelum memuat data di Load2 dan setelah beban. StoreLoad Barrier mencegah beban membaca data lama daripada data yang baru -baru ini ditulis oleh prosesor lain.
Hampir semua multiprosesor di zaman modern membutuhkan storeLoad. Overhead storeLoad biasanya yang terbesar, dan storeLoad memiliki efek dari tiga hambatan lainnya, sehingga storeLoad dapat digunakan sebagai penghalang umum (tetapi overhead yang lebih tinggi).
Oleh karena itu, menggunakan penghalang memori di atas, aturan pemesanan ulang dalam tabel di atas dapat diimplementasikan
| Butuh hambatan | Operasi kedua | Operasi kedua | Operasi kedua | Operasi kedua |
|---|---|---|---|---|
| Operasi pertama | Bacaan normal | Tulisan normal | Baca/monitor yang mudah menguap masuk | Exit Write/Monitor yang mudah menguap |
| Bacaan normal | Loadstore | |||
| Bacaan normal | Storestore | |||
| Voaltile Read/Monitor Enter | Beban | Loadstore | Beban | Loadstore |
| Exit Write/Monitor yang mudah menguap | StoreLoad | Storestore |
Untuk mendukung aturan bidang akhir, perlu untuk menambahkan penghalang ke tulisan akhir ke final
x.finalfield = v; Storestore; sharedref = x;
Masukkan penghalang memori
Berdasarkan aturan di atas, Anda dapat menambahkan penghalang untuk pemrosesan bidang yang mudah menguap dan kata kunci yang disinkronkan untuk memenuhi aturan model memori.
Masukkan StoreStore sebelum penghalang toko yang mudah menguap setelah semua bidang terakhir ditulis tetapi masukkan StoreStore sebelum konstruktor kembali
Masukkan penghalang StoreLoad setelah toko volatile. Masukkan loadload dan loadstore penghalang setelah beban volatil.
Aturan monitor entri dan volatile konsisten, dan monitor keluar dan aturan toko yang mudah menguap konsisten.
Terjadi sebelum
Berbagai hambatan memori yang disebutkan di atas masih relatif kompleks untuk pengembang, jadi JMM dapat menggunakan serangkaian aturan hubungan pesanan parsial sebelum diilustrasikan. Untuk memastikan bahwa utas yang mengeksekusi Operasi B melihat hasil Operasi A (terlepas dari apakah A dan B dieksekusi di utas yang sama), maka hubungan yang terjadi sebelum harus dipenuhi antara A dan B, jika tidak JVM dapat memesan ulang secara sewenang -wenang.
Terjadi daftar aturan
Peraturan HappendBefore termasuk
Aturan Urutan Program: Jika operasi A dalam program sebelum operasi B, maka operasi A di utas yang sama akan melakukan aturan kunci monitor sebelum operasi B: Operasi kunci pada kunci monitor harus dilakukan sebelum operasi kunci pada kunci monitor yang sama.
volatile variable rules: The write operation of the volatile variable must execute thread startup rules before the read operation of the variable: The call to Thread.start on the thread must execute thread end rules before any operation in the thread: Any operation in the thread must execute interrupt rules before other threads detect that the thread has ended: When a thread calls interrupt on another thread, it must execute passivity before the interrupted thread detects interrupt: If operation A is executed before operation B and Operasi B dijalankan sebelum Operasi C, kemudian Operasi A dieksekusi sebelum operasi C.
Kunci tampilan memiliki semantik memori yang sama dengan kunci monitor, dan variabel atom memiliki semantik memori yang sama dengan volatile. Akuisisi dan pelepasan kunci, operasi baca dan tulis dari variabel volatil memenuhi hubungan orde penuh, sehingga penulisan volatil dapat dilakukan sebelum bacaan volatil berikutnya.
Kejadian yang disebutkan di atas dapat digabungkan dengan menggunakan banyak aturan.
Misalnya, setelah utas A memasuki kunci monitor, operasi sebelum melepaskan kunci monitor didasarkan pada aturan urutan program, dan operasi pelepasan monitor yang terjadi sebelum digunakan untuk mendapatkan kunci monitor yang sama di utas B berikutnya, dan operasi dalam operasi dalam kejadian dan utas B.
Meringkaskan
Di atas adalah semua penjelasan terperinci dari Java Memory Model JMM dalam artikel ini, saya harap ini akan membantu semua orang. Jika ada kekurangan, silakan tinggalkan pesan untuk menunjukkannya. Terima kasih teman atas dukungan Anda untuk situs ini!