Java berkembang dengan mekanisme baru yang trendi, dan bersama dengan itu, makamnya tumbuh dengan fitur yang sudah usang seperti Vector, Finalization, NashornScriptEngine, SecurityManager, dan Unsafe. Bagaimanakah kuburan itu diisi? Java berkembang dan berkembang, mengumpulkan fitur baru, API, dan modul, tetapi pada saat yang sama, itu membuang kulit lama dari waktu ke waktu. Beberapa bagian terbukti berlebihan, yang lain tidak lagi cocok dengan tugas modern, dan beberapa selalu dimaksudkan sebagai kludges. Mari kita lihat beberapa di antaranya. Apa yang ada di dalam kubur? Koleksi Legacy Kembali ke hari-hari sebelum Java memiliki , yang atau yang liar , yang dan Berbeda dengan semua antiquities berikutnya, API yang sudah usang ini tidak ditandai sebagai Namun, pada saat yang sama, yang untuk setiap kelas berteriak "Jangan Gunakan Saya!" Di luasnya internet, kelas-kelas yang sudah usang ini biasanya disebut . List Deque Map Vector Stack Dictionary Deprecated Dokumentasi legacy collection classes Mengapa mereka sudah usang? Koleksi legacy memiliki dua kelemahan utama: semua titik interaksi dengan kelas-kelas ini disinkronkan; kelas tidak memiliki antarmuka umum dan membutuhkan pendekatan individual. Mari kita pergi dalam urutan. Kelemahan pertama adalah** bahwa semua titik interaksi dengan kelas-kelas ini disinkronkan.** Namun, itu lebih terasa seperti keuntungan daripada sebaliknya. Setelah semua, multithreading yang aman selalu menjadi hal yang baik. Tapi bagaimana jika koleksi ini digunakan dalam konteks single-threaded? Sinkronisasi hanya mengurangi kinerja tanpa memberikan manfaat keamanan nyata. Ini jelas tercermin dalam praktik Java modern, di mana pengembang lebih suka menyalin koleksi daripada memodifikasi mereka. Kelemahan kedua** adalah bahwa kelas tidak memiliki antarmuka umum dan membutuhkan pendekatan individual.** Ini mengarah pada masalah yang sama tua dengan pemrograman itu sendiri: kode non-universal. hanya bayangkan bahwa kita adalah pengembang yang bekerja pada versi Java yang lama, dan kita memiliki kode yang terikat dengan Misalnya, di sini ada satu: Stack Stack<Integer> stack = new Stack<>(); for (int i = 0; i < 5; i++) { stack.add(i); } System.out.println(stack.pop()); Saat ini, kode ini tidak ada hitch, tetapi bayangkan bahwa itu telah melonjak menjadi 1.000 baris dengan 150 referensi ke yang variabel. stack Kemudian suatu hari, pelanggan mengirimkan faks kepada kami, mengatakan mereka bekerja sangat lambat (blok kode, bukan pelanggan). mengandung array untuk menyimpan elemen, dan array ini sendiri direproduksi terlalu sering. Stack Kami berpikir dan mengemukakan teori: daftar yang terhubung dapat membantu meningkatkan kinerja aplikasi sebanyak 100 kali! di internet—dan kemudian roll up sarung tangan kami untuk menyalin dan refactor semua 150 referensi ke Tidak sama sekali, tapi kita tidak melihat jalan keluar lain. LinkedList stack Mari kita lanjutkan ke saat ini, di mana kode kita terlihat seperti ini: Deque<Integer> deque = new ArrayDeque<>(); IntStream.range(0, 5) .forEach(deque::addLast); System.out.println(deque.pollLast()); Seiring waktu, kode juga tumbuh untuk mencakup 150 referensi, tetapi sekarang memiliki referensi untuk Kami mengikuti jalur yang sama dan juga mengemukakan teori bahwa itu akan lebih cepat dengan daftar tertaut. Dua yang juga menerapkan Dan itu semua yang dibutuhkan! deque ArrayDeque LinkedList Deque Ini adalah persis bagaimana memecahkan masalah kurangnya antarmuka umum. Ini membantu menghindari terikat pada implementasi tertentu. misalnya, pengembang tidak lagi terkunci dalam sesuatu seperti Dan dapat digunakan untuk Interface saja ya. Kerangka Koleksi Vector List Finalisasi Pada suatu waktu, (Dengan ) tampak seperti ide yang sangat baik: metode yang akan dipanggil ketika sebuah objek dihancurkan, melepaskan sumber daya sistem. Finalisasi #finalize() Tetapi, dalam praktek, gagasan ini tidak begitu brilian: finalis mungkin tidak dipanggil sama sekali jika masih ada referensi ke objek di suatu tempat atau jika pengumpul sampah tidak ingin menghapusnya. jika pengumpul sampah benar-benar ingin menghancurkan objek, ia masih harus menunggu sampai kode ini dijalankan; objek bahkan dapat bangkit kembali sendiri; Paling buruk, finalis menciptakan kondisi yang sempurna untuk kebocoran memori. Mari kita mulai dari jauh. Java modern sangat fleksibel; bahkan pengumpul sampah dapat dari segala jenis dan dikonfigurasi dengan cara yang berbeda. Dan jangan lupa bahwa pengumpulan sampah dijamin tidak akan mengumpulkan instansi selama memiliki setidaknya satu referensi yang kuat. Untuk alasan ini, logika melepaskan sumber daya sistem tidak harus tergantung pada siklus hidup objek. Jika kita melihat dokumentasi untuk metode, kita melihat bahwa dianjurkan untuk menggunakan Ini dibuat khusus untuk secara efisien melepaskan sumber daya tanpa melibatkan pengumpul sampah. untuk melakukannya. #finalize() AutoCloseable Jangan Lupa Untuk melacak kapan sebuah objek benar-benar dihapus, Java menyediakan alternatif: , yang memecahkan masalah yang dijelaskan di atas. java.lang.ref.Cleaner Untuk kejelasan, mari kita bermain-main dengan memori Java dan menulis beberapa kode yang tidak aman menggunakan finisher: class ImmortalObject { private static Collection<ImmortalObject> triedToDelete = new ArrayList<>(); @Override protected void finalize() throws Throwable { triedToDelete.add(this); } } Di sini, kita secara harfiah membangkitkan setiap contoh bahwa pengumpulan sampah akan dihapus, yang tidak akan memuaskan RAM Anda atau Anda sendiri ketika bertabrakan dengan . ImmortalObject OutOfMemoryError Inilah yang terlihat dari kode yang sama dengan : Cleaner static List<ImmortalObject> immortalObjects = new ArrayList<>(); .... var immortalObject = new ImmortalObject(); Cleaner.create() .register( immortalObject, () -> immortalObjects.add(immortalObject) ); Perlu dicatat bahwa kita juga dapat mendengarkan penghapusan objek yang sudah ada. Stop! Apakah kita baru saja belajar mengapa pendekatan itu bermasalah? Itu benar, tetapi itu hanya buruk dengan manajemen sumber daya. Terkadang aplikasi Java masih perlu tahu bahwa pengumpulan sampah menghapus sebuah objek. Kelas masih hidup sampai namanya, tetapi tidak karena kita menambahkan ke daftar statis ketika kita menghapus instansi. sebagai pendengar di sini memiliki referensi untuk Sekarang objek tidak akan dibangkitkan kembali karena pengumpulan sampah tidak akan menghapus instansi. lebih idiomatis dalam Java dan tidak ada necromancy yang terlibat. Runnable immortalObject Finalizer sering digunakan ketika bekerja dengan objek yang dibuat melalui JNI di suatu tempat di luar kode Java, misalnya, di C++. NashornSebelumnya Sedikit yang ingat bahwa Java pernah secara harfiah menjaga kecepatan dengan JavaScript: JDK 8 menampilkan built-in JS engine, yang memungkinkan pengembang untuk menjalankan kode JS langsung dari Java. Nashorn Kita bisa menerapkan teka-teki pisang yang sangat populer tanpa meninggalkan Java: new NashornScriptEngineFactory() .getScriptEngine() .eval("('b' + 'a' + + 'a' + 'a').toLowerCase();"); Memang, itu hal yang keren, tetapi hanya sampai kita menyadari bahwa pengembang Java harus mempertahankan mesin JavaScript. Nashorn tidak bisa mengikuti perkembangan cepat JavaScript, serta muncul solusi polyglot, seperti GraalVM Polyglot. faktor-faktor ini menyebabkan mesin menjadi usang setelah hanya empat versi bahasa (dalam Java 11), dan setelah beberapa waktu, itu akhirnya Untuk Java 15. Tembakan Overboard Sementara Nashorn telah mengimplementasikan JavaScript di atas Java, Polyglot menciptakan seluruh platform berbasis JVM yang secara natif mendukung beberapa bahasa, termasuk Java dan JavaScript. Manajer Keamanan Temukan timer lama lainnya yang bertahan sampai Java 17— . SecurityManager Pada hari-hari applets liar, ketika kode Java dapat dijalankan langsung di browser, adalah tulang belakang dari keamanan aplikasi Java. SecurityManager Applet adalah program kecil yang berjalan langsung di browser dan dapat menampilkan animasi, grafis, atau elemen interaktif, tetapi beroperasi di bawah batasan ketat. Applet adalah program kecil yang berjalan langsung di browser dan dapat menampilkan animasi, grafis, atau elemen interaktif, tetapi beroperasi di bawah batasan ketat. Waktu berlalu, si liar akhirnya bermigrasi dari browser ke desktop, dan kemudian mati sepenuhnya sebagai solusi. masih dan sering digunakan dalam aplikasi perusahaan. Apelnya SecurityManager yang Tapi tidak ada yang bertahan selamanya, dan Grim Reaper datang untuk Java 17 menghapus seluruh interiornya dan meninggalkan hanya fasad tanpa logika apa pun. Kita hanya akan menemukan tempat-tempat: SecurityManager SecurityManager @Deprecated(since = "17", forRemoval = true) public static void setSecurityManager(SecurityManager sm) { throw new UnsupportedOperationException( "Setting a Security Manager is not supported"); } Masuk mode fullscreen keluar mode fullscreen Bagaimana yang dilakukan Ini bertindak sebagai penjaga internal JVM yang menangkap operasi yang berpotensi berbahaya dan memeriksa apakah setiap elemen stack memiliki hak untuk melakukan tindakan tersebut. SecurityManager Apa yang sebenarnya dilakukan Daftar itu besar, jadi saya sarankan kita hanya melihat poin-poin utama: SecurityManager Akses sistem file; koneksi jaringan; Mengembangkan dan menyelesaikan proses; Akses ke Properti Sistem; manipulasi dengan kelas dan refleksi. bekerja dengan baik di zaman applets browser, di mana perlu untuk menyimpan kode yang tidak dapat dipercaya dalam sandbox, tetapi dengan pengembangan kontainerisasi, itu menjadi usang. SecurityManager Meskipun awalnya dibuat untuk applets, itu juga digunakan dalam konteks lain. misalnya, Tomcat Dalam analis statis PVS-Studio kami, itu membantu memecahkan masalah yang menyenangkan: jika inisialis kelas statis dalam kode yang dianalisis disebut Proses analisis akan berakhir. memungkinkan kita untuk memblokir operasi seperti itu ketika menganalisis bidang statis, dan sesuatu yang kemudian kita ganti dengan ASM. SecurityManager didukung System#exit SecurityManager Selain itu, dengan adanya aplikasi ini, para penggunanya akan lebih cepat Namun, mereka baru saja mulai dihapus dari JDK. SecurityManager Dunia berubah, pendekatan berubah, dan Ia sedang pergi. SecurityManager Tidak Aman Java menghapus ketidakamanan secara harfiah. Mungkin penduduk paling legendaris dari kuburan ini adalah . sun.misc.Unsafe Kelas ini adalah gerbang rahasia ke kedalaman JVM, di mana aturan bahasa biasa tidak lagi berlaku. Cara Mudah Menggunakan Aplikasi Java . Unsafe SIGSEGV Contohnya seperti ini: class Container { Object value; // (1) } // ✨ some magic to get unsafe unsafely ✨ Unsafe unsafe = ....; long Container_value = unsafe.objectFieldOffset(Container.class.getDeclaredField("value")); var container = new Container(); unsafe.getAndSetLong(container, Container_value, Long.MAX_VALUE); // (2) System.out.println(container.value); // (3) Di dalam field (1), we set 2) Sebagai gantinya dan coba output nilai untuk (3) yang value Long.MAX_VALUE Object System.out Mesin virtual tidak suka trik ini, dan itu crash pada baris terakhir karena mengandung sampah alih-alih referensi nyata ke objek. dan ini hanyalah salah satu contoh kesalahan yang mudah ditemukan. value Tapi mengapa mekanisme berbahaya seperti itu ditambahkan ke Java? Dia tidak dilahirkan sebagai seorang anarkis, tetapi sebagai seorang penyelamat. dan (kita akan berbicara tentang mereka nanti), perpustakaan standar harus melakukan operasi tingkat rendah seperti manajemen memori, operasi atom, atau memblokir thread secara efisien. Unsafe VarHandle MemorySegment Tapi bagaimana kita bisa melakukan semua yang di atas jika bahasa melarang Anda melihat di bawah topi? Terlihat sebagai sebuah Untuk memenuhi kebutuhan Java sendiri. Unsafe internal Pengembang bermaksud untuk menggunakannya hanya dalam Java – dan itu bahkan memiliki perlindungan terhadap mendapatkan akses eksternal ke instansi. tetapi apa yang terjadi di Java, tidak tinggal di Java, dan pengembang menemukan cara untuk mendapatkan Lihatlah sihir yang menangkap "ketidakamanan" dengan cara yang tidak aman: Unsafe var Unsafe_theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); Unsafe_theUnsafe.setAccessible(true); Unsafe unsafe = (Unsafe) Unsafe_theUnsafe.get(null); Objek ini memungkinkan kita untuk menyiksa mesin virtual sebanyak yang kita inginkan. Tetapi jangan terburu-buru untuk mengkritik pengembang yang berani mencapai apa yang Java berusaha begitu keras untuk menyembunyikan dari mereka; itu layak diingat: akses ke membantu banyak perpustakaan menjadi lebih efisien. misalnya, perpustakaan Netty populer untuk bekerja dengan jaringan dipaksa untuk menggunakan Dan ketika sebuah pengganti datang, Netty untuk itu. Unsafe Unsafe Bergantian Jadi, jenis pengganti apa yang sedang kita bicarakan? Tidak ada satu, tetapi dua: Foreign Function & Memory API menggantikan operasi memori. VarHandle API menggantikan operasi atom. API ini tidak terbatas hanya untuk menggantikan metode tertentu dalam Mereka adalah alat yang kuat untuk bekerja dengan aman dengan operasi tingkat rendah. misalnya, Foreign Function & Memory API memungkinkan untuk memanggil fungsi asli tanpa menggunakan JNI yang membosankan! Unsafe Ketidakpastian Java perlahan-lahan menjadi lupa, meninggalkan APIs baru, dirancang dengan baik, dan kuat yang melakukan hal yang sama tanpa takut akan kondisi. . SIGSEGV Mengapa kuburan itu dipenuhi? Java tumbuh, membuang ide-ide yang sudah usang, menjadi lebih aman, lebih bersih, dan lebih sederhana. setiap API baru tidak hanya "menambahkan modul lain", tetapi menutup kesenjangan lama dengan cara yang lebih canggih. Pada saat yang sama, virtual “tomb” memainkan peran budaya yang penting: ia mencatat evolusi ide-ide dan keputusan, mengingatkan kita pada jalan yang telah kita jalani dan membentuk konteks yang diperlukan untuk pengembangan platform yang berarti.