เกร็ดกายวิภาคของ JVM
(shipilev.net)- ซีรีส์มินิโพสต์แบบต่อเนื่องที่แยกความรู้ภายใน JVM ออกเป็น หน่วยเล็กๆ เพื่ออธิบาย โดยแต่ละบทความจะโฟกัสที่หัวข้อ การทดสอบ เบนช์มาร์ก หรือข้อสังเกตเพียงอย่างเดียว
- แต่ละโพสต์ตั้งเป้าให้อ่านจบได้ภายใน 5–10 นาที และตั้งอยู่บนสมมติฐานว่าองค์ประกอบต่างๆ ของ JVM มีปฏิสัมพันธ์ต่อกัน
- หลักฐานและการอภิปรายอาจมีลักษณะเป็น ประสบการณ์เฉพาะกรณี และอาจยังไม่ได้รับการตรวจทานเรื่องข้อผิดพลาด ความสม่ำเสมอ สำนวน ไวยากรณ์ และความซ้ำซ้อนอย่างเพียงพอ จึงควรใช้ความระมัดระวังหากจะเชื่อถือโดยตรง
- ชุดรวมทั้งหมดมีให้ในรูปแบบ ePUB, MOBI, PDF โดย PDF มีขนาดระดับหลายสิบ MB เนื่องจากเป็นการแปลงคุณภาพสูง
- ดัชนีบทความรายชิ้นแบ่งเป็นหมวด Compiler, Runtime, GC และ Library ครอบคลุมหัวข้อภายใน JVM เช่น การเพิ่มประสิทธิภาพล็อก, TLAB, การหยุดทำงานของ GC,
String.intern(), safepoint, compressed references และ conditional moves
ข้อสมมติเมื่ออ่านซีรีส์นี้
- JVM Anatomy Quarks เป็นซีรีส์มินิโพสต์แบบต่อเนื่องที่สรุปความรู้พื้นฐานของ JVM ในรูปแบบบทความสั้น
- แต่ละโพสต์เป็นงานเขียนที่เจาะลึกไปยัง หัวข้อ การทดสอบ เบนช์มาร์ก หรือข้อสังเกตเพียงหนึ่งอย่าง
- หากอ่านเฉพาะโพสต์เดี่ยวๆ อาจขาดบริบท และองค์ประกอบส่วนใหญ่ที่กล่าวถึงมัก มีปฏิสัมพันธ์ ต่อกันได้ง่าย
- หลักฐานและการอภิปรายในบทความอาจเป็นเพียงประสบการณ์เฉพาะกรณี และอาจยังไม่ได้ผ่านการตรวจทานเรื่องข้อผิดพลาด ความสม่ำเสมอ สำนวน ไวยากรณ์ ความหมาย และความซ้ำซ้อนอย่างเพียงพอ
- หากจะนำเนื้อหาไปใช้หรือเชื่อถือ ผู้อ่านต้องยอมรับความเสี่ยงด้วยตนเอง
ไฟล์ชุดรวมและโครงสร้างดัชนี
- ชุดรวมของซีรีส์ทั้งหมดมีให้ในสามรูปแบบ
- ePUB มีขนาดเล็กที่สุด ไม่ถึง 1 MB และสร้างบนพื้นฐาน Pandoc HTML-to-ePUB
- MOBI มีขนาดเล็ก ระดับ MB และสร้างบนพื้นฐาน KindleGen ePUB-to-MOBI
- PDF มีขนาดใหญ่มาก ระดับหลายสิบ MB และเป็นผลลัพธ์คุณภาพสูงจาก wkhtmltopdf HTML-to-PDF
- ดัชนีรายชิ้นประกอบด้วยหมวด Compiler, Runtime, GC, Library
รายการบทความตามหัวข้อ
-
หมวดที่เน้น Compiler
#1: Lock Coarsening and Loops#14: Constant Variables#15: Just-In-Time Constants#16: Megamorphic Virtual Calls#17: Trust Non-Static Final Fields#18: Scalar Replacement#19: Lock Elision#20: FPU Spills#25: Implicit Null Checks#27: Compiler Blackholes#28: Frequency-Based Code Layout#29: Uncommon Traps#30: Conditional Moves
-
หมวดที่เกี่ยวข้องทั้ง Runtime และ GC
- เป็นบทความที่ว่าด้วย หน่วยความจำและพฤติกรรมการหยุดชั่วคราว ระหว่างการทำงานของ JVM
#2: Transparent Huge Pages#4: TLAB Allocation#5: TLABs and Heap Parsability#6: New Object Stages#7: Object Initialization Costs#9: JNI Critical and GC Locker#22: Safepoint Polls
-
หมวดที่เน้น GC
- เน้นเรื่องการออกแบบตัวเก็บกวาดและพฤติกรรมของฮีป
#3: GC Design and Pauses#11: Moving GC and Locality#13: Intergenerational Barriers#21: Heap Uncommit
-
หมวดที่เน้น Runtime
- เป็นบทความที่ว่าด้วยสภาพแวดล้อมการทำงานของ JVM และการแทนวัตถุ
#12: Native Memory Tracking#23: Compressed References#24: Object Alignment#26: Identity Hash Code
-
หมวด Library หรือหมวดผสม
- หมวดที่รวม Library มี
#10: String.intern() - หมวดที่ระบุทั้ง Compiler และ Runtime ได้แก่
#16,#25,#29,#30
- หมวดที่รวม Library มี
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
https://shipilev.net/jvm/anatomy-quarks/17-trust-nonstatic-f... เป็นกรณีที่น่าเสียดายจริง ๆ
เพราะบางเฟรมเวิร์กใช้ JNI และ reflection ในทางที่เกินควรเพื่อเปลี่ยนฟิลด์
finalที่เดิมควรจะไม่เปลี่ยนแปลงได้ ทำให้โค้ดของผู้ใช้พลาด การปรับแต่งประสิทธิภาพที่สำคัญ ซึ่งทำได้เฉพาะกับคลาสที่ระบบจัดเตรียมให้เท่านั้นแพลตฟอร์ม โดยเฉพาะคอมไพเลอร์และรันไทม์ ควรบังคับใช้ ข้อจำกัดเชิงความหมาย อย่างเข้มงวดมาก เพื่อรักษาพื้นที่สำหรับการปรับแต่งประสิทธิภาพในอนาคต
โค้ดที่จำเป็นต้องเปลี่ยน
finalจริง ๆ มีไม่มาก และตอนนี้งานนั้นก็ถูกจำกัดไว้กับคลาสภายในโมดูลของตัวเอง หรือคลาสที่ถูกopenอย่างชัดเจนอยู่แล้ว ดังนั้นในอนาคตจะเป็นรูปแบบที่แอปพลิเคชันต้องให้สิทธิ์แก่โมดูลที่พยายามเปลี่ยนfinalคล้ายกับแนวทางที่เพิ่งนำไปใช้กับการเรียก native และการเข้าถึงหน่วยความจำแบบ unsafe
[1]: https://openjdk.org/jeps/8305968
finalให้ทุกอย่าง หรือเปล่าผลคือทุกวันนี้เราไม่สามารถ mock คลาส
finalในการทดสอบได้ง่าย ๆ และเครื่องมือ mock ถึงขั้นต้อง ดัดแปลง bytecode เพื่อ mock คลาสfinalตัวอย่างเช่น ภายใน Google นั้น Effective Java เป็นข้อกำหนด จึงมีคลาส
finalแม้แต่ใน GDrive API สาธารณะ ทั้งที่ API ภายนอกนี่แหละคือสิ่งที่อยาก mock มากที่สุดSystem.outเป็นปัญหาของ Java เองhttps://docs.oracle.com/javase/specs/jls/se7/html/jls-17.htm...
เมื่อมีบรรทัดฐานแบบนี้ ก็ไม่น่าแปลกใจที่คนอื่นจะคิดว่านี่เป็นวิธีที่ยอมรับได้
หากผ่านขั้นตอนการเลี่ยงที่เหมาะสม ก็ควรจะเลี่ยงได้
ประเด็นสำคัญคือความปลอดภัย เพราะการเปลี่ยนสิ่งที่ไม่ควรแก้ไขได้อาจทำให้เกิด SEGV และนั่นคือข้อกังวลที่ access modifier พยายามจัดการ
finalอีกครั้ง เพื่อหลีกเลี่ยงการรีแฟกเตอร์ที่น่าปวดหัวถ้าถูกห้ามไว้ก็คงดี แต่ตอนนั้นมีความต้องการทางธุรกิจ
ออกนอกเรื่องเล็กน้อย แต่ Apple เพิ่งปล่อย Swift Java bridge ตัวใหม่ และมันเจ๋งทีเดียว
รองรับทั้ง JNI และ Panama และเมื่อสัปดาห์ก่อนก็กำลังพอร์ตมันไปยัง Android อยู่
https://github.com/swiftlang/swift-java
ด้วย interoperability ระหว่างภาษา ทำให้สามารถแชร์ไลบรารีเดียวกันบนหลายแพลตฟอร์มได้ในกรณีที่เหมาะสม
ที่ผ่านมาทำกันด้วย C++ เท่านั้น และถ้าจำเป็นก็เสริม C API เข้าไป แต่ในสถานการณ์ที่ไม่ได้จำเป็นต้องใช้ C++ โดยตัวมันเอง แน่นอนว่ามันไม่ใช่ภาษาที่อยากเลือกใช้
อย่างไรก็ตาม กังวลเรื่องต้นทุนอยู่ ถ้า Swift ในแอปมือถือเรียกไลบรารี Kotlin ได้ง่าย ๆ ก็รู้สึกว่าแอป iOS คงต้องโหลดอะไรสักอย่างที่คล้าย JVM ในรูปแบบหนึ่งหรือไม่ และในทางกลับกัน ถ้าแอป Android เรียก Swift ก็น่าจะต้องโหลด Swift runtime
สุดท้ายก็จะมี overhead เกิดขึ้น
กังวลว่าสักวันหนึ่งนักพัฒนาจะพึ่งพาไลบรารี Swift แล้วไลบรารีนั้นพึ่งพาไลบรารี Kotlin จนต้องเปิด JVM จากนั้นก็เรียก C++ ผ่าน JNI อีกที จนกลายเป็นเรื่องปกติ
มันคล้ายกับสถานการณ์ของ package manager สมัยใหม่ที่ถึงแม้ dependency โดยตรงจะมีแค่ไม่กี่ตัว แต่เพราะ “มันง่ายเกินไปจนไม่ใส่ใจ” โปรแกรมจึงลงเอยด้วย transitive dependencies มากกว่า 100 รายการ ได้ในพริบตา
ดีใจที่ชุดบทความดี ๆ นี้ถูกนำมาแชร์ที่นี่ ได้เรียนรู้เกี่ยวกับ JVM เยอะมากจากซีรีส์นี้
ชอบบทความนี้เป็นพิเศษ ซึ่งอธิบายว่าคำที่มักเรียกกันใน Java ว่า “stack allocation” นั้นไม่ถูกต้อง: https://shipilev.net/jvm/anatomy-quarks/18-scalar-replacemen...
สิ่งที่ JVM ทำจริง ๆ คือ escape analysis + scalar replacement
ชอบ ความยาว ของบทความเหล่านี้
สามารถอ่านจบทีละเรื่องได้ภายในไม่กี่นาที และถ้าต้องการก็ลองรัน benchmark ในเครื่องตัวเองได้ด้วย ดีมาก
ถ้าเคยทำงานกับภาษา JVM-based มาหลายปี ชุดบทความนี้น่าสนใจมากจริง ๆ
ยังจำได้ว่าตอนอ่านไล่ไปครั้งแรกเมื่อหลายปีก่อนเป็นอย่างไร
มีใครรู้ไหมว่าทำไมชื่อซีรีส์นี้ถึงเปลี่ยนจาก ‘JVM Anatomy Park’?
ผมแทบลืม Java ไปแล้ว
ไม่ได้มีความคิดเลยว่าจะเริ่มโปรเจกต์ใหม่ด้วย Java
ถ้าต้องการการพัฒนาที่รวดเร็วและความยืดหยุ่น ผมน่าจะเลือก Python; ถ้าอยากจัดการ I/O concurrency จำนวนมากด้วยแนวทางที่มี garbage collection ก็เลือก Go; ถ้าต้องการภาษาที่คอมไพล์และสมดุลดี ก็เลือก Swift; ถ้าต้องการภาษาแบบคอมไพล์ที่มีประสิทธิภาพและความปลอดภัย ก็เลือก Rust
เป็นแค่รสนิยมส่วนตัว และรู้ว่า Kotlin ทำให้ Java ใช้ง่ายขึ้น แต่ก็ยังรู้สึกแบบนั้นอยู่ดี
จุดแข็งหลักของ Java คือมีนักพัฒนาที่มีประสบการณ์ Java มากแทบไม่จำกัด มีไลบรารีเดิมจำนวนมหาศาลและจำนวนมากก็เน้นฝั่ง enterprise จัดการ codebase ขนาดใหญ่มากที่มีผู้ร่วมพัฒนาจำนวนมากได้ง่าย VM มาตรฐาน ที่พัฒนามาหลายสิบปีมีความแข็งแกร่งมากและค่อนข้างเร็ว อีกทั้งรองรับแทบทุกแพลตฟอร์ม
แม้จะไม่ได้ครองตลาดอย่างท่วมท้นเหมือนต้นทศวรรษ 2000 แม้แต่ใน enterprise และก็เป็นภาษา “blub” แบบคลาสสิก แต่ถ้าคาดหวัง ขนาดระดับ enterprise และการขยายทีมด้วยนักพัฒนาจำนวนมากสำคัญกว่าประสิทธิภาพล้วน ๆ ก็ยังเป็นตัวเลือกที่สมเหตุสมผลมาก
ผมชอบ Rust แต่ Java เป็นตัวเลี้ยงปากท้องผม
ด้วยเฟรมเวิร์กสมัยใหม่และตัวช่วย AI สามารถตั้ง backend ที่ใช้ได้ดีภายในไม่กี่วัน
ถ้าเป็น co-founder ฝั่งเทคนิคคนเดียว แค่รู้ Java หรือ Kotlin และสแต็ก frontend ก็พอสำหรับทำ MVP ให้เร็ว และในความเป็นจริงจะใช้เวลากับงานนอกเหนือจากการเขียนโค้ดมากกว่ามาก ทำให้ความแตกต่างของฟีเจอร์ภาษาไม่สำคัญเท่าไร
ถ้าไปทาง mobile native, Swift อาจเป็นภาษาที่สองได้
อีกอย่าง ช่วงหนึ่ง scalability อาจยังไม่ใช่ปัญหาแรก ทีมจะต้องขยายก่อน และ performance bottleneck มักจะเห็นชัดทีหลังมาก
Java เหมาะกับทีมใหญ่
จากมุมมองธุรกิจ ถ้าต้องการ talent pool ที่ใหญ่กว่า รอบการส่งมอบที่รวดเร็ว และสิ่งที่อยู่เป็นสแต็กหลักได้ในระยะยาว Java หรือ Kotlin น่าจะเป็นตัวเลือกที่ดีที่สุด
หากต้องการชูเทคโนโลยีเท่ ๆ เป็นเหมือนสวัสดิการเพื่อดึงดูดนักพัฒนาบางกลุ่ม หรือมีกรณีธุรกิจที่พบไม่บ่อย ก็อาจเลือก Go หรือ Rust
Python เป็นที่นิยมในแวดวงวิชาการและ bootcamp แต่พูดตรง ๆ ผมไม่ค่อยแน่ใจเรื่องคุณค่าทางธุรกิจของมันสำหรับ backend ทั่วไป
ผมคงไม่มองข้าม JVM ทั้งหมดหรอก JVM เป็น ผลงานวิศวกรรมชั้นเยี่ยม และทุกวันนี้ก็กำลังพัฒนาเร็ว ดู Loom, Panama, Leyden เป็นต้น
ดังนั้นสำหรับแทบทุกอย่าง Java จึงเป็นตัวเลือกที่ดีค่อนข้างชัดเจน
Kotlin ที่ใช้ร่วมกับ Java 21+ เป็นชุดที่ผมเลือกก่อนสำหรับบริการที่เน้น I/O หรือแทบจะบริการใด ๆ ก็ตาม
มันใช้ง่ายจริง ๆ และด้วย virtual threads ก็ทำให้เขียนโค้ดได้เรียบง่ายและมีประสิทธิภาพเท่า Go ขณะเดียวกันก็ใช้ประโยชน์จาก ecosystem ไลบรารีที่ใหญ่และดีมากระดับโลกได้
ไม่ได้จะด้อยค่า Go หรือ Python ถ้ามันเป็นเครื่องมือที่คุณชอบ ก็ถือว่าใช้ได้ดีพอ
เพียงแต่ Java ไม่ได้หมดความสำคัญไปอย่างที่คุณคิด