JAVA luôn tự hào về sologan "Viết một lần chạy mọi nơi", để có được điều đó là do JVM, một bộ máy ảo của JAVA. Vậy thực sự như thế nào? Hầu hết chúng ta đều biết byecode được thực thi bởi JRE (môi trường thực thi JAVA) nhưng thực tế JRE là một implementation của JVM, trong bài viết này chúng ta sẽ cùng tham khảo sâu về cách thực thi của JVM, rất mong nhận được sự góp ý của các bạn, anh chị em đồng nghiệp.
Khái niệm
Mở rộng từ khái niệm máy ảo thì máy ảo nó là một triển khai của máy vật lý. Trình biên dịch thực hiện biên dịch các file JAVA thành file JAVA.class, và JVM là một máy ảo chuyên nhận và thực thi các tệp đó. Vì vậy khi chúng ta code, thật ra là code cho JVM hiểu chứ không phải là code cho máy chủ vật lý hiểu, bởi vì vậy với bất kỳ một hệ điều hành nào chúng ta chỉ cần cài đặt JRE tức là sẽ có JVM đều có thể chạy được.
Sơ đồ xử lý
JVM có 3 step chính xử lý
- Class Loader
- Data Runtime Area
- Execution Engine
Class Loader
Classloader - chịu trách nhiệm tải, liên kết, khởi tạo các file ở lần đầu của Runtime.
- Loading : Là bước khởi tạo và tải các file vào máy ảo JVM
- Linking: Xác minh mã bytecode hợp lệ hay không? Sau đó gán các giá trị mặc định cho biến tĩnh từ file đã tải ở bước loading,
- Initialization: Đây là bước cuối cùng của class loader, tất cả các biến tĩnh sẽ được gán giá trị ban đầu, và khối tĩnh sẽ được thực thi.
Runtime Data Area
Đây là món chính chiếm phần lớn nhất của kiến trúc JVM. Chính là phần bộ nhớ mà JVM sử dụng được hệ điều hành phân cho.
Có 5 vùng chính Runtime Data:
- Method Area: Tất cả các dữ liệu cấp độ Class-Level, bao gồm các biến tĩnh được lưu trữ ở đây.
Và một phân vùng riêng Runtime constant pool nằm trong method area: Gồm các numeric constant, field reference, method reference, và các attribute. - Heap Area: Lưu trữ dữ liệu của các biến, Object, mảng tương ứng, String Pool...
Garbage Collection là trình quản lý bộ nhớ của Heap, khi không còn một User Thread nào sử dụng tham chiếu đến Object hoặc Array,..nữa nó sẽ chịu trách nhiệm dọn dẹp bộ nhớ.
Mặc định kích thước lớn nhất của heap là 64 Mb, chúng ta có thể cấu hình thêm - Native method stack: Chứa byecode của các ngôn ngữ khác không phải JAVA, đã được JNI (java native interface) tham thiếu qua lib native để bytecode và máy có thể hiểu để thực thi được.
- PC Reigster: PC (Program Counter) register được tạo ra khi khởi tạo một thread và cho riêng thread ấy.
- JVM Stack: Cũng giống PC Reigster, được tạo ra khi khởi tạo một thread và cho riêng thread ấy. JVM Stack là ngăn xếp để JVM lưu trữ Stack Frame.
Stack Frame được khởi tạo mỗi khi có 1 có một method được gọi, stack frame chính là mang thông tin của method đó, nó bao gồm biến cục bộ, mảng cục bộ...
Execution Engine
Nó gán byteCode cho vùng dữ liệu Runtime, đọc byteCode và thực thi từng phần một.
- Interpreter – Interpreter thông dịch byteCode nhanh, nhưng thực thi chậm. Nhược điểm của trình thông dịch là khi một phương thức được gọi nhiều lần, mỗi lần lại cần một thông dịch mới. Trình thông dịch sẽ trực tiếp thực thi mã byte mà không chuyển nó thành mã máy
- JIT – Jit là trung gian của Trình thông dịch và Trình biên dịch. Trong khi Runtime nó chuyển byteCode thành mã máy (JVM hoặc Máy vật lý) Lần sau, nó lấy từ bộ đệm và chạy, hạn chế được việc thực thi chậm của Interpreter.
- Garbage Collection: Thu thập và loại bỏ các đối tượng không còn được tham chiếu bởi các User Thread
- JNI: tương tác với các Native Method Lib và cung cấp các Thư viện gốc cần thiết.