JEP 491 แก้ปัญหา pinning ที่คนพูดถึงกันหมดแล้ว และเหลือบานที่ไม่มีใครพูดถึง
virtual thread ถูกออกแบบมาให้ถอนตัวออกจาก carrier thread เวลามันบล็อก · carrier ตัวนั้นจะได้ไปทำงานอื่นต่อ
มีข้อยกเว้นข้อหนึ่งที่ดังมากตอน virtual thread ออกใหม่ ๆ · ถ้ามันบล็อกขณะถือ
monitor ของ synchronized มันถอนตัวไม่ได้ และ carrier ถูกตรึงไปด้วย
JDK 24 แก้ข้อนั้นแล้ว · หน้านี้ยืนยันว่าแก้จริง แล้วชี้บานที่ยังเหลือ
ตัววัดปล่อย virtual thread 8 ตัว แล้วนับ event jdk.VirtualThreadPinned
ที่ JFR อัดไว้ · หนึ่ง event ต่อการตรึงหนึ่งครั้ง
| JDK | บล็อกเฉย ๆ | บล็อกขณะถือ monitor | บล็อกใน static initializer |
|---|---|---|---|
| 21 | 0 | 8 | 1 |
| 25 | 0 | 0 | 1 |
| 26 | 0 | 0 | 1 |
column กลางคือเรื่องที่ทุกคนรู้ · JDK 21 ตรึงครบทุกเธรด ที่ปล่อยไป และตั้งแต่ 25 เป็นต้นไปเหลือศูนย์
column ขวาคือเรื่องที่ยังไม่จบ
บานที่ JEP 491 ไม่ได้แตะ
หัวข้อที่มีชื่อว่า “บานที่ JEP 491 ไม่ได้แตะ”virtual thread ที่ไปกระตุ้นให้ class หนึ่งถูก initialize แล้ว code ใน static initializer ของ class นั้นบล็อก · carrier ถูกตรึงอยู่ดี
| JDK | synchronized | static initializer |
|---|---|---|
| 21 | ตรึง | ตรึง |
| 25 | ไม่ตรึง | ตรึง |
| 26 | ไม่ตรึง | ตรึง |
เรื่องนี้ไม่ได้หายาก · class ที่อ่าน config ตอน initialize, ที่เปิด connection pool ไว้ใน static field, หรือที่โหลด library ตอนแรกสุด · ทุกอันเข้าข่าย
และมันเกิดครั้งเดียวต่อ class ตอนที่มีคนแตะมันเป็นคนแรก · ซึ่งแปลว่ามันจะโผล่ตอน ระบบเพิ่งสตาร์ต ปนกับความช้าอื่น ๆ ที่คนคาดหวังอยู่แล้ว
สิ่งที่ JEP 491 แก้คือ การบล็อกขณะถือ monitor ซึ่งเป็นบานที่พบบ่อยที่สุด และเป็นบานที่ทำให้ virtual thread ใช้กับ library เก่าไม่ได้
สิ่งที่มันไม่ได้แก้คือการตรึงจากเฟรมที่ JVM ถอนออกไม่ได้ · static initializer เป็นหนึ่งในนั้น และ native frame ก็เป็นอีกหนึ่ง
ถ้าคุณอัป JDK แล้วยังเห็น jdk.VirtualThreadPinned ในรายงาน · นั่นไม่ใช่ความผิดพลาด
ของการวัด มันคือบานที่เหลือ
หน้านี้เคยวัดผิดหน่วย และนี่คือเหตุผลที่เปลี่ยน
หัวข้อที่มีชื่อว่า “หน้านี้เคยวัดผิดหน่วย และนี่คือเหตุผลที่เปลี่ยน”ร่างแรกของตัววัดไม่ได้นับ event · มันปล่อย virtual thread หลายตัวโดยตั้ง scheduler ให้มี carrier ตัวเดียว แล้วนับว่ากี่ตัวเดินหน้าได้ภายในหน้าต่างเวลาหนึ่ง
ได้ผลที่ถูก คือ 1 จาก 4 บน JDK 21 และ 4 จาก 4 บนตัวใหม่กว่า
แต่ หน่วยนั้นคือเวลาที่ปลอมตัวมาเป็นจำนวนนับ · ตัวเลข 1 กับ 4 มาจาก “กี่ตัวทันภายในสามวินาที” ซึ่งขึ้นกับว่าเครื่องที่รันโหลดหนักแค่ไหน
การนับ event ไม่มีหน้าต่างเวลาเข้ามาเกี่ยวเลย · โปรแกรมรันจนจบ แล้วอ่านว่า JVM บันทึกการตรึงไว้กี่ครั้ง
ตอนตั้งตัววัด ผู้เขียนลองส่ง jdk.VirtualThreadPinned#enabled=false เข้าไปดู
· jfr summary ยังพิมพ์แถวของ event นั้นออกมาพร้อมเลข 0 เหมือนเดิมทุกตัวอักษร
แปลว่าการเช็กว่า “แถวมีอยู่” ไม่ใช่ช่องควบคุม และศูนย์ของ JDK 25 จะแยกไม่ออกจาก “JFR ถูกปิดอยู่”
ช่องควบคุมที่ใช้ได้จริงคือ column ขวา · มันต้องได้มากกว่าศูนย์บนทุก version ถ้ามันได้ศูนย์เมื่อไร ด่านแดงแล้วบอกว่า การวัดพัง ไม่ใช่ว่าไม่มีการตรึง · อีกช่องคือ column ซ้าย ซึ่งเป็น code เดียวกันแต่ไม่ถือ monitor และต้องได้ศูนย์เสมอ
เอาไปใช้ยังไง
หัวข้อที่มีชื่อว่า “เอาไปใช้ยังไง”ถ้าคุณอยู่บน JDK 25 ขึ้นไป เลิกไล่ถอด synchronized
ออกเพื่อเปลี่ยนไปใช้ ReentrantLock ได้แล้ว งานนั้นจบไปกับ JEP 491
เปิด event jdk.VirtualThreadPinned ไว้ในโปรไฟล์การอัดของ production
· ค่าปริยายของ default.jfc ตั้ง threshold ไว้ที่ 20 มิลลิวินาที ซึ่งกรอง
การตรึงสั้น ๆ ทิ้งหมด · ตัววัดของหน้านี้ตั้งเป็นศูนย์ เพราะมันนับจำนวนครั้ง ไม่ใช่วัดความนาน
เวลาเห็น event โผล่ ให้ดู stack trace ของมันก่อนสรุป · ถ้าปลายทางคือ <clinit>
ปัญหาอยู่ที่ static initializer ที่ทำงานหนักเกินไป ไม่ใช่ที่ virtual thread
สิ่งที่หน้านี้ไม่ได้วัด
หัวข้อที่มีชื่อว่า “สิ่งที่หน้านี้ไม่ได้วัด”- ไม่ได้วัดเวลา หน่วยของหน้านี้คือจำนวน event ที่ JVM บันทึก
- ไม่ได้วัดการตรึงจาก native frame ซึ่งเป็นอีกบานที่ JEP 491 ไม่ได้แก้ · หน้านี้พูดถึงมันแต่ไม่ได้เอาเลขมาแสดง
- ไม่ได้วัดผลกระทบต่อ throughput การตรึงหนึ่งครั้งจะเจ็บแค่ไหน ขึ้นกับว่ามี carrier กี่ตัวและงานอื่นรออยู่เท่าไร · หน้านี้ตอบแค่ว่ามันเกิดหรือไม่เกิด
- วัดบน JDK 21, 25, 26 เท่านั้น และทั้งหมดเป็น build ของ OpenJDK
ตัววัดอยู่ที่ scripts/vthread-pinning/ รันด้วย npm run verify:vthread-pin
· มันอยู่ในเกตของ CI จึงมีคนรันทุกครั้งที่มีคน push
ด่านบังคับว่าต้องวัดคร่อมเส้นแบ่งของ JEP 491 · ถ้าเครื่องไหนมี JDK ฝั่งเดียว ด่านแดงและบอกว่าเทียบสองฝั่งไม่ได้ ไม่ใช่รายงานผลครึ่งเดียวแล้วบอกว่าผ่าน