Java đang mở rộng với các cơ chế thời trang mới, và cùng với nó, mộ của nó đang phát triển với các tính năng lỗi thời như Vector, Finalization, NashornScriptEngine, SecurityManager và Unsafe. Ngôi mộ được lấp đầy như thế nào? Java phát triển và phát triển, tích lũy các tính năng mới, API và mô-đun, nhưng đồng thời, nó tháo rời làn da cũ của nó theo thời gian.Một số bộ phận chứng tỏ là dư thừa, những bộ phận khác không còn phù hợp với các nhiệm vụ hiện đại, và một số luôn được dự định như là khe hở.Tất cả các cơ chế lỗi thời hoặc bị loại bỏ này lặng lẽ và ổn định lấp đầy mộ của Java. Chúng ta hãy nhìn vào một số trong số họ. Bên trong mộ có gì? Legacy bộ sưu tập Những ngày trước khi Java có , hoặc hoang dã , và Không giống như tất cả các cổ xưa sau này, API lỗi thời này không được đánh dấu là Tuy nhiên, đồng thời, các Đối với mỗi lớp học hét lên "Đừng sử dụng tôi!" Trên sự rộng lớn của Internet, những lớp học đã lỗi thời này thường được gọi là . List Deque Map Vector Stack Dictionary Deprecated tài liệu legacy collection classes Nhưng tại sao chúng bị lỗi thời? Bộ sưu tập Legacy có hai nhược điểm chính: tất cả các điểm tương tác với các lớp này được đồng bộ hóa; Các lớp học không có giao diện chung và đòi hỏi một cách tiếp cận cá nhân. Chúng ta đi theo trật tự. Nhược điểm đầu tiên là** rằng tất cả các điểm tương tác với các lớp này được đồng bộ hóa.** Tuy nhiên, nó có vẻ như là một lợi thế hơn là ngược lại. Sau khi tất cả, bảo mật multithreading luôn là một điều tốt. Nhưng nếu những bộ sưu tập này được sử dụng trong một bối cảnh duy nhất? Đồng bộ hóa chỉ đơn giản là giảm hiệu suất mà không cung cấp bất kỳ lợi ích bảo mật thực sự. Điều này được phản ánh rõ ràng trong thực tiễn Java hiện đại, nơi các nhà phát triển thích sao chép các bộ sưu tập thay vì sửa đổi chúng. Nhược điểm thứ hai** là các lớp không có giao diện chung và đòi hỏi một cách tiếp cận cá nhân.** Điều này dẫn đến một vấn đề cũ như bản thân lập trình: mã không phổ quát.Chỉ cần tưởng tượng rằng chúng ta là một nhà phát triển đang làm việc trên một phiên bản Java cũ, và chúng ta có mã gắn liền với Ví dụ, đây là một: Stack Stack<Integer> stack = new Stack<>(); for (int i = 0; i < 5; i++) { stack.add(i); } System.out.println(stack.pop()); Tại thời điểm này, mã không có bất kỳ nhược điểm nào, nhưng hãy tưởng tượng rằng nó đã bốc lên đến 1.000 dòng với 150 tham chiếu đến biến đổi stack Sau đó một ngày, khách hàng gửi fax cho chúng tôi, nói rằng họ làm việc rất chậm (các khối mã, không phải khách hàng). chứa một mảng để lưu trữ các yếu tố, và chính mảng này được tái tạo quá thường xuyên. Stack Chúng tôi suy nghĩ và đưa ra một lý thuyết: danh sách được liên kết có thể giúp tăng hiệu suất ứng dụng gấp 100 lần! trên internet - và sau đó cuộn tay áo của chúng tôi để sao chép và tái tạo tất cả 150 tham chiếu đến Không phải, nhưng chúng ta không thấy cách nào khác. LinkedList stack Chúng ta hãy chuyển sang hiện tại, nơi mã của chúng ta trông như thế này: Deque<Integer> deque = new ArrayDeque<>(); IntStream.range(0, 5) .forEach(deque::addLast); System.out.println(deque.pollLast()); Theo thời gian, mã cũng phát triển để bao gồm 150 tham chiếu, nhưng bây giờ nó có tham chiếu đến Chúng tôi đi theo cùng một con đường và cũng đưa ra lý thuyết rằng nó sẽ nhanh hơn với một danh sách được liên kết. hai , cũng thực hiện Và đó là tất cả những gì nó cần! deque ArrayDeque LinkedList Deque Đây chính xác là cách mà các Giải quyết vấn đề thiếu giao diện phổ biến. Nó giúp tránh bị ràng buộc với các ứng dụng cụ thể. Ví dụ, các nhà phát triển không còn bị khóa trong một cái gì đó như Và có thể sử dụng các Thay vào đó, interface Bộ sưu tập framework Vector List Kết thúc Trong một thời gian, (Những ) dường như là một ý tưởng tuyệt vời: một phương pháp sẽ được gọi khi một đối tượng bị phá hủy, giải phóng tài nguyên hệ thống. Kết thúc #finalize() Nhưng, trong thực tế, ý tưởng này không phải là tuyệt vời như vậy: Finaler có thể không được gọi nếu vẫn còn một tham chiếu đến đối tượng ở đâu đó hoặc nếu bộ sưu tập rác không muốn xóa nó. Nếu bộ sưu tập rác thực sự muốn phá hủy đối tượng, nó vẫn phải đợi cho đến khi mã này được thực hiện; đối tượng thậm chí có thể tự hồi sinh; Tồi tệ nhất, những người hoàn thành đã tạo ra điều kiện hoàn hảo cho sự rò rỉ trí nhớ. Hãy bắt đầu từ xa. Java hiện đại rất linh hoạt; ngay cả bộ sưu tập rác có thể thuộc bất kỳ loại nào và được cấu hình theo những cách khác nhau. Và đừng quên rằng bộ sưu tập rác được đảm bảo sẽ không thu thập một trường hợp miễn là nó có ít nhất một tham chiếu mạnh. Vì những lý do này, logic của việc phát hành tài nguyên hệ thống không nên phụ thuộc vào vòng đời của đối tượng. Nếu chúng ta nhìn vào tài liệu về phương pháp, chúng tôi thấy rằng nó được khuyến khích sử dụng các giao diện thay vào đó. Nó được tạo ra đặc biệt để giải phóng tài nguyên một cách hiệu quả mà không liên quan đến bộ sưu tập rác. to do it. #finalize() AutoCloseable Không quên Đối với việc theo dõi khi một đối tượng thực sự bị xóa, Java cung cấp một lựa chọn thay thế: , giải quyết các vấn đề được mô tả ở trên. java.lang.ref.Cleaner Để rõ ràng, chúng ta hãy chơi xung quanh với bộ nhớ Java và viết một số mã không an toàn bằng cách sử dụng một finalizer: class ImmortalObject { private static Collection<ImmortalObject> triedToDelete = new ArrayList<>(); @Override protected void finalize() throws Throwable { triedToDelete.add(this); } } Ở đây, chúng ta thực sự phục sinh mọi trường hợp của rằng bộ sưu tập rác sắp bị xóa, điều này sẽ không làm hài lòng RAM của bạn hoặc chính bạn khi nó va chạm với một . ImmortalObject OutOfMemoryError Đây là cách mã tương đương trông như thế nào với : Cleaner static List<ImmortalObject> immortalObjects = new ArrayList<>(); .... var immortalObject = new ImmortalObject(); Cleaner.create() .register( immortalObject, () -> immortalObjects.add(immortalObject) ); Điều đáng chú ý là chúng ta cũng có thể lắng nghe việc xóa hoàn toàn bất kỳ đối tượng hiện có nào. Ngừng! Chúng ta chỉ mới biết tại sao cách tiếp cận đó là có vấn đề? Đó là sự thật, nhưng nó chỉ tồi tệ với quản lý tài nguyên. Đôi khi một ứng dụng Java vẫn cần biết rằng bộ sưu tập rác xóa một đối tượng. Lớp vẫn tồn tại đến tên của nó, nhưng không phải vì chúng tôi thêm nó vào một danh sách tĩnh khi chúng tôi xóa trường hợp. đó hoạt động như người nghe ở đây giữ một tham chiếu đến Bây giờ đối tượng sẽ không được hồi sinh bởi vì bộ sưu tập rác sẽ không xóa các trường hợp. nó là idiomatic hơn trong Java và không có necromancy liên quan. Runnable immortalObject Finalizers thường được sử dụng khi làm việc với các đối tượng được tạo ra thông qua JNI ở đâu đó bên ngoài mã Java, ví dụ, trong C++. NashornSửa đổi Rất ít người nhớ rằng Java từng theo nghĩa đen theo kịp với JavaScript: JDK 8 có tính năng tích hợp JS engine, cho phép các nhà phát triển chạy JS code trực tiếp từ Java. Nashorn Chúng ta có thể thực hiện câu đố chuối rất phổ biến mà không rời khỏi Java: new NashornScriptEngineFactory() .getScriptEngine() .eval("('b' + 'a' + + 'a' + 'a').toLowerCase();"); Thật vậy, đó là một điều tuyệt vời, nhưng chỉ cho đến khi chúng ta nhận ra rằng các nhà phát triển Java phải duy trì công cụ JavaScript. Nashorn không thể theo kịp với sự tiến hóa nhanh chóng của JavaScript, cũng như các giải pháp đa ngữ xuất hiện, chẳng hạn như GraalVM Polyglot. Những yếu tố này dẫn đến công cụ trở nên lỗi thời sau khi chỉ có bốn phiên bản ngôn ngữ (trong Java 11), và sau một thời gian, nó cuối cùng đã Trong Java 15. Thả Overboard Tại sao GraalVM Polyglot tốt hơn? Trong khi Nashorn đã thực hiện JavaScript trên Java, Polyglot tạo ra một nền tảng toàn bộ dựa trên JVM hỗ trợ nhiều ngôn ngữ, bao gồm Java và JavaScript. Quản lý an ninh Gặp gỡ một bộ đếm thời gian cũ còn tồn tại cho đến khi Java 17 . SecurityManager Trong những ngày của applets hoang dã, khi mã Java có thể được thực hiện trực tiếp trong trình duyệt, Đây là nền tảng của Java Application Security. SecurityManager Một applet là một chương trình nhỏ chạy trực tiếp trong một trình duyệt và có thể hiển thị hình ảnh động, đồ họa hoặc các yếu tố tương tác, nhưng hoạt động dưới những hạn chế nghiêm ngặt. Một applet là một chương trình nhỏ chạy trực tiếp trong một trình duyệt và có thể hiển thị hình ảnh động, đồ họa hoặc các yếu tố tương tác, nhưng hoạt động dưới những hạn chế nghiêm ngặt. Thời gian trôi qua, hoang dã cuối cùng đã di chuyển từ trình duyệt đến máy tính để bàn, và sau đó chết hoàn toàn như một giải pháp. vẫn còn và thường được sử dụng trong các ứng dụng doanh nghiệp. Ứng dụng SecurityManager Thì But nothing lasts forever, and the Grim Reaper came for Java 17 đã loại bỏ toàn bộ nội thất và chỉ để lại mặt tiền mà không có bất kỳ logic nào. , chúng ta sẽ chỉ tìm thấy người giữ chỗ: SecurityManager SecurityManager @Deprecated(since = "17", forRemoval = true) public static void setSecurityManager(SecurityManager sm) { throw new UnsupportedOperationException( "Setting a Security Manager is not supported"); } Vào chế độ Full Screen Exit Mode Làm sao làm Nó hoạt động như một bảo vệ JVM nội bộ để chặn các hoạt động nguy hiểm tiềm tàng và kiểm tra xem mỗi yếu tố stack có quyền thực hiện hành động như vậy hay không. SecurityManager Chính xác những gì đã làm Danh sách là rất lớn, vì vậy tôi đề nghị chúng ta chỉ nhìn vào các điểm chính: SecurityManager Truy cập hệ thống file; kết nối mạng; Tạo và hoàn thành các quy trình; Truy cập vào các tài sản hệ thống; thao túng với các lớp học và phản ánh. Nó hoạt động tốt trong thời đại của các ứng dụng trình duyệt, nơi cần phải giữ mã không đáng tin cậy trong một hộp cát, nhưng với sự phát triển của containerization, nó đã trở nên lỗi thời. SecurityManager Tuy nhiên ban đầu được tạo ra cho applets, nó cũng được sử dụng trong các bối cảnh khác. ví dụ, Tomcat Trong máy phân tích tĩnh PVS-Studio của chúng tôi, nó đã giúp giải quyết một vấn đề thú vị: nếu bộ khởi tạo lớp tĩnh trong mã được phân tích được gọi là quá trình phân tích sẽ kết thúc. cho phép chúng tôi chặn các hoạt động như vậy khi phân tích các trường tĩnh, và một cái gì đó chúng tôi sau đó thay thế bằng ASM. SecurityManager ủng hộ System#exit SecurityManager Nhân tiện, mặc dù applets như một cách tiếp cận đã bị ngừng lại sớm hơn , họ chỉ mới bắt đầu được loại bỏ khỏi JDK. SecurityManager Thế giới đang thay đổi, cách tiếp cận đang thay đổi, và đang đi mất. SecurityManager Không an toàn Java loại bỏ sự không an toàn. Có lẽ cư dân huyền thoại nhất của ngôi mộ này là . sun.misc.Unsafe Lớp này là một cổng bí mật vào độ sâu của JVM, nơi các quy tắc ngôn ngữ thông thường không còn hiệu lực nữa. Có thể dễ dàng crash một ứng dụng Java với . Unsafe SIGSEGV Ví dụ như thế này: 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) Trong the trường (1), chúng tôi thiết lập (2) Thay vì và cố gắng đưa ra giá trị để 3 ) value Long.MAX_VALUE Object System.out Máy ảo không thích thủ thuật này, và nó sụp đổ ở dòng cuối bởi vì chứa rác thay vì một tham chiếu thực sự đến đối tượng. và đây chỉ là một ví dụ về một lỗi dễ tìm thấy như vậy. value Nhưng tại sao một cơ chế nguy hiểm như vậy lại được thêm vào Java? Ông không được sinh ra như một kẻ vô chính phủ, nhưng như một vị cứu tinh. và (chúng ta sẽ nói về chúng sau), các thư viện tiêu chuẩn phải thực hiện các hoạt động cấp thấp như quản lý bộ nhớ, hoạt động nguyên tử hoặc chặn chủ đề một cách hiệu quả. Unsafe VarHandle MemorySegment Nhưng làm thế nào chúng ta có thể làm tất cả những điều trên nếu ngôn ngữ cấm bạn nhìn dưới mũ? Phải, chúng ta có thể tạo ra một khoảng trống. xuất hiện như một công cụ cho các nhu cầu của Java. Unsafe internal Các nhà phát triển dự định chỉ sử dụng nó trong Java – và nó thậm chí còn có bảo vệ chống lại việc truy cập bên ngoài vào phiên bản của nó. nhưng những gì xảy ra trong Java, không ở lại trong Java, và các nhà phát triển đã tìm ra cách để có được Xem ma thuật bắt được một "không an toàn" trong một cách không an toàn: Unsafe var Unsafe_theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); Unsafe_theUnsafe.setAccessible(true); Unsafe unsafe = (Unsafe) Unsafe_theUnsafe.get(null); This object allows us to torture the virtual machine as much as we want. Nhưng đừng vội vã chỉ trích các nhà phát triển đã dám tiếp cận những gì Java đã cố gắng để che giấu khỏi họ; nó đáng nhớ: quyền truy cập vào giúp nhiều thư viện trở nên hiệu quả hơn. Ví dụ, thư viện Netty phổ biến để làm việc với các mạng đã buộc phải sử dụng Và khi một sự thay thế xuất hiện, Netty Hai cái đó. Unsafe Unsafe Thay đổi Vậy chúng ta đang nói về loại thay thế nào? Không phải là một mà là hai: Foreign Function & Memory API thay thế các hoạt động bộ nhớ. VarHandle API thay thế các hoạt động nguyên tử. Các API này không giới hạn chỉ đơn giản là thay thế một số phương pháp nhất định trong Chúng là các công cụ mạnh mẽ để làm việc an toàn với các hoạt động cấp thấp. Ví dụ, Foreign Function & Memory API cho phép gọi các chức năng bản địa mà không cần sử dụng JNI bận rộn! Unsafe Sự không an toàn của Java đang dần dần biến mất thành sự quên lãng, để lại những API mới, được thiết kế tốt và mạnh mẽ làm điều tương tự mà không sợ điều kiện. . SIGSEGV Tại sao ngôi mộ lại đầy? Java đang phát triển, loại bỏ các ý tưởng lỗi thời, trở nên an toàn hơn, sạch hơn và đơn giản hơn.Mỗi API mới không chỉ là "thêm một mô-đun khác", mà là đóng các khoảng trống cũ theo một cách tinh vi hơn. Đồng thời, ngôi mộ ảo đóng một vai trò văn hóa quan trọng: nó ghi lại sự tiến hóa của các ý tưởng và quyết định, nhắc nhở chúng ta về con đường chúng ta đã đi qua và tạo ra bối cảnh cần thiết cho sự phát triển nền tảng có ý nghĩa.