JVM ทำงานอย่างไร?
หลายคนเขียนโปแกรมด้วย Java มาสักพักก็อาจจะสงสัยว่า JVM ที่เป็น run-time engine ของ Java นั้นมีองค์ประกอบอะไรบ้างและมันทำงานอย่างไร บทความนี้จะพาคุณไปรู้จักสิ่งเหล่านี้ว่าทำงานอย่างไร?
JVM (Java Virtual Machine) เป็น run-time engine สำหรับให้ Java application ทำงาน โดยที่ JVM เป็นตัวที่เรียก main method ขึ้นมาทำงาน และเป็นส่วนหนึ่งของ JRE (Java Runtime Environment)
Java เรียกได้ว่าเป็น WORA (Write Once Run Anywhere — เขียนครั้งเดียวทำงานได้ทุกที่) ซึ่งหมายความว่าโปรแกรมเมอร์สามารถเขียนโปรแกรม Java ที่ทำงานบนระบบหนึ่งและสามารถทำงานได้บนระบบอื่นๆ โดยไม่ต้องปรับเปลี่ยนโปรแกรมได้ ก็เพราะว่า JVM นี่เอง
เมื่อเราคอมไพล์ไฟล์ .java คอมไพล์เลอร์ (Java compiler) จะสร้างไฟล์ .class ที่ชื่อเหมือนกับไฟล์ .java ขึ้นมา ซึ่งเป็นไฟล์ที่บรรจุ bytecode ของโปรแกรม ซึ่งการที่เราจะรันไฟล์ .class จะมีหลายขั้นตอนที่ดำเนินการด้วย JVM
JVM แบ่งออกเป็น 3 ส่วนดังนี้
- Class Loader Subsystem
- Runtime Data Area (JVM Memory)
- Execution Engine
Class Loader Subsystem
ฟังก์ชันการทำงาน dynamic class loading ของ Java จัดการด้วยส่วน Class Loader Subsystem โดยที่จะทำขั้นตอน Load, Link และ Initialize คลาสไฟล์ เมื่อมีการอ้างถึงคลาสครั้งแรกในขณะที่ทำงาน ไม่ใช้ตอนคอมไพล์ ดังนี้
Loading
Class Loader จะอ่านไฟล์ .class แล้วสร้างข้อมูลไบนารีและบันทึกไว้ใน method area หลังจากโหลดไฟล์ .class แล้ว JVM จะสร้าง object ของคลาสเป็นตัวแทนของไฟล์ใน heap memory
Link
หลังจากที่ได้ object ของคลาสใน heap memory แล้วขั้นตอนต่อไปคือเชื่อมโยงเข้าด้วยกัน โดยจะทำการ Verification, Preparation และ Resolution
- Verification — ตรวจสอบความถูกต้องของคลาสที่โหลดเข้ามา
- Preparation — จัดสรรหน่วยความจำสำหรับตัวแปลของคลาสและตั้งค่าเริ่มต้น
- Resolution — แปลงการอ้างอิง symbolic ไปเป็นการอ้างอิงโดยตรง
Initialize
เป็นขั้นตอนสุดท้ายของ Class Loading ซึ่งตัวแปล static ทั้งหมดจะถูกกำหนดค่าด้วยค่าที่กำหนดในโปรแกรม จากบนลงล่างและจากคลาสแม่ไปยังคลาสลูก
โดยทั่วไปจะมี 3 Class Loader คือ
- Bootstrap Class Loader — ทำหน้าที่โหลดคลาสจาก bootstrap class path ซึ่งเป็น core api ของ Java ใน JAVA_HOME/jre/lib
- Extension Class Loader — เป็นลูกของ bootstrap class loader ทำหน้าที่โหลดคลาส extension ที่อยู่ใน JAVA_HOME/jre/lib/ext
- Application Class Loader — เป็นลูกของ extension class loader ทำหน้าที่โหลดคลาส application class path
Runtime Data Area
แบ่งออกเป็น 5 ส่วนคือ
Method Area
ข้อมูลระดับคลาสจะถูกเก็บไว้ในส่วนนี้ รวมทั้งตัวแปล static โดยที่เป็นส่วนที่มีเพียงหนึ่งเดียวใน JVM และเป็นส่วนที่แชร์กันของ JVM
Heap Area
Object ทั้งหมดและตัวแปล instance และ array ที่เกี่ยวข้องของ Object จะถูกเก็บไว้ในส่วนนี้ ซึ่งมีส่วนที่มีเพียงหนึ่งเดียวใน JVM และเป็นส่วนที่แชร์กันของ JVM
Stack Area
สำหรับทุกๆ thread, JVM จะสร้าง run-time stack ให้แต่ละ thread และสำหรับทุกๆ method call จะเป็น entry ภายใน stack memory ซึ่งเรียกว่า Stack Frame ตัวแปรทั้งหมดจะถูกสร้างใน stack memory หลังจากที่ thread หยุดทำงาน run-time stack ก็จะถูกทำลาย ซึ่ง Stack Frame แบ่งออกได้ดังนี้
- Local Variable Area — เกี่ยวข้องกับ method ว่ามีจำนวน local variable เท่าไหร่จะถูกเก็บไว้ที่นี่
- Operand Stack —ทำงานเหมือนกับ runtime workspace ที่จะทำ operation
- Frame Data — symbol ทั้งหมดที่เกี่ยวกับ method จะเก็บไว้ที่นี่ ในกรณีของ exception ข้อมูลของ catch block จะ maintain ใน data frame
PC Registers
แต่ละ thread จะมี PC Register ที่แยกจากกัน ซึ่งจะเก็บ address ของคำสั่งที่ประมวลผลอยู่ ถ้าคำสั่งถูกประมวลผล PC register จะถูกเปลี่ยนแปลงเป็นคำสั่งต่อไป
Native Method Stack
สำหรับทุกๆ thread จะสร้าง native stack แยกจากกัน ซึ่งจะเก็บข้อมูลของ native method ของ thread
Execution Engine
Bytecode ที่อยู่ใน Runtime Data Area จะถูกประมวลผลด้วย Execution Engine นี้โดยที่จะอ่าน bytecode และประมวลผลที่ละบรรทัด ประกอบด้วย 3 ส่วน:
Interpreter
เป็นส่วนที่จะแปล byecode และประมวลผลทีละบรรทัด ข้อเสียคือถ้ามีการเรียกใช้งาน method เดิมหลายๆ ครั้ง ทุกๆ ครั้งก็จะต้องถูกแปลด้วย Intepreter
JIT Compiler
ใช้ในการเพิ่มประสิทธิภาพของ Intepreter ซึ่งจะคอมไพล์ทั้ง bytecode และเปลี่ยนเป็น native code ดังนั้นเมื่อใดก็ตามที่ Intepreter เห็น method ที่ซ้ำกัน JIT จะให้ native method โดยตรงสำหรับส่วนนั้น ดังนั้นจะไม่จำเป็นต้องแปลใหม่ทุกครั้งเมื่อเรียกใช้งาน method เดิม ทำให้มีประสิทธิภาพมากขึ้น มีส่วนต่างๆ ดังนี้
- Intermediate Code Generator — สร้าง intermediate code
- Code Optimizer — ปรับปรุงประสิทธิภาพ intermediate code
- Target Code Generator — สร้าง machine code หรือ native code
- Profiler — เป็นส่วนพิเศษที่ทำการค้นหา hotspot
Garbage Collection
จะเป็นส่วนที่เก็บรวบรวมและลบ object ที่ไม่ได้ถูกอ้างอิงในหน่วยความจำ ซึ่งสามารถเรียกใช้งานโดยตรงได้จาก System.gc()
Garbage Collection ของ JVM จะรวบรวม object ที่ถูกสร้างขั้น
Java Native Interface
เป็นส่วนที่เชื่อมโยงกับ Native Method Library และให้ Native Library (C, C++) ที่จำเป็นต่อ Execution Engine ทำให้ JVM สามารถเรียกใช้งาน C/C++ library ได้
Native Method Library
เป็นที่รวบรวม Native Library ซึ่งจำเป็นต่อ Execution Engine