Java จะขยายตัวด้วยกลไกที่ทันสมัยใหม่และพร้อมกับมันจะเติบโตขึ้นด้วยคุณสมบัติที่หมดอายุเช่น Vector, Finalization, NashornScriptEngine, SecurityManager และ Unsafe ลองดูโบราณเหล่านี้และดูสิ่งที่แทนที่พวกเขา วิธีการกรวยที่เต็มไปด้วย Java มีความเจริญเติบโตและเติบโตโดยการสะสมฟีเจอร์ API และโมดูลใหม่ แต่ในเวลาเดียวกันก็ลบผิวเก่าของมันเป็นครั้งคราว บางส่วนพิสูจน์ให้เห็นว่าไม่เพียงพอบางส่วนไม่เหมาะกับงานที่ทันสมัยอีกต่อไปและบางส่วนมักจะมีวัตถุประสงค์เป็น kludges ทั้งหมดนี้กลไกที่ทันสมัยหรือถูกลบออกอย่างเงียบสงบและมั่นคงเติมฝาครอบของ Java ลองดูบางส่วนของพวกเขา อะไรอยู่ภายในกรง คอลเลกชัน Legacy กลับไปในวันก่อนที่ Java ได้ , หรือ เถื่อน , และ frolicked ในรหัส ไม่เหมือนกับโบราณทั้งหมดในภายหลัง API ที่หมดอายุนี้ไม่ได้ทําเครื่องหมายเป็น แต่ในเวลาเดียวกัน สําหรับแต่ละชั้นเรียนร้องไห้ "ไม่ใช้ฉัน!" ในความกว้างขวางของอินเทอร์เน็ตชั้นเรียนเหล่านี้หมดอายุมักจะเรียกว่า . List Deque Map Vector Stack Dictionary Deprecated หลักฐาน legacy collection classes แต่ทําไมพวกเขามีอายุเกินไป คอลเลกชัน Legacy มีข้อเสียสองประการหลัก: จุดโต้ตอบทั้งหมดกับชั้นเรียนเหล่านี้จะซิงโครไนซ์ ชั้นเรียนไม่ได้มีอินเตอร์เฟซร่วมกันและต้องการวิธีการแต่ละบุคคล ลองไปตามลําดับ ข้อเสียครั้งแรกคือ** ทั้งหมดจุดโต้ตอบกับชั้นเหล่านี้จะซิงโครไนซ์ อย่างไรก็ตามมันดูเหมือนเป็นข้อได้เปรียบมากกว่าตรงกันข้าม หลังจากที่ทั้งหมดการเชื่อมโยงหลายท่อที่ปลอดภัยเป็นสิ่งที่ดี แต่ถ้าคอลเลกชันเหล่านี้ถูกใช้ในแง่มุมแบบเดี่ยวหรือไม่? การซิงโครไนซ์เพียงแค่ลดประสิทธิภาพโดยไม่ต้องให้ประโยชน์ด้านความปลอดภัยที่แท้จริง สิ่งนี้สะท้อนให้เห็นอย่างชัดเจนในพฤติกรรม Java ของสมัยใหม่ซึ่งนักพัฒนาชอบคัดลอกคอลเลกชันแทนที่จะแก้ไข ข้อเสียที่สอง** คือว่าชั้นเรียนไม่ได้มีอินเทอร์เฟซร่วมกันและต้องใช้วิธีการแต่ละบุคคล** สิ่งนี้นําไปสู่ปัญหาที่เก่าแก่เท่ากับการเขียนโปรแกรมเอง: รหัสที่ไม่ใช่สากล เพียงจินตนาการว่าเราเป็นนักพัฒนาที่ทํางานบน Java รุ่นเก่าและเรามีรหัสที่เชื่อมโยงกับ ตัวอย่างเช่นนี่คือหนึ่ง: Stack Stack<Integer> stack = new Stack<>(); for (int i = 0; i < 5; i++) { stack.add(i); } System.out.println(stack.pop()); ในขณะนี้รหัสนี้ไม่มีรอยขีดข่วนใด ๆ แต่จินตนาการว่ามันได้กลายเป็นบรรทัด 1,000 กับ 150 การอ้างอิงถึง เปลี่ยนตัว stack จากนั้นวันหนึ่งลูกค้าส่งแฟกซ์ให้เราบอกว่าพวกเขาทํางานช้ามาก (บล็อกโค้ดไม่ใช่ลูกค้า) เราตระหนักว่า ประกอบด้วยมาร์เรย์สําหรับการจัดเก็บองค์ประกอบและมาร์เรย์นี้จะถูกสร้างใหม่ขึ้นบ่อยเกินไป Stack เราคิดและนําเสนอทฤษฎี: รายการที่เชื่อมโยงสามารถช่วยเพิ่มประสิทธิภาพของแอพได้ 100 เท่า! มีความปรารถนาที่จะทดสอบมันเราพบบางอย่าง บนอินเทอร์เน็ต - แล้วม้วนแขนของเราเพื่อคัดลอกและ refactor ทั้ง 150 การอ้างอิง สนุก? ไม่ใช่เลย แต่เราไม่เห็นทางออกอื่น LinkedList stack ลองไปสู่ปัจจุบันที่รหัสของเราดูดังนี้: Deque<Integer> deque = new ArrayDeque<>(); IntStream.range(0, 5) .forEach(deque::addLast); System.out.println(deque.pollLast()); เมื่อเวลาผ่านไปรหัสยังเติบโตขึ้นเพื่อรวมถึง 150 การอ้างอิง แต่ตอนนี้มันมีการอ้างอิงถึง เราทําตามเส้นทางเดียวกันและยังนําเสนอทฤษฎีว่ามันจะเร็วขึ้นด้วยรายการที่เชื่อมโยง และตอนนี้ ... เราเพียงแค่เปลี่ยนการประกาศจาก สอง ซึ่งยังใช้ และนี่คือสิ่งจําเป็นทั้งหมด! deque ArrayDeque LinkedList Deque นี่คือวิธีการที่ แก้ปัญหาการขาดอินเตอร์เฟซทั่วไป ช่วยหลีกเลี่ยงการผูกพันกับการใช้งานเฉพาะ ตัวอย่างเช่นนักพัฒนาไม่ได้ถูกปิดกั้นในบางสิ่งบางอย่างเช่น และสามารถใช้ อินเตอร์เฟซแทน กรอบการเก็บรวบรวม Vector List การเสร็จสิ้น ในเวลาหนึ่ง (ที่ ) ดูเหมือนเป็นความคิดที่ยอดเยี่ยม: วิธีการที่จะเรียกว่าเมื่อวัตถุถูกทําลายปล่อยทรัพยากรระบบ เสร็จสิ้น #finalize() แต่ในทางปฏิบัติความคิดนี้ไม่ได้ฉลาดเช่นนี้: ผู้เสร็จสิ้นอาจไม่เรียกร้องเลยหากยังคงมีการอ้างอิงถึงวัตถุที่ไหนสักแห่งหรือหากผู้เก็บขยะไม่ต้องการลบ ถ้าเครื่องรวบรวมขยะต้องการทําลายวัตถุก็ยังคงต้องรอจนกว่าจะดําเนินการรหัสนี้ วัตถุที่สามารถฟื้นตัวได้ด้วยตัวเอง สิ่งที่เลวร้ายที่สุดคือผู้สําเร็จรูปสร้างสภาพที่สมบูรณ์แบบสําหรับการรั่วไหลของหน่วยความจํา ลองเริ่มต้นจากระยะไกล Java แบบสมัยใหม่มีความยืดหยุ่นมากแม้กระทั่งเครื่องรวบรวมขยะสามารถเป็นชนิดใด ๆ และกําหนดค่าได้แตกต่างกัน ภายใต้เงื่อนไขดังกล่าวไม่สามารถรับประกันได้ว่าสําเนาจะถูกลบออกได้ และอย่าลืมว่าเครื่องรวบรวมขยะรับประกันว่าจะไม่รวบรวมตัวอย่างจนกว่าจะมีการอ้างอิงที่แข็งแกร่งอย่างน้อยหนึ่งตัว ด้วยเหตุผลเหล่านี้กลยุทธ์ของการปล่อยทรัพยากรระบบไม่ควรขึ้นอยู่กับวงจรชีวิตของวัตถุ ถ้าเราดูเอกสารสําหรับ วิธีการที่เราเห็นว่าขอแนะนําให้ใช้ อินเตอร์เฟซแทน มันถูกสร้างขึ้นโดยเฉพาะอย่างยิ่งเพื่อปล่อยทรัพยากรได้อย่างมีประสิทธิภาพโดยไม่ต้องมีส่วนร่วมกับเครื่องเก็บฝุ่น สิ่งสําคัญคือ เพื่อทํา #finalize() AutoCloseable ไม่ต้องลืม สําหรับการติดตามเมื่อวัตถุถูกลบแล้ว Java ให้ทางเลือก: ซึ่งแก้ปัญหาที่อธิบายไว้ข้างต้น java.lang.ref.Cleaner สําหรับความชัดเจนโปรดเล่นกับหน่วยความจํา Java และเขียนโค้ดที่ไม่ปลอดภัยโดยใช้ Finalizer: class ImmortalObject { private static Collection<ImmortalObject> triedToDelete = new ArrayList<>(); @Override protected void finalize() throws Throwable { triedToDelete.add(this); } } ที่นี่เรารู้คืนทุกตัวอย่างของ ที่เครื่องรวบรวมขยะจะถูกลบซึ่งจะไม่พึงพอใจกับ RAM ของคุณหรือคุณเองเมื่อมันขัดแย้งกับ . ImmortalObject OutOfMemoryError นี่คือสิ่งที่โค้ดเทียบเท่าดูเหมือนกับ : Cleaner static List<ImmortalObject> immortalObjects = new ArrayList<>(); .... var immortalObject = new ImmortalObject(); Cleaner.create() .register( immortalObject, () -> immortalObjects.add(immortalObject) ); เป็นที่น่าสังเกตว่าเรายังสามารถฟังการลบวัตถุที่มีอยู่ใด ๆ หยุด! เราเพิ่งเรียนรู้ว่าทําไมวิธีการนี้เป็นปัญหาหรือไม่? นั่นเป็นความจริง แต่ก็ไม่ดีกับการจัดการทรัพยากร บางครั้งแอปพลิเคชัน Java ยังคงต้องรู้ว่าเครื่องรวบรวมขยะลบวัตถุ เช่นสําหรับอะไร ตัวอย่างเช่นเพื่อสร้างแคชที่เพิ่มประสิทธิภาพ The class still lives up to its name, but not because we add it to a static list when we delete the instance. Instead, ซึ่งทําหน้าที่ในขณะที่ผู้ฟังที่นี่ถือคําอธิบายถึง . ตอนนี้วัตถุจะไม่ถูกฟื้นฟูขึ้นเพราะเครื่องรวบรวมขยะจะไม่ลบตัวอย่าง มันเป็น idiomatic มากขึ้นใน Java และไม่มี necromancy ที่เกี่ยวข้อง Runnable immortalObject Finalizers were often used when working with objects that were created via JNI somewhere outside of the Java code, for example, in C++. But the mechanism was unsafe, and the finalizers were eventually replaced with a far more elegant alternative. ภาษาไทย ไม่กี่คนจะจําได้ว่า Java เป็นครั้งที่สอดคล้องกับ JavaScript: JDK 8 มีการบูรณาการ เครื่องยนต์ JS ซึ่งช่วยให้นักพัฒนาสามารถเรียกใช้รหัส JS โดยตรงจาก Java นมโต เราสามารถประยุกต์ใช้พนันแบนนาที่ได้รับความนิยมมากโดยไม่ต้องออกจาก Java: new NashornScriptEngineFactory() .getScriptEngine() .eval("('b' + 'a' + + 'a' + 'a').toLowerCase();"); แน่นอนมันเป็นสิ่งที่ดี แต่เพียงจนกว่าเราจะตระหนักว่านักพัฒนา Java จะต้องรักษาเครื่องมือ JavaScript Nashorn ไม่สามารถสอดคล้องกับการพัฒนาอย่างรวดเร็วของ JavaScript เช่นเดียวกับโซลูชั่น polyglot ที่ปรากฏตัวเช่น GraalVM Polyglot สิ่งเหล่านี้นําไปสู่เครื่องยนต์กลายเป็นชั่วคราวหลังจากเพียงสี่ภาษารุ่น (ใน Java 11) และหลังจากนั้นก็เป็นครั้งคราว ใน Java 15 กระโดด Overboard ทําไม GraalVM Polyglot ดีขึ้น? ในขณะที่ Nashorn ได้ใช้ JavaScript ขึ้นอยู่กับ Java Polyglot สร้างแพลตฟอร์ม JVM ทั้งหมดที่สนับสนุนหลายภาษารวมถึง Java และ JavaScript ผู้จัดการความปลอดภัย พบกับไทม์เก่าอื่น ๆ ที่อยู่รอดจนถึง Java 17 - . SecurityManager ในวันของ applets เถื่อนเมื่อรหัส Java สามารถเรียกใช้ได้โดยตรงในเบราว์เซอร์ เป็นกระดูกสันหลังของการรักษาความปลอดภัยของแอปพลิเคชัน Java SecurityManager แอปเปิ้ลเป็นโปรแกรมขนาดเล็กที่ทํางานโดยตรงในเบราว์เซอร์และสามารถแสดงภาพเคลื่อนไหวกราฟิกหรือองค์ประกอบแบบโต้ตอบ แต่ทํางานภายใต้ข้อ จํากัด อย่างเคร่งครัด แอปเปิ้ลเป็นโปรแกรมขนาดเล็กที่ทํางานโดยตรงในเบราว์เซอร์และสามารถแสดงภาพเคลื่อนไหวกราฟิกหรือองค์ประกอบแบบโต้ตอบ แต่ทํางานภายใต้ข้อ จํากัด อย่างเคร่งครัด แต่เวลาผ่านไปป่า สุดท้ายย้ายจากเบราว์เซอร์ไปยังเดสก์ท็อปแล้วหายไปอย่างสมบูรณ์เป็นโซลูชั่น แต่ ยังคงอยู่และใช้กันบ่อยในแอปพลิเคชันองค์กร แอพลิเคชัน SecurityManager โซฟา แต่ไม่มีอะไรอยู่ตลอดไปและ Grim Reaper มา . Java 17 ลบภายในทั้งหมดและปล่อยให้หน้าต่างเท่านั้นโดยไม่มีเหตุผลใด ๆ หากเรามองไปที่จุดเข้าสําหรับการติดตั้ง , เราจะหาผู้ถือตําแหน่งเท่านั้น: SecurityManager SecurityManager @Deprecated(since = "17", forRemoval = true) public static void setSecurityManager(SecurityManager sm) { throw new UnsupportedOperationException( "Setting a Security Manager is not supported"); } เข้าสู่โหมดหน้าจอเต็ม Exit โหมดหน้าจอเต็ม วิธีการทํา ทํางาน? มันทําหน้าที่เป็นผู้คุ้มครอง JVM ภายในซึ่งจับการดําเนินงานที่เป็นอันตรายและตรวจสอบว่าแต่ละองค์ประกอบของสต็อกมีสิทธิ์ที่จะดําเนินการเช่นนั้นหรือไม่ SecurityManager สิ่งที่ทําอย่างแน่นอน intercept? รายการมีขนาดใหญ่ดังนั้นฉันขอแนะนําให้เราดูเฉพาะจุดหลัก: SecurityManager การเข้าถึงระบบไฟล์ การเชื่อมต่อเครือข่าย การสร้างและเสร็จสิ้นกระบวนการ การเข้าถึงคุณสมบัติของระบบ การจัดการกับชั้นเรียนและการสะท้อน ทํางานได้ดีในวันของแอปเปิ้ลเบราว์เซอร์ซึ่งจําเป็นต้องเก็บรหัสที่ไม่น่าเชื่อถือในกล่องทราย แต่ด้วยการพัฒนาของคอนเทนเนอร์มันกลายเป็นเก่าแก่ SecurityManager แม้ว่า เป็นต้นฉบับที่ถูกสร้างขึ้นสําหรับ applets มันถูกใช้ในแง่มุมอื่น ๆ เช่น Tomcat การใช้งานในแอพพลิเคชันเซิร์ฟเวอร์ ในเครื่องวิเคราะห์สถิต PVS-Studio ของเราช่วยแก้ปัญหาที่น่าตื่นเต้น: ถ้าเครื่องเริ่มต้นคลาสสถิตในรหัสที่วิเคราะห์เรียกว่า กระบวนการวิเคราะห์จะสิ้นสุดลง ช่วยให้เราสามารถบล็อกการดําเนินงานดังกล่าวเมื่อวิเคราะห์ฟิลด์สถิตและบางสิ่งบางอย่างที่เราแทนที่ด้วย ASM SecurityManager สนับสนุน System#exit SecurityManager อย่างไรก็ตามแม้ว่า applets เป็นวิธีการที่ถูกยกเลิกก่อน , พวกเขาเพิ่งเริ่มที่จะถูกลบออกจาก JDK SecurityManager โลกเปลี่ยนแปลงวิธีการเปลี่ยนแปลงและ จะหายไป SecurityManager ไม่ปลอดภัย Java ลบความไม่ปลอดภัย ปกติ อาจเป็นผู้อยู่อาศัยตํานานที่สุดของกรวยนี้ . sun.misc.Unsafe คลาสนี้เป็นทางเข้าที่ลับไปยังความลึกของ JVM ที่กฎภาษาปกติไม่สามารถใช้ได้อีกต่อไป สามารถปิดการใช้งาน Java ได้อย่างง่ายดาย . Unsafe SIGSEGV วิธีการ? ตัวอย่างเช่นเช่นนี้: 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) ใน the พื้นที่ (1) เราตั้ง (2) แทนที่จะ และลองส่งออกมูลค่าเพื่อ 3 ) value Long.MAX_VALUE Object System.out เครื่องเสมือนไม่ได้ชอบเทคนิคนี้และมันตกอยู่ในบรรทัดสุดท้ายเพราะ มีขยะแทนการอ้างอิงที่แท้จริงไปยังวัตถุ และนี่เป็นเพียงตัวอย่างหนึ่งของข้อผิดพลาดที่ง่ายต่อการค้นหา value แต่ทําไมกลไกอันตรายเช่นนี้ถูกเพิ่มไปยัง Java? เขาไม่ได้เกิดเป็นนักปฏิเสธ แต่เป็นผู้ช่วยชีวิต เมื่อหนึ่งครั้งก่อน และ (เราจะพูดคุยเกี่ยวกับพวกเขาในภายหลัง) ห้องสมุดมาตรฐานต้องดําเนินการในระดับต่ําเช่นการจัดการหน่วยความจําการดําเนินงานแบบอะตอมหรือการบล็อกลวดได้อย่างมีประสิทธิภาพ Unsafe VarHandle MemorySegment แต่วิธีการที่เราสามารถทําทั้งหมดข้างต้นถ้าภาษาห้ามคุณที่จะมองใต้ฝาครอบ? ขวาเราสามารถทําให้ช่องว่าง นี่คือที่ แสดงให้เห็นว่าเป็น เครื่องมือสําหรับความต้องการของ Java Unsafe internal ผู้พัฒนามีแนวโน้มที่จะใช้มันเฉพาะภายใน Java และแม้แต่ก็มีการป้องกันจากการเข้าถึงตัวอย่างภายนอก แต่สิ่งที่เกิดขึ้นใน Java ไม่ยังคงอยู่ใน Java และนักพัฒนาพบวิธีที่จะได้รับ นี่คือความมหัศจรรย์ที่จับ "ไม่ปลอดภัย" ในทางที่ไม่ปลอดภัย: Unsafe var Unsafe_theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); Unsafe_theUnsafe.setAccessible(true); Unsafe unsafe = (Unsafe) Unsafe_theUnsafe.get(null); วัตถุนี้ช่วยให้เราสามารถทรมานเครื่องเสมือนได้มากเท่าที่เราต้องการ แต่อย่าลังเลที่จะวิจารณ์นักพัฒนาที่กล้าหาญที่จะเข้าถึงสิ่งที่ Java ได้พยายามที่จะซ่อนพวกเขา; มันคุ้มค่าที่จะจําไว้: การเข้าถึง ช่วยให้ห้องสมุดจํานวนมากมีประสิทธิภาพมากขึ้น ตัวอย่างเช่นห้องสมุด Netty ที่เป็นที่นิยมสําหรับการทํางานกับเครือข่ายถูกบังคับให้ใช้ และเมื่อการเปลี่ยนมา Netty สองมัน Unsafe Unsafe อะไหล่ ดังนั้นชนิดของการแทนที่ที่เราพูดถึง ไม่ใช่หนึ่ง แต่สอง: API ฟังก์ชั่นและหน่วยความจําต่างประเทศแทนการดําเนินงานหน่วยความจํา replaces atomic operations. VarHandle API API เหล่านี้ไม่ได้ จํากัด เพื่อแทนที่วิธีการบางอย่างใน . พวกเขาเป็นเครื่องมือที่มีประสิทธิภาพสําหรับการทํางานได้อย่างปลอดภัยกับการดําเนินงานระดับต่ํา ตัวอย่างเช่น Foreign Function & Memory API ช่วยให้คุณสามารถเรียกฟังก์ชั่นพื้นฐานได้โดยไม่ต้องใช้ JNI ที่ยากลําบาก! Unsafe ความไม่ปลอดภัยของ Java นั้นค่อยๆล้มเหลวไปในความลืมทําให้มี API ใหม่ที่ออกแบบมาอย่างดีและมีประสิทธิภาพซึ่งทําสิ่งเดียวกันโดยไม่ต้องกลัวข้อกําหนด . SIGSEGV ทําไมฝังฝังฝังฝังฝัง Java จะเติบโตขึ้นโดยการลบความคิดที่หมดอายุกลายเป็นความปลอดภัยสะอาดและง่ายขึ้น แต่ละ API ใหม่ไม่ได้เป็นเพียง "เพิ่มโมดูลอื่น" แต่จะปิดช่องว่างเก่าในลักษณะที่ซับซ้อนมากขึ้น ในขณะเดียวกัน "มังกร" ไวรัสมีบทบาททางวัฒนธรรมที่สําคัญ: มันบันทึกการพัฒนาของความคิดและการตัดสินใจทําให้เรารู้สึกถึงเส้นทางที่เราได้เดินทางและสร้างพื้นฐานที่จําเป็นสําหรับการพัฒนาแพลตฟอร์มที่มีความหมาย