Tất cả các chương trình khi muốn thực thi được thì phải bắt buộc phải được biên dịch ra code máy. Code máy từng loại kiến trúc CPU của mỗi máy tính là hoàn toàn khác nhau (tập lệnh code máy của CPU Intel, CPU Solarix, CPU Macintosh … là không giống nhau). Vì vậy, đối với một chương trình sau khi đã được biên dịch xong thì chỉ có thể chạy được trên một kiến trúc CPU cụ thể. Đối với các CPU Intel chúng ta có thể chạy các hệ điều hành như Microsoft Windows, Unix, Linux, OS/2, …
Chương trình chạy được trên Windows thường được biên dịch dưới dạng file có đuôi .EXE, còn khi chạy trên Linux thì sẽ được biên dịch dưới dạng file có đuôi .ELF. Trước đây một chương trình chạy được trên Windows muốn chạy được trên hệ điều hành khác như Linux chẳng hạn thì phải chỉnh sửa và biên dịch lại đó là một khó khăn vô cùng lớn dành cho các nhà lập trình viên.
Từ đó ngôn ngữ lập trình Java ra đời, nhờ vào hệ máy ảo Java mà khó khăn đã nêu ra ở trên đã được khắc phục một cách dễ dàng. Với một chương trình được viết bằng ngôn ngữ Java sẽ được biên dịch ra code của máy ảo java (code java bytecode). Khi ấy máy ảo Java sẽ chịu trách nhiệm chuyển code java bytecode thành code máy tương ứng. Sun Microsystem sẽ chịu trách nhiệm về việc phát triển các máy ảo Java có thể chạy trên các hệ điều hành với các kiến trúc CPU khác nhau.
Vậy Java Virtual Machine là gì?
Java Virtual Machine (Viết tắt là JVM) là môi trường dùng để chạy ứng dụng được viết bằng ngôn ngữ lập trình Java.
Nhờ có JVM mà Java có thể chạy trên nhiều Platform khác nhau. JVM giống như một cái máy ảo, muốn khởi chạy Java thì bắt buộc phải chạy trên cái máy ảo này. Cứ với mỗi Platform ta sẽ có một JVM tương ứng, ví dụ như Ubuntu thì sẽ có bản JVM cho Ubuntu, Windows thì có JVM cho Windows. Và cơ chế hoạt động của JVM ở mọi nền tảng là hoàn toàn như nhau cho nên ứng dụng Java viết trên Window chạy được trên JVM của Window, khi đem cái ứng dụng đó qua Ubuntu thì chỉ cần cài JVM lên Ubuntu là ứng dụng được.
Các thành phần chính của Java Virtual Machine
- Class Loader: là một hệ thống con của JVM, làm nhiệm vụ tải các lớp được định nghĩa.
- Class Area: lưu trữ cấu trúc của các lớp, thuộc tính, phương thức của lớp, và code của các phương thức.
- Heap: là vùng nhớ lưu trử các đối tượng được khởi tạo trong quá trình thực thi.
- Stack: chứa các frame. Mỗi frame chứa các biến cục bộ và thực thi một hàm gọi và trả kết quả về. Mỗi tiến trình có một Stack riêng, được khởi tạo cùng lúc với tiến trình. Mỗi frame sẽ được tạo khi một hàm được gọi và hủy khi việc gọi hàm kết thúc.
- Programming Counter Register chứa địa chỉ của máy chủ ảo đang thực thi
- Native Method Stack: chứa các hàm của hệ thống được sữ dụng trong chương trình
- Execution Engine: là một hệ thống bao gồm: bộ xử lý ảo, trình thông dịch (đọc Java byte code và thực thi các chỉ thị), JIT compiler biên dịch mã byte code sang mã máy. Các nhiệm vụ chính của JVM bao gồm: tải code, kiểm tra code, thực thi code, cung cấp môi trường runtime.
Cơ chế làm việc của Java Virtual Machine
JVM được chia thành 3 mô-đun chính:
- Class-Loader Subsytem : chuyên tìm kiếm và load các file .class vào vùng nhớ của Java.
- Runtime Data Area : vùng nhớ hệ thống cấp phát cho Java Virtual Machine.
- Execution Engine: chuyển các lệnh của JVM trong file .class thành các lệnh của máy, hệ điều hành tương ứng và thực thi chúng.
Bộ nhớ trong Java JVM
Khi thực hiện cấp phát một bộ nhớ hoặc một đối tượng mới có thể được tạo và đặt vào vùng nhớ Heap. Khi ứng dụng của bạn không còn tham chiếu tới đối tượng này nữa thì Java garbage collector cho phép xóa đối tượng này đi để sử dụng lại vùng nhớ đó.
Java Heap: JVM giúp lưu tất cả đối tượng đã được tạo ra bởi toán tử “new” trong ứng dụng Java vào trong vùng nhớ Heap ngay tại thời điểm chạy.
Java Stack: Các phương thức và tham chiếu tới đối tượng địa phương được lưu trữ trong Stack. Mỗi Thread sẽ được quản lý một stack. Khi phương thức được gọi, nó được đưa vào đỉnh của Stack. Stack lưu trữ trạng thái của phương thức bao gồm: dòng code thực thi, tham chiếu tới đối tượng địa phương. Khi phương thức chạy xong, vùng nhớ (dòng code thực thi, tham chiếu tới đối tượng địa phương) được đẩy ra khỏi stack và tự động giải phóng.
Java Perm: Lưu trữ thông tin của Class được nạp vào và một vài tính năng khác như StringPool (vùng nhớ của biến String) thường được tạo bởi phương thức String.interm(). Khi ứng dụng của bạn chạy, Perm space được lấp đầy nhanh chóng.