JavaRush /Java Blog /Random-ID /Bagaimana kelas dimuat ke dalam JVM
Aleksandr Zimin
Level 1
Санкт-Петербург

Bagaimana kelas dimuat ke dalam JVM

Dipublikasikan di grup Random-ID
Setelah bagian tersulit dari pekerjaan seorang programmer selesai dan aplikasi “Hello World 2.0” telah ditulis, yang tersisa hanyalah merakit kit distribusi dan mentransfernya ke pelanggan, atau setidaknya ke layanan pengujian. Dalam distribusinya, semuanya berjalan sebagaimana mestinya, dan ketika kami meluncurkan program kami, Java Virtual Machine muncul. Bukan rahasia lagi bahwa mesin virtual membaca perintah yang disajikan dalam file kelas dalam bentuk bytecode dan menerjemahkannya sebagai instruksi ke prosesor. Saya mengusulkan untuk memahami sedikit tentang skema bytecode yang masuk ke mesin virtual.

Pemuat kelas

Ini digunakan untuk memasok bytecode yang dikompilasi ke JVM, yang biasanya disimpan dalam file dengan ekstensi .class, tetapi juga dapat diperoleh dari sumber lain, misalnya, diunduh melalui jaringan atau dihasilkan oleh aplikasi itu sendiri. Bagaimana kelas dimuat di JVM - 1Menurut spesifikasi Java SE, untuk menjalankan kode di JVM, Anda perlu menyelesaikan tiga langkah:
  • memuat bytecode dari sumber daya dan membuat instance kelasClass

    Ini termasuk mencari kelas yang diminta di antara kelas yang dimuat sebelumnya, memperoleh bytecode untuk memuat dan memeriksa kebenarannya, membuat instance kelas Class(untuk bekerja dengannya saat runtime), dan memuat kelas induk. Jika kelas induk dan antarmuka belum dimuat, maka kelas yang dimaksud dianggap tidak dimuat.

  • mengikat (atau menghubungkan)

    Menurut spesifikasinya, tahapan ini dibagi menjadi tiga tahapan lagi:

    • Verifikasi , kebenaran bytecode yang diterima diperiksa.
    • Persiapan , mengalokasikan RAM untuk bidang statis dan menginisialisasinya dengan nilai default (dalam hal ini, inisialisasi eksplisit, jika ada, sudah terjadi pada tahap inisialisasi).
    • Resolusi , resolusi tautan simbolik jenis, bidang, dan metode.
  • menginisialisasi objek yang diterima

    di sini, tidak seperti paragraf sebelumnya, segala sesuatunya tampak jelas apa yang harus terjadi. Tentu saja menarik untuk memahami secara pasti bagaimana hal ini terjadi.

Semua langkah ini dilakukan secara berurutan dengan persyaratan sebagai berikut:
  • Kelas harus terisi penuh sebelum ditautkan.
  • Sebuah kelas harus sepenuhnya diuji dan dipersiapkan sebelum diinisialisasi.
  • Kesalahan resolusi tautan terjadi selama eksekusi program, meskipun terdeteksi pada tahap penautan.
Seperti yang Anda ketahui, Java mengimplementasikan pemuatan kelas yang lambat (atau lambat). Ini berarti bahwa pemuatan kelas bidang referensi dari kelas yang dimuat tidak akan dilakukan sampai aplikasi menemukan referensi eksplisit ke kelas tersebut. Dengan kata lain, penyelesaian tautan simbolik bersifat opsional dan tidak terjadi secara default. Namun, implementasi JVM juga dapat menggunakan pemuatan kelas yang energik, yaitu. semua tautan simbolik harus segera diperhitungkan. Pada titik inilah persyaratan terakhir berlaku. Perlu juga dicatat bahwa resolusi tautan simbolik tidak terikat pada tahap pemuatan kelas mana pun. Secara umum, masing-masing tahapan ini merupakan pembelajaran yang baik; mari kita coba mencari tahu yang pertama, yaitu memuat bytecode.

Jenis Java Loader

Ada tiga loader standar di Java, yang masing-masing memuat kelas dari lokasi tertentu:
  1. Bootstrap adalah pemuat dasar, juga disebut Primordial ClassLoader.

    memuat kelas JDK standar dari arsip rt.jar

  2. Extension ClassLoader – pemuat ekstensi.

    memuat kelas ekstensi, yang terletak di direktori jre/lib/ext secara default, tetapi dapat diatur oleh properti sistem java.ext.dirs

  3. System ClassLoader – pemuat sistem.

    memuat kelas aplikasi yang ditentukan dalam variabel lingkungan CLASSPATH

Java menggunakan hierarki pemuat kelas, di mana root, tentu saja, adalah yang dasar. Berikutnya adalah pemuat ekstensi, dan kemudian pemuat sistem. Biasanya, setiap pemuat menyimpan penunjuk ke induknya agar dapat mendelegasikan pemuatan kepadanya jika pemuat itu sendiri tidak dapat melakukan hal ini.

Kelas abstrak ClassLoader

Setiap pemuat, kecuali pemuat dasar, adalah turunan dari kelas abstrak java.lang.ClassLoader. Misalnya, implementasi pemuat ekstensi adalah kelas sun.misc.Launcher$ExtClassLoader, dan pemuat sistem adalah sun.misc.Launcher$AppClassLoader. Pemuat dasar adalah asli dan implementasinya disertakan dalam JVM. Kelas mana pun yang diperluas java.lang.ClassLoaderdapat menyediakan caranya sendiri untuk memuat kelas dengan blackjack dan kelas yang sama. Untuk melakukan ini, perlu mendefinisikan kembali metode-metode terkait, yang saat ini saya hanya dapat mempertimbangkannya secara dangkal, karena Saya tidak memahami masalah ini secara detail. Di sini mereka:
package java.lang;
public abstract class ClassLoader {
    public Class<?> loadClass(String name);
    protected Class<?> loadClass(String name, boolean resolve);
    protected final Class<?> findLoadedClass(String name);
    public final ClassLoader getParent();
    protected Class<?> findClass(String name);
    protected final void resolveClass(Class<?> c);
}
loadClass(String name)salah satu dari sedikit metode publik, yang merupakan titik masuk untuk memuat kelas. Implementasinya adalah memanggil metode lain yang dilindungi loadClass(String name, boolean resolve), yang perlu diganti. Jika Anda melihat Javadoc dari metode yang dilindungi ini, Anda dapat memahami sesuatu seperti berikut: dua parameter diberikan sebagai masukan. Salah satunya adalah nama biner kelas (atau nama kelas yang memenuhi syarat) yang perlu dimuat. Nama kelas ditentukan dengan daftar semua paket. Parameter kedua adalah tanda yang menentukan apakah resolusi tautan simbolik diperlukan. Secara default adalah false , yang berarti pemuatan kelas lambat digunakan. Selanjutnya, menurut dokumentasi, dalam implementasi default metode ini terdapat panggilan findLoadedClass(String name)yang memeriksa apakah kelas telah dimuat sebelumnya dan, jika demikian, mengembalikan referensi ke kelas ini. Jika tidak, metode pemuatan kelas dari pemuat induk akan dipanggil. Jika tidak ada pemuat yang dapat menemukan kelas yang dimuat, masing-masing pemuat, mengikuti dalam urutan terbalik, akan mencoba menemukan dan memuat kelas tersebut, dengan mengesampingkan kelas findClass(String name). Hal ini akan dibahas lebih rinci pada bab “Skema Pemuatan Kelas”. Dan terakhir, yang tak kalah pentingnya, setelah kelas dimuat, bergantung pada flag tekadnya , akan diputuskan apakah akan memuat kelas melalui tautan simbolik. Contoh yang jelas adalah tahap Resolusi dapat dipanggil selama tahap pemuatan kelas. Oleh karena itu, dengan memperluas kelas ClassLoaderdan mengganti metodenya, pemuat khusus dapat mengimplementasikan logikanya sendiri untuk mengirimkan bytecode ke mesin virtual. Java juga mendukung konsep pemuat kelas "saat ini". Pemuat saat ini adalah pemuat yang memuat kelas yang sedang dijalankan. Setiap kelas mengetahui loader mana yang memuatnya, dan Anda bisa mendapatkan informasi ini dengan memanggil String.class.getClassLoader(). Untuk semua kelas aplikasi, pemuat "saat ini" biasanya adalah pemuat sistem.

Tiga Prinsip Pemuatan Kelas

  • Delegasi

    Permintaan untuk memuat kelas diteruskan ke pemuat induk, dan upaya untuk memuat kelas itu sendiri dilakukan hanya jika pemuat induk tidak dapat menemukan dan memuat kelas. Pendekatan ini memungkinkan Anda memuat kelas dengan pemuat yang sedekat mungkin dengan pemuat dasar. Ini mencapai visibilitas kelas maksimum. Setiap loader menyimpan catatan kelas-kelas yang dimuat olehnya, menempatkannya dalam cache-nya. Himpunan kelas-kelas ini disebut ruang lingkup.

  • Visibilitas

    Pemuat hanya melihat kelas “nya” dan kelas “induk” dan tidak mengetahui kelas yang dimuat oleh “anaknya”.

  • Keunikan

    Sebuah kelas hanya dapat dimuat satu kali. Mekanisme delegasi memastikan bahwa loader yang memulai pemuatan kelas tidak membebani kelas yang sebelumnya dimuat ke JVM.

Jadi, ketika menulis bootloadernya, seorang pengembang harus berpedoman pada tiga prinsip ini.

Skema pemuatan kelas

Ketika panggilan untuk memuat suatu kelas terjadi, kelas ini dicari di cache kelas yang sudah dimuat dari pemuat saat ini. Jika kelas yang diinginkan belum pernah dimuat sebelumnya, prinsip delegasi mentransfer kendali ke pemuat induk, yang terletak satu tingkat lebih tinggi dalam hierarki. Pemuat induk juga mencoba menemukan kelas yang diinginkan dalam cache-nya. Jika kelas telah dimuat dan pemuat mengetahui lokasinya, maka objek Classkelas tersebut akan dikembalikan. Jika tidak, pencarian akan dilanjutkan hingga mencapai base bootloader. Jika pemuat dasar tidak memiliki informasi tentang kelas yang diperlukan (yaitu, belum dimuat), bytecode kelas ini akan dicari di lokasi kelas yang diketahui oleh pemuat tersebut, dan jika kelas tidak dapat dimuat, kontrol akan kembali ke pemuat anak, yang akan mencoba memuat dari sumber yang dikenalnya. Seperti disebutkan di atas, lokasi kelas untuk pemuat dasar adalah perpustakaan rt.jar, untuk pemuat ekstensi - direktori dengan ekstensi jre/lib/ext, untuk sistem - CLASSPATH, untuk pengguna dapat berupa sesuatu yang berbeda . Dengan demikian, kemajuan kelas pemuatan berjalan ke arah yang berlawanan - dari root loader ke yang sekarang. Ketika bytecode kelas ditemukan, kelas tersebut dimuat ke dalam JVM dan sebuah instance dari tipe tersebut diperoleh Class. Seperti yang dapat Anda lihat dengan mudah, skema pemuatan yang dijelaskan mirip dengan penerapan metode di atas loadClass(String name). Di bawah ini Anda dapat melihat diagram ini dalam diagram.
Bagaimana kelas dimuat di JVM - 2

Sebagai sebuah kesimpulan

Pada langkah pertama mempelajari suatu bahasa, tidak ada kebutuhan khusus untuk memahami bagaimana kelas dimuat di Java, namun mengetahui prinsip-prinsip dasar ini akan membantu Anda menghindari keputusasaan ketika menghadapi kesalahan seperti ClassNotFoundExceptionatau NoClassDefFoundError. Ya, atau setidaknya secara kasar memahami apa akar masalahnya. Jadi, pengecualian ClassNotFoundExceptionterjadi ketika suatu kelas dimuat secara dinamis selama eksekusi program, ketika pemuat tidak dapat menemukan kelas yang diperlukan baik di cache atau di sepanjang jalur kelas. Namun kesalahan ini NoClassDefFoundErrorlebih kritis dan terjadi ketika kelas yang diperlukan tersedia selama kompilasi, namun tidak terlihat selama eksekusi program. Hal ini bisa terjadi jika program lupa menyertakan perpustakaan yang digunakannya. Fakta memahami prinsip-prinsip struktur alat yang Anda gunakan dalam pekerjaan Anda (belum tentu mendalami secara jelas dan mendetail) menambah kejelasan pada pemahaman tentang proses yang terjadi di dalam mekanisme ini, yang, di gilirannya, mengarah pada penggunaan alat ini dengan percaya diri.

Sumber

Cara Kerja ClassLoader di Java Secara keseluruhan merupakan sumber yang sangat berguna dengan penyajian informasi yang mudah diakses. Memuat kelas, ClassLoader Artikel yang cukup panjang, tetapi dengan penekanan pada cara membuat implementasi loader Anda sendiri dengan yang sama. ClassLoader: pemuatan kelas secara dinamis Sayangnya, sumber daya ini tidak tersedia sekarang, tetapi di sana saya menemukan diagram yang paling mudah dipahami dengan skema pemuatan kelas, jadi mau tidak mau saya menambahkannya. Spesifikasi Java SE: Bab 5. Memuat, Menghubungkan, dan Inisialisasi
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION