Alat untuk meluncurkan dan mengembangkan aplikasi Java, kompilasi, eksekusi pada JVM

Bukan rahasia lagi bahwa saat ini Jawa adalah salah satu bahasa pemrograman paling populer di dunia. Tanggal rilis resmi untuk Jawa adalah 23 Mei 1995.

Artikel ini dikhususkan untuk dasar-dasar: menguraikan fitur-fitur dasar bahasa, yang akan berguna untuk pemula "javists", dan pengembang Java yang berpengalaman akan dapat menyegarkan pengetahuan mereka.

* Artikel ini disusun berdasarkan laporan oleh Eugene Freiman - pengembang Java IntexSoft
Artikel tersebut berisi tautan ke materi eksternal .





1. JDK, JRE, JVM


Java Development Kit adalah kit pengembangan aplikasi Java . Ini termasuk Java Development Tools dan Java Runtime Environment ( JRE ).

Alat pengembangan Java mencakup sekitar 40 alat yang berbeda: javac (kompiler), java (peluncur aplikasi), javap (penyingkap file kelas java), jdb (debugger java), dll.

JRE runtime adalah paket dari semua yang diperlukan untuk menjalankan program Java yang dikompilasi. Termasuk mesin virtual JVM dan Java Class Library .

JVM adalah program yang dirancang untuk mengeksekusi bytecode. Keuntungan pertama JVM adalah prinsip "Tulis sekali, jalankan di mana saja" . Ini berarti bahwa aplikasi yang ditulis dalam Java akan bekerja sama pada semua platform. Ini adalah keuntungan besar dari JVM dan Java sendiri.

Sebelum Java, banyak program komputer ditulis untuk sistem komputer tertentu, dan preferensi diberikan kepada manajemen memori manual, karena lebih efisien dan dapat diprediksi. Sejak paruh kedua 1990-an, setelah munculnya Jawa, manajemen memori otomatis telah menjadi praktik umum.

Ada banyak implementasi JVM, baik komersial maupun open source. Salah satu tujuan menciptakan JVM baru adalah untuk meningkatkan kinerja untuk platform tertentu. Setiap JVM ditulis secara terpisah untuk platform, sementara dimungkinkan untuk menulisnya sehingga bekerja lebih cepat pada platform tertentu. Implementasi JVM yang paling umum adalah OpenJDK JVM Hotspot. Ada juga implementasi IBM J9 , Excelsior JET .

2. Eksekusi kode JVM


Menurut spesifikasi Java SE , untuk menjalankan kode di JVM, Anda perlu menyelesaikan 3 langkah:

  • Memuat bytecode dan instantiating kelas Class
    Secara kasar, untuk mendapatkan JVM, kelas harus dimuat. Ada kelas loader yang terpisah untuk ini, kami akan kembali kepada mereka nanti.
  • Menautkan atau menautkan
    Setelah memuat kelas, proses penautan dimulai, di mana bytecode diuraikan dan diperiksa. Proses penautan, pada gilirannya, terjadi dalam 3 langkah:

    - verifikasi atau verifikasi bytecode: kebenaran instruksi, kemungkinan stack overflow pada bagian kode ini, kompatibilitas jenis variabel diperiksa; periksa terjadi satu kali untuk setiap kelas;
    - persiapan atau persiapan: pada tahap ini, sesuai dengan spesifikasi, memori dialokasikan untuk bidang statis dan inisialisasi terjadi;
    - resolusi atau resolusi: resolusi tautan simbolik (ketika dalam bytecode kita membuka file dengan ekstensi .class, kita melihat nilai numerik alih-alih tautan simbolik).
  • Menginisialisasi objek Kelas yang dihasilkan
    Pada tahap terakhir, kelas yang kita buat diinisialisasi, dan JVM dapat mulai menjalankannya.

3. Loader kelas dan hierarki mereka


Kembali ke pemuat kelas, ini adalah kelas khusus yang merupakan bagian dari JVM. Mereka memuat kelas ke dalam memori dan membuatnya tersedia untuk dieksekusi. Loader bekerja dengan semua kelas: kelas kami dan kelas yang secara langsung dibutuhkan untuk Java.

Bayangkan situasinya: kami menulis aplikasi kami, dan selain kelas standar, ada kelas kami, dan ada banyak dari mereka. Bagaimana cara kerja JVM dengan ini? Java mengimplementasikan pemuatan kelas yang ditangguhkan, dengan kata lain pemuatan malas. Ini berarti bahwa memuat kelas tidak akan dilakukan sampai dalam aplikasi tidak ada panggilan ke kelas.

Hirarki Pemuat Kelas





Pemuat kelas pertama adalah classloader Bootstrap . Itu ditulis dalam C ++. Ini adalah pemuat dasar yang memuat semua kelas sistem dari arsip rt.jar . Pada saat yang sama, ada sedikit perbedaan antara memuat kelas dari rt.jar dan kelas kami: ketika JVM memuat kelas dari rt.jar , itu tidak melakukan semua langkah verifikasi yang dilakukan saat memuat file kelas lain sejak JVM awalnya menyadari bahwa semua kelas ini sudah divalidasi. Oleh karena itu, Anda tidak boleh memasukkan file Anda dalam arsip ini.

Bootloader berikutnya adalah classloader Ekstensi. Ini memuat kelas ekstensi dari folder jre / lib / ext . Misalkan Anda ingin kelas memuat setiap kali mesin Java dimulai. Untuk melakukan ini, Anda dapat menyalin file kelas sumber ke folder ini, dan itu akan dimuat secara otomatis.

Bootloader lain adalah System classloader . Itu memuat kelas dari classpath yang kami tentukan ketika aplikasi dimulai.

Proses memuat kelas terjadi dalam hierarki:

  • Pertama-tama, kami meminta pencarian di cache System Class Loader (cache loader sistem berisi kelas yang sudah dimuat olehnya);
  • Jika kelas tidak ditemukan di cache pemuat sistem, kami melihat cache Pemuat kelas ekstensi;
  • Jika kelas tidak ditemukan di cache pemuat ekstensi, kelas diminta dari pemuat Bootstrap.

Jika kelas tidak ditemukan di cache Bootstrap, itu mencoba memuat kelas ini. Jika Bootstrap tidak dapat memuat kelas, ia mendelegasikan pemuatan kelas ke pemuat ekstensi. Jika pada saat ini kelas dimuat, itu tetap dalam cache classloader ekstensi, dan pemuatan kelas selesai.

4. Struktur file kelas dan proses boot


Kami melanjutkan langsung ke struktur file Kelas.

Satu kelas yang ditulis dalam Java dikompilasi menjadi satu file dengan ekstensi .class. Jika ada beberapa kelas dalam file Java kita, satu file Java dapat dikompilasi menjadi beberapa file dengan ekstensi .class - file bytecode dari kelas ini.

Semua angka, string, pointer ke kelas, bidang dan metode disimpan di kolam Konstan - area memori ruang Meta . Deskripsi kelas disimpan di tempat yang sama dan berisi nama, pengubah, kelas-super, antarmuka-super, bidang, metode, dan atribut. Atribut, pada gilirannya, dapat berisi informasi tambahan apa pun.

Jadi, saat memuat kelas:

  • membaca file kelas, yaitu validasi format
  • representasi kelas dibuat di kolam Konstan (ruang Meta)
  • kelas super dan antarmuka super dimuat; jika mereka tidak dimuat, maka kelas itu sendiri tidak akan dimuat

5. Eksekusi bytecode pada JVM


Pertama-tama, untuk mengeksekusi bytecode, JVM dapat menafsirkannya . Interpretasi adalah proses yang agak lambat. Dalam proses interpretasi, penerjemah “menjalankan” baris demi baris melalui file kelas dan menerjemahkannya ke dalam perintah yang dapat dimengerti oleh JVM.

Juga, JVM dapat menyiarkannya , mis. kompilasi ke dalam kode mesin yang akan dieksekusi langsung pada CPU.

Perintah yang sering dieksekusi tidak akan ditafsirkan, tetapi akan segera disiarkan.

6. Kompilasi


Compiler adalah program yang mengubah bagian sumber program yang ditulis dalam bahasa pemrograman tingkat tinggi menjadi program bahasa mesin yang “dapat dimengerti” ke komputer.

Kompiler dibagi menjadi:

  • Tidak mengoptimalkan
  • Pengoptimalan sederhana (Klien Hotspot): bekerja dengan cepat, tetapi menghasilkan kode yang tidak optimal
  • Pengoptimalan kompleks (Server Hotspot): melakukan transformasi pengoptimalan kompleks sebelum membuat bytecode


Kompiler juga dapat diklasifikasikan berdasarkan waktu kompilasi:

  • Kompiler dinamis
    Mereka bekerja bersamaan dengan program, yang memengaruhi kinerja. Penting bahwa kompiler ini dijalankan pada kode yang sering dieksekusi. Selama pelaksanaan program, JVM tahu kode mana yang paling sering dieksekusi, dan agar tidak terus menafsirkannya, mesin virtual segera menerjemahkannya ke dalam perintah yang sudah akan dieksekusi langsung pada prosesor.
  • Kompiler Statis
    Kompilasi lebih lama, tetapi hasilkan kode optimal untuk dieksekusi. Dari pro: mereka tidak memerlukan sumber daya selama pelaksanaan program, setiap metode dikompilasi menggunakan optimasi.

7. Organisasi memori di Jawa


Tumpukan adalah wilayah memori di Jawa yang bekerja sesuai dengan skema LIFO - " Last in - Fisrt Out " atau " Last In, First Out ".



Diperlukan untuk menyimpan metode. Variabel pada stack ada selama metode di mana mereka dibuat dieksekusi.

Ketika metode apa pun dipanggil di Java, bingkai atau area memori dibuat di tumpukan, dan metode diletakkan di atasnya. Ketika suatu metode menyelesaikan eksekusi, itu dihapus dari memori, dengan demikian membebaskan memori untuk metode-metode berikut. Jika memori tumpukan penuh, Java akan melempar pengecualian java.lang.StackOverFlowError . Misalnya, ini bisa terjadi jika kita memiliki fungsi rekursif yang akan memanggil dirinya sendiri dan tidak akan ada cukup memori pada stack.

Fitur utama tumpukan:

  • Tumpukan diisi dan dibebaskan saat metode baru dipanggil dan diselesaikan.
  • Akses ke area memori ini lebih cepat daripada tumpukan.
  • Ukuran tumpukan ditentukan oleh sistem operasi.
  • Ini adalah thread yang aman, karena setiap tumpukan memiliki tumpukan yang terpisah.

Area memori lain di Jawa adalah Heap atau heap . Ini digunakan untuk menyimpan objek dan kelas. Objek baru selalu dibuat di heap, dan referensi ke mereka disimpan di stack. Semua objek di heap memiliki akses global, yaitu, mereka dapat diakses dari mana saja di aplikasi.

Tumpukan dibagi menjadi beberapa bagian yang lebih kecil yang disebut generasi:

  • Generasi muda - area di mana objek yang baru dibuat berada
  • Generasi lama (bertenor) - area tempat benda "berumur panjang" disimpan
  • Sebelum ke Java 8, ada area lain - Generasi Permanen - yang berisi meta-informasi tentang kelas, metode, dan variabel statis. Setelah munculnya Java 8, diputuskan untuk menyimpan informasi ini secara terpisah, di luar heap, yaitu di ruang Meta




Mengapa meninggalkan generasi permanen? Pertama-tama, ini disebabkan oleh kesalahan yang dikaitkan dengan luapan area: karena Perm memiliki ukuran konstan dan tidak dapat berkembang secara dinamis, cepat atau lambat memori habis, kesalahan dilemparkan, dan aplikasi macet.

Ruang meta memiliki ukuran dinamis, dan saat runtime dapat diperluas ke ukuran memori JVM.

Fitur tumpukan utama:

  • Ketika area memori ini penuh, Java melempar java.lang.OutOfMemoryError
  • Akses tumpukan lebih lambat dari akses tumpukan
  • Pengumpul sampah berfungsi untuk mengumpulkan benda-benda yang tidak terpakai
  • Tumpukan, tidak seperti tumpukan, tidak aman untuk thread, karena thread apa pun dapat mengaksesnya


Berdasarkan informasi di atas, pertimbangkan bagaimana manajemen memori dilakukan menggunakan contoh sederhana:

public class App { public static void main(String[] args) { int id = 23; String pName = "Jon"; Person p = null; p = new Person(id, pName); } } class Person { int pid; String name; // constructors, getters/setters } 


Kami memiliki kelas App di mana satu-satunya metode utama terdiri dari:

- variabel id primitif dari tipe int dengan nilai 23
- Variabel referensi pName dari tipe String dengan nilai Jon
- variabel referensi p dari tipe orang



Seperti yang telah disebutkan, ketika suatu metode dipanggil, area memori dibuat di bagian atas tumpukan di mana data yang diperlukan untuk metode ini untuk disimpan disimpan.
Dalam kasus kami, ini adalah referensi ke kelas orang : objek itu sendiri disimpan di heap, dan tautan disimpan di tumpukan. Tautan ke string juga didorong ke tumpukan, dan string itu sendiri disimpan di tumpukan di kolam String. Primitif disimpan langsung di tumpukan.

Untuk memanggil konstruktor dengan parameter Person (String) dari metode main () pada stack, di atas panggilan main () sebelumnya, frame terpisah dibuat pada stack yang menyimpan:

- ini - tautan ke objek saat ini
- nilai id primitif
- variabel personName referensi, yang menunjuk ke sebuah string di String Pool.

Setelah kami memanggil konstruktor, setPersonName () dipanggil, setelah itu frame baru dibuat di stack lagi, di mana data yang sama disimpan: referensi objek, referensi garis, nilai variabel.

Jadi, ketika metode setter dieksekusi, frame menghilang, tumpukan dihapus. Selanjutnya, konstruktor dieksekusi, bingkai yang dibuat untuk konstruktor dihapus, setelah itu metode main () menyelesaikan pekerjaannya dan juga dihapus dari tumpukan.

Jika metode lain dipanggil, frame baru juga akan dibuat untuk mereka dengan konteks metode spesifik ini.

8. Pengumpul sampah


Pengumpul sampah sedang mengerjakan heap - program yang berjalan di mesin virtual Java yang menghilangkan objek yang tidak dapat diakses.

JVM yang berbeda mungkin memiliki algoritma pengumpulan sampah yang berbeda, ada juga pengumpul sampah yang berbeda.

Kami akan berbicara tentang kolektor Serial GC paling sederhana. Kami meminta pengumpulan sampah menggunakan System.gc () .



Seperti disebutkan di atas, heap dibagi menjadi 2 area: Generasi baru dan generasi lama.

Generasi baru (generasi muda) meliputi 3 wilayah: Eden , Survivor 0 dan Survivor 1 .

Generasi tua termasuk wilayah Tenured .

Apa yang terjadi ketika kita membuat objek di Jawa?

Pertama-tama, benda itu jatuh ke Eden . Jika kita telah membuat banyak objek dan tidak ada lagi ruang di Eden , pengumpul sampah akan terbakar dan membebaskan memori. Ini adalah apa yang disebut pengumpulan sampah kecil - pada lintasan pertama, ia membersihkan daerah Eden dan menempatkan benda-benda yang "selamat" di wilayah Survivor 0 . Dengan demikian, wilayah Eden sepenuhnya dibebaskan.

Jika kebetulan area Eden penuh lagi, pemulung mulai bekerja dengan area Eden dan Survivor 0 , yang saat ini diduduki. Setelah pembersihan, benda-benda yang selamat akan jatuh ke wilayah lain - Survivor 1 , dan dua lainnya akan tetap bersih. Setelah pengumpulan sampah berikutnya, Survivor 0 akan dipilih kembali sebagai daerah tujuan. Itu sebabnya penting bahwa salah satu wilayah Survivor selalu kosong.

JVM memonitor objek yang secara konstan disalin dan dipindahkan dari satu daerah ke daerah lain. Dan untuk mengoptimalkan mekanisme ini, setelah melewati batas tertentu, pengumpul sampah memindahkan benda-benda tersebut ke wilayah Bertenor .

Ketika tidak ada cukup ruang untuk objek baru di Tenured , ada pengumpulan sampah lengkap - Mark-Sweep-Compact .



Selama mekanisme ini, ditentukan objek mana yang tidak lagi digunakan, wilayah dibersihkan dari objek-objek ini, dan area memori Bertahan didefragmentasi, mis. secara berurutan diisi dengan benda-benda yang diperlukan.

Kesimpulan


Dalam artikel ini, kami memeriksa alat dasar bahasa Jawa: JVM, JRE, JDK, prinsip dan tahapan pelaksanaan kode JVM, kompilasi, organisasi memori, serta prinsip pengumpul sampah.

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


All Articles