TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI
VIỆN CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THÔNG
──────── * ───────
TIỂU LUẬN MÔN HỌC
KIẾN TRÚC MÁY TÍNH
TIÊN TIẾN
Đề tài: Tìm hiểu máy ảo Java Virtual Machine
Học viên thực hiện:
Nguyễn Anh Tuấn – Lớp 2017A CNTT
Đỗ Thị Mai – Lớp 2017A SPKT CNTT
Nguyễn Thị Thùy Dung – Lớp 2015A CNTT
Giáo viên hướng dẫn: TS. Nguyễn Kim Khánh
HÀ NỘI 12-2017
MỤC LỤC
I. TỔNG QUAN VỀ MÁY ẢO JAVA...........................................................................4
1. Khái niệm máy ảo Java.........................................................................................4
2. Quy trình thực thi 1 chương trình Java..................................................................4
3. Kiến trúc cơ bản của JVM.....................................................................................4
3.1. Thành phần tải lớp..........................................................................................5
3.2. Vùng dữ liệu thời gian chạy...........................................................................6
3.3. Máy thực thi...................................................................................................7
II. THÀNH PHẦN TẢI LỚP........................................................................................8
1. Tổng quan về các lệnh chỉ dẫn của JVM...............................................................8
1.1. Các kiểu dữ liệu trong máy ảo Java................................................................8
1.2. Lệnh load và store..........................................................................................9
1.3. Lệnh số học..................................................................................................10
1.4. Lệnh chuyển đổi kiểu dữ liệu.......................................................................10
1.5. Tạo và thao tác với đối tượng.......................................................................11
1.6. Lệnh quản lý trong operand stack.................................................................11
1.7. Lệnh điều khiển............................................................................................11
1.8. Lệnh gọi và trả về của phương thức.............................................................11
1.9. Xử lý ngoại lệ...............................................................................................12
2. Cấu trúc dạng file class.......................................................................................12
2.1. Constant pool...............................................................................................15
2.2. Trường và phương thức:...............................................................................17
2.3. Thuộc tính (Attribute)...................................................................................18
3. Tải lớp, liên kết và khởi tạo (Loading, Linking and Initializing).........................19
3.1. Tải lớp (Loading).........................................................................................19
3.2. Liên kết (Linking)........................................................................................20
3.3. Khởi tạo........................................................................................................21
III. KHU VỰC DỮ LIỆU THỰC THI........................................................................23
1. Khu vực dữ liệu thực thi......................................................................................23
1.1. Ngăn xếp JVM.............................................................................................23
1.2. Khu vực phương thức (Method Area)...........................................................23
1.3. Khu vực Heap (Heap Area)..........................................................................23
1.4. Thanh ghi PC (PC Register).........................................................................23
1.5. Ngăn xếp phương thức native.......................................................................23
2. Khung ngăn xếp..................................................................................................23
2.1 Các biến địa phương......................................................................................24
2.2 Ngăn xếp toán hạng.......................................................................................25
IV. MÁY THỰC THI..................................................................................................26
1. Bộ thông dịch bytecode.......................................................................................26
2. Bộ biên dịch JIT..................................................................................................27
2.1. Các cơ chế biên dịch trong JVM..................................................................27
2.2. Cơ chế hoạt động của bộ biên dịch JIT.........................................................28
3. Bộ dọn rác...........................................................................................................31
3.1. Khái niệm tự động quản lý vùng nhớ (Automatic memory management)....31
3.2. Các kỹ thuật thu gom rác..............................................................................31
TÀI LIỆU THAM KHẢO...........................................................................................34
I. TỔNG QUAN VỀ MÁY ẢO JAVA
1. Khái niệm máy ảo Java
Máy ảo Java, hay còn gọi là JVM là một máy tính toán trừu tượng cho phép
một máy tính có thể chạy được chương trình Java. Có 3 khái niệm chính liên quan tới
JVM: đặc tả (specification), cài đặt (implementation) và thể hiện (instance).
Đặc tả là tài liệu mô tả chính thống về các thành phần cần thiết cho một cài đặt
của JVM.
Một cài đặt của JVM là một chương trình máy tính đáp ứng đầy đủ các yêu cầu
của đặc tả JVM.
Một thể hiện của JVM là cài đặt của JVM được chạy trên 1 tiến trình cụ thể để
thực thi chương trình máy tính (đã được biên dịch sang Java bytecode).
2. Quy trình thực thi 1 chương trình Java
Chương trình Java sẽ được bộ biên dịch Java chuyển thành dạng file có đuôi
class. File class thực chất là một dạng mã máy bao gồm mã Java bytecode và sẽ được
thực thi bởi máy ảo Java. Bởi vì Java bytecode không phụ thuộc vào nền tảng nào
(platform independent) cho nên ứng dụng Java có thể chạy trên mọi nền tảng, miễn là
nền tảng đó có máy ảo Java tương ứng.
3. Kiến trúc cơ bản của JVM
Sơ đồ kiến trúc cơ bản của JVM:
JVM được chia làm 3 thành phần chính:
Thành phần tải lớp (Class Loader Sub-system)
Vùng dữ liệu thời gian chạy (Runtime Data Area)
Máy thực thi (Execution Engine)
3.1. Thành phần tải lớp
3.1.1. Tải lớp (Loading)
Các lớp của chương trình sẽ được tải lên bởi thành phần này, thông qua 3 bộ tải
chính: bộ tải lớp Boot Strap, bộ tải lớp Extension, bộ tải lớp Application.
Bộ tải lớp Boot Strap: làm nhiệm vụ tải các lớp cần thiết để khởi động chương
trình.
Bộ tải lớp Extension: làm nhiệm vụ tải các lớp mở rộng trong thư mục
jre/lib/ext.
Bộ tải lớp Application: làm nhiệm vụ tải các lớp thuộc tầng ứng dụng.
3.1.2. Liên kết (Linking)
Bước liên kết được thực hiện bao gồm:
Xác thực (Verify): Bộ xác thực bytecode sẽ kiểm tra nội dung các mã bytecode
được sinh có đúng chuẩn hay không. Trong trường hợp xác thực thất bại, ta sẽ
nhận được thông báo về lỗi xác thực.
Chuẩn bị (Prepare): Tất cả các biến tĩnh (static variables) sẽ được khởi tạo và
gán giá trị mặc đinh.
Phân giải (Resolve): Các tham chiếu vùng nhớ biểu tượng (symbolic memory
reference) được thay thế bởi các tham chiếu nguyên bản (original reference)
trong khu vực phương thức (Method area).
3.1.3. Khởi tạo (Initialization)
Pha cuối của bước tải lớp, tại đây tất cả các biến tĩnh sẽ được gán giá trị
nguyên bản, và các khối tĩnh (static block) sẽ được thực thi.
3.2. Vùng dữ liệu thời gian chạy
Vùng dữ liệu thời gian chạy được chia làm 5 thành phần chính:
1. Khu vực phương thức: chứa tất cả các dữ liệu mức lớp, bao gồm cả các biến
tĩnh. Mỗi JVM chỉ có một khu vực phương thức, và khu vực này được coi là tài
nguyên chia sẻ.
2. Khu vực Heap: Tất cả các đối tượng và các thể hiện tương ứng của chúng và
mảng sẽ được lưu trữ tại đây. Mỗi JVM chỉ có 1 khu vực Heap.
3. Khu vực ngăn xếp: Với mỗi luồng, một ngăn xếp thời gian chạy riêng biệt sẽ
được tạo ra. Với mỗi lời gọi phương thức, một đăng ký sẽ được lưu trong bộ
nhớ ngăn xếp và được gọi là khung ngăn xếp (Stack Frame). Tất cả các biến
cục bộ sẽ được lưu trong bộ nhớ ngăn xếp. Khung ngăn xếp được chia làm 3
thành phần:
a. Mảng biến cục bộ: lưu giá trị các biến cục bộ liên quan tới phương thức.
b. Ngăn xếp toán hạng (Operand stack): nếu có mệnh lệnh nào cần thực thi
ngay lập tức, ngăn xếp toán hạng sẽ hoạt động như không gian thời gian
chạy (runtime workspace) để thực thi mệnh lệnh.
c. Dữ liệu khung (Frame data): lưu trữ các biểu tượng liên quan tới phương
thức. Trong trường hợp xảy ra các ngoại lệ (exception), thông tin trong
khối Catch sẽ được duy trình trong thành phần này.
4. Bộ đăng ký PC (PC Registers): Mỗi luồng sẽ có các bộ đăng ký PC riêng biệt
để lưu địa chỉ của mệnh lệnh đang thực thi. Sau khi mệnh lệnh thực thi xong,
bộ đăng ký PC sẽ được cập nhật lệnh tiếp theo.
5. Ngăn xếp phương thức Native (Native method stack): thành phần này chứa các
thông tin về phương thức native. Với mỗi luồng, một ngăn xếp phương thức
native riêng biệt sẽ được tạo ra.
3.3. Máy thực thi
Các mã bytecode được gán trong vùng dữ liệu thời gian chạy sẽ thực thi bởi bộ
máy thực thi. Máy thực thi đọc các mã bytecode và thực thi tuần tự từng mã lệnh một.
Bộ thông dịch (Interpreter): Bộ thông dịch đọc hiểu mã bytecode tương đối
nhanh, nhưng lại chậm trong thực thi. Nhược điểm của bộ thông dịch là nếu 1
phương thức được gọi nhiều lần, mỗi lần gọi phương thức đó sẽ lại được thông
dịch lại.
Bộ biên dịch JIT (JIT Compiler – Just In Time Compiler): Bộ biên dịch JIT giải
quyết nhược điểm của bộ thông dịch bằng cách biên dịch các mã bytecode
thường được gọi nhiều lần sang ngôn ngữ máy để thực thi trực tiếp, giúp tăng
hiệu năng của hệ thống. Các thành phần chính của bộ biên dịch JIT
o Bộ sinh mã trung gian (Intermediate Code Generator): sinh mã trung
gian.
o Bộ tối ưu mã (Code Optimizer): tối ưu mã trung gian sinh ở bước trên.
o Bộ sinh mã mục tiêu (Target Code Generator): làm nhiệm vụ sinh mã
native hoặc mã máy.
o Bộ lọc hồ sơ (Profiler): làm nhiệm vụ xác định các điểm nóng (hotspot):
các phương thức thường xuyên được gọi tới.
Bộ thu gom rác (Garbage Collector): thu thập và giải phóng các đối tượng
không còn được tham chiếu nữa.
Giao diện Java Native (Java Native Interface – JNI): làm nhiệm vụ tương tác
với các thư viện Native và cung cấp các thư viện native cần thiết cho Máy thực thi.
Thư viện phương thức Native: là tập các thư viện native cần thiết cho Máy thực
thi.
II. THÀNH PHẦN TẢI LỚP
1. Tổng quan về các lệnh chỉ dẫn của JVM
Một lệnh trong Java Virtual Machine bao gồm một byte opcode chỉ rõ phép toán
được thực hiện, cùng với đó có thể không có hoặc có nhiều toán hạng cung cấp các
đối số hoặc dữ liệu được sử dụng bởi hoạt động đó. Nhiều lệnh không có các toán
hạng (chỉ bao gồm một opcode).
Số lượng và kích thước của các toán hạng được xác định bởi opcode. Nếu một toán
hạng có kích thước nhiều hơn một byte, thì nó được lưu trữ theo thứ tự big-endian byte cao xếp trước.
Dòng lệnh bytecode chỉ nằm trong một byte. Hai trường hợp ngoại lệ
là lookupswitch và tableswitch giới hạn toán hạng trong 4 byte.
1.1. Các kiểu dữ liệu trong máy ảo Java
Hầu hết các chỉ dẫn trong tập lệnh Java Virtual Machine đặt kiểu mã hóa thông
tin về các hoạt động sẽ được thực hiện. Ví dụ, chỉ dẫn iload tải các nội dung của một
biến địa phương, mà phải là một số nguyên vào ngăn xếp chứa toán hạng. Hai chỉ dẫn
có thể được cài đặt giống nhau, nhưng có các mã opcode riêng biệt (ví dụ: chỉ dẫn
iadd and fadd).
Đối với phần lớn các loại lệnh, kiểu dữ liệu trong câu lệnh được thể hiện rõ
ràng trong mnemonic opcode bởi một kí tự: I cho int, l cho long, s cho short, b cho
byte, c cho char, f cho float, d cho double, và a cho reference
opcode
Tipush
byte short int
long float
doubl
char reference
e
bipus sipus
h
h
Tconst
iconst
lcons fcons dcons
t
t
t
aconst
Tload
iload
lload fload dload
aload
Tstore
istore
lstor fstor dstor
e
e
e
astore
Tinc
iinc
Taload
baloa saloa iaload
d
d
laloa faloa daloa caloa aaload
d
d
d
d
Tastore
basto sasto iastore
re
re
lasto fasto dasto casto aastore
re
re
re
re
Tadd
iadd
ladd fadd dadd
Tsub
isub
lsub fsub dsub
Tmul
imul
lmul fmul dmul
Tdiv
idiv
ldiv
Trem
irem
lrem frem drem
Tneg
ineg
lneg fneg dneg
Tshl
ishl
lshl
Tshr
ishr
lshr
Tushr
iushr
lushr
Tand
iand
land
Tor
ior
lor
Txor
ixor
lxor
i2T
i2b
i2s
i2l
l2T
l2i
f2T
f2i
f2l
d2T
d2i
d2l
Tcmp
fdiv
ddiv
i2f
i2d
l2f
l2d
f2d
d2f
lcmp
Tcmpl
fcmp dcmp
l
l
Tcmpg
fcmp dcmp
g
g
if_Tcmp
OP
if_icmp
OP
Treturn
ireturn
if_acmp
OP
lretu fretu dretu
rn
rn
rn
areturn
Kiểu dữ liệu trong tập lệnh máy ảo Java
Bảng trên tóm tắt các kiểu dữ liệu hỗ trợ trong tập lệnh của Java Virtual
machine. Đối với một lệnh cụ thể, thông tin kiểu dữ liệu được xây dựng bằng việc
thay thế T trong cột opcode bằng ký tự trong cột kiểu dữ liệu, nếu kiêu dữ liệu ở cột
nào trống thì có nghĩa không tồn tại lệnh hỗ trợ kiểu dữ liệu đó.
1.2. Lệnh load và store
Lệnh load và store được sử dụng để chuyển giá trị giữa local variables và
operand stack trong JVM frame (frame được sử dụng để lưu trữ dữ liệu)
Lệnh load chuyển giá trị local variables vào operand stack: iload, iload_<n>,
lload, lload_<n>, fload, fload_<n>, dload, dload_<n>, aload, aload_<n>
Lệnh store chuyển giá trị từ operand stack vào local variables: istore,
istore_<n>, lstore, lstore_<n>, fstore, fstore_<n>, dstore, dstore_<n>, astore,
astore_<n>.
Lệnh load 1 hằng số vào trong operand stack: bipush, sipush, ldc, ldc_w,
ldc2_w, aconst_null, iconst_m1, iconst_<i>, lconst_<l>, fconst_<f>,
dconst_<d>.
Các lệnh truy cập tới các trường của đối tượng hoặc các phần tử của mảng cũng
được sử dụng để chuyển giá trị từ đối tượng hoặc phần tử đó vào trong operand stack
1.3. Lệnh số học
Lệnh số học được sử dụng để tính toán 2 giá trị trên được lưu trữ trong operand
stack. Có 2 loại chính trong lệnh số học đó là việc tính toán trên các giá trị integer và
float-point. Các lệnh số học được liệt kê ở dưới đây:
Cộng: iadd, ladd, fadd, dadd.
Trừ: isub, lsub, fsub, dsub.
Nhân: imul, lmul, fmul, dmul.
Chia: idiv, ldiv, fdiv, ddiv.
Chia lấy dư: irem, lrem, frem, drem.
Phủ định: ineg, lneg, fneg, dneg.
Dịch bit: ishl, ishr, iushr, lshl, lshr, lushr.
OR các bit: ior, lor.
AND các bit: iand, land.
XOR các bit: ixor, lxor.
So sánh: dcmpg, dcmpl, fcmpg, fcmpl, lcmp
1.4. Lệnh chuyển đổi kiểu dữ liệu
Lệnh chuyển đổi kiểu dữ liệu cho phép chuyển đổi các kiểu dữ liệu số học
trong Java Virtual Machine. JVM hỗ trợ trực tiếp việc chuyển đồi từ kiểu dữ liệu nhỏ
hơn sang kiểu dữ liệu lớn hơn:
int chuyển đổi thành long, float, hoặc double
long chuyển đổi thành float or double
float chuyển đổi thành double
Lệnh chuyển đổi sang kiểu dữ liệu lớn hơn có dạng i2l, i2f, i2d, l2f, l2d, và f2d.
Việc chuyển đổi từ kiểu int sang kiểu long hoặc kiểu int sang kiểu double sẽ không
làm mất mát thông tin của giá trị ứng với kiểu dữ liệu trước đó. Tuy nhiên việc chuyển
đổi từ kiểu float sang kiểu double thì có thể gây mất mát thông tin của giá trị.
Ngoài ra JVM cũng hỗ trợ việc chuyển đổi kiểu dữ liệu về kiểu dữ liệu nhỏ hơn:
int chuyển đổi về byte, short, hoặc char
long chuyển đổi về int
float chuyển đổi về int hoặc long
double chuyển đổi về int, long, hoặc float
Lệnh chuyển đổi về kiểu dữ liệu nhỏ hơn có dạng i2b, i2c, i2s, l2i, f2i, f2l, d2i,
d2l, and d2f. Việc chuyển đối sang kiểu dữ liệu nhỏ hơn này sẽ làm mất đi độ chính
xác.
1.5. Tạo và thao tác với đối tượng
Mặc dù thể hiện của lớp và mảng là những đối tượng, tuy nhiên trong JVM
việc tạo và sử dụng chúng không giống nhau:
Tạo mới thể hiện của 1 lớp ta sử dụng: new
Tạo mới một mảng ta sử dụng: newarray, anewarray, multianewarray
Truy cập tới các thuộc tính của đối tượng ta sử dụng: getfield, putfield,
getstatic, putstatic.
Nạp các phần tử của mảng vào trong operand stack sử dụng: baload, caload,
saload, iaload, laload, faload, daload, aaload
Lưu mỗi giá trị của operand stack vào mỗi phần tử của mảng sử dụng: bastore,
castore, sastore, iastore, lastore, fastore, dastore, aastore
Tính độ dài của mảng thông qua: arraylength
1.6. Lệnh quản lý trong operand stack
Số lượng tập lệnh sử dụng trực tiếp trong operand stack bao gồm: pop, pop2,
dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2, swap.
1.7. Lệnh điều khiển
Các lệnh điều khiển có điều kiện hoặc không có điều kiện xảy ra để tiếp tục
thực thi một câu lệnh khác, chúng được liệt kê dưới đây:
Một số lệnh điều khiển có điều kiện: ifeq, ifne, iflt, ifle, ifgt, ifge, ifnull,
ifnonnull, if_icmpeq, if_icmpne, if_icmplt, if_icmple, if_icmpgt if_icmpge,
if_acmpeq, if_acmpne.
Một số lệnh điều khiển không điều kiện: goto, goto_w, jsr, jsr_w, ret.
Lệnh điều kiện kết hợp: tableswitch, lookupswitch
1.8. Lệnh gọi và trả về của phương thức
Có 5 loại lệnh để gọi phương thức:
invokevirtual: gọi phương thức của một đối tượng.
invokeinterface: gọi phương thức của 1 interface, tìm kiếm phương thức được
cài đặt bởi 1 đối tượng thời gian chạy (run-time object).
invokespecial: gọi phương thức cần có xử lý đặc biệt: phương thức khởi tạo
của một đối tượng, phương thức dạng private, hoặc phương thức của lớp cha.
invokestatic: gọi phương thức static của lớp.
invokedynamic:gọi phương thức là mục tiêu của đối tượng gọi gắn với chỉ dẫn
invokedynamic.
Lệnh trả về của phương thức có các kiểu: ireturn (sử dụng để trả về giá trị kiểu
boolean, byte, char, short, hoặc int), lreturn, freturn, dreturn, và areturn.
1.9. Xử lý ngoại lệ
Ngoại lệ trong JVM được xử lý thông qua lệnh athrow. Các ngoại lệ cũng có
thể được phát ra bởi các chỉ dẫn của JVM nếu có xảy ra các điều kiện bất thường.
2. Cấu trúc dạng file class
Như đã trình bày ở trên, file class là kết quả biên dịch từ file java. Mỗi file sẽ
chứa định nghĩa của 1 lớp hoặc 1 interface. Một file class là một chuỗi các byte 8 bit,
tất cả các thành phần 16 bit, 32 bit và 64 bit đều được dựng lại bằng việc đọc 2, 4 và 8
byte liên tiếp. Tất cả các thành phần dữ liệu là đa byte (multibyte) đều sẽ được lưu trữ
theo thứ tự big endian, tức là byte cao xếp trước.
Cấu trúc của 1 file class có thể được mô tả như sau:
ClassFile {
u4
u2
u2
u2
cp_info
u2
u2
u2
u2
u2
u2
field_info
u2
method_info
u2
attribute_info
magic;
minor_version;
major_version;
constant_pool_count;
constant_pool[constant_pool_count-1];
access_flags;
this_class;
super_class;
interfaces_count;
interfaces[interfaces_count];
fields_count;
fields[fields_count];
methods_count;
methods[methods_count];
attributes_count;
attributes[attributes_count];
}
Các thành phần trong file class:
magic: số đặc trưng cho định dạng file class với giá trị 0xCAFEBABE.
minor_version, major_version: số hiệu phiên bản (số nhỏ, số lớn) của
định dạng. Định dạng file class cho Java version 7 là 0x0000, 0x0033 (51.00).
constant_pool_count: có giá trị bằng số lượng phần tử trong bảng
constant_pool cộng với 1. Chỉ số của 1 phần tử trong constant_pool là
hợp lệ nếu lớn hơn 0 và nhỏ hơn constant_pool_count.
constant_pool[]: là một bảng cấu trúc đại diện cho các hằng số xuất hiện
trong cấu trúc của file class bao gồm chuỗi, tên lớp, tên interface, tên trường…
access_flags: có giá trị bằng hợp của các cờ đánh dấu quyền truy cập cũng
như đặc tính của lớp hay interface của file class. Các cờ này bao gồm:
Tên cờ
ACC_PUBLIC
Giá trị
0x0001
ACC_FINAL
ACC_SUPER
ACC_INTERFACE
ACC_ABSTRACT
0x0010
0x0020
0x0200
0x0400
ACC_SYNTHETIC
0x1000
ACC_ANNOTATION
0x2000
ACC_ENUM
0x4000
Ý nghĩa
Khai báo public: có thể truy nhập từ
ngoài package
Khai báo final: không cho kế thừa
Đánh dấu là interface, không phải lớp
Khai báo abstract: không được khởi
tạo
Khai báo synthetic: không có mã
nguồn
Khai báo là dạng chú thích
(annotation)
Khai báo là dạng liệt kê
(enumeration)
this_class: có giá trị là một chỉ số hợp lệ trong bảng constant_pool.
Phần tử tại chỉ số này phải có cấu trúc CONSTANT_Class_info đại diện cho
lớp hoặc interface được định nghĩa bởi file class.
super_class:
o Với lớp, giá trị của phần tử này hoặc bằng 0 hoặc là một chỉ số hợp lệ
trong bảng constant_pool. Phần tử tại chỉ số này phải có cấu trúc
CONSTANT_Class_info đại diện cho lớp cha trực tiếp của lớp được
định nghĩa bởi file class. Nếu giá trị này bằng 0, file class này sẽ đại
diện cho lớp Object, lớp duy nhất không có lớp cha trực tiếp.
o Với interface, giá trị của phần tử này là chỉ số trỏ tới phần tử có cấu trúc
CONSTANT_Class_info đại diện cho lớp Object.
interfaces_count: giá trị bằng số lượng interface mà lớp/interface này kế
thừa.
interfaces[]: một mảng các giá trị là chỉ số trỏ tới bảng constant_pool
mà phần tử tại đó có cấu trúc CONSTANT_Class_info đại diện cho interface
được lớp/interface này kế thừa trực tiếp.
fields_count: có giá trị bằng số phần tử trong bảng fields.
fields[]: mỗi phần tử trong bảng fields phải có cấu trúc field_info
đưa ra mô tả đầy đủ cho một trường được khai báo trong lớp/interface này.
Bảng fields không bao gồm các trường kế thừa từ lớp cha hoặc interface cha.
methods_count: có giá trị bằng số phần tử trong bảng methods.
methos[]: mỗi phần tử trong bảng methods phải có cấu trúc method_info
đưa ra mô tả đầy đủ về một phương thức trong lớp/interface này. Nếu các cờ
ACC_NATIVE hoặc ACC_ABSTRACT không được thiết lập, các chỉ dẫn JVM cài
đặt cho phương thức sẽ được cung cấp. Bảng method không bao gồm các
phương thức kế thừa từ lớp cha hoặc interface cha.
attributes_count: có giá trị bằng số phần tử trong bảng attributes.
attributes[]: mỗi phần tử trong bảng attributes phải có cấu trúc
attribute_info. Các phần tử này chứa thông tin của trường, phương thức
và lớp.
Sơ đồ mô tả cấu trúc file class:
2.1. Constant pool
Constant pool là thành phần quan trọng nhất trong file class, chứa các thông tin
sẽ được trỏ tới bởi các thành phần khác trong file. Constant pool là một mảng các
phần tử với chỉ số bắt đầu bằng 1. Mỗi phần tử bắt đầu bằng 1 nhãn 1 byte đánh dấu
loại phần tử:
CONSTANT_Utf8: đại diện cho các chuỗi UTF8.
CONSTANT_Integer: đại diện cho giá trị số nguyên, độ dài 4 byte.
CONSTANT_Float: đại diện cho giá trị float tuân theo định dạng dấu phẩy
động IEEE 754, độ dài 4 byte.
CONSTANT_Long: đại diện cho giá trị long, độ dài 8 byte. Lưu ý phần tử này
được đếm 2 lần trong số lượng phần tử của constant pool.
CONSTANT_Double: đại diện cho giá trị double, độ dài 8 byte. Tương tự
CONSTANT_Long, phần tử này cũng được đếm 2 lần trong số lượng phần tử
của constant pool.
CONSTANT_Class: đại diện cho một lớp hoặc interface, bao gồm 1 chỉ số
trong constant pool trỏ tới 1 giá trị CONSTANT_Utf8 chứa tên của
lớp/interface. Ví dụ: java/lang/Thread.
CONSTANT_String: đại diện cho các chuỗi hằng trong lớp, bao gồm 1 chỉ số
trong constant_pool trỏ tới 1 giá trị trong CONSTANT_Utf8 chứa giá trị của
chuỗi.
CONSTANT_Fieldref: tham chiếu cho 1 trường, bao gồm 1 chỉ số trỏ tới
CONSTANT_Class mà trường này nằm trong và 1 chỉ số trỏ tới
CONSTANT_NameAndType đại diện cho tên và loại của trường.
CONSTANT_Methodref: tham chiếu cho 1 phương thức, bao gồm 1 chỉ số trỏ
tới CONSTANT_Class mà phương thức này nằm trong và 1 chỉ số trỏ tới
CONSTANT_NameAndType đại diện cho tên và mô tả của phương thức (method
descriptor). Lưu ý CONSTANT_Class phải đại diện cho 1 lớp chứ không phải
interface.
CONSTANT_InterfaceMethodref: tương tự CONSTANT_Methodref nhưng
phần tử CONSTANT_Class đại diện cho 1 interface chứ không phải lớp.
CONSTANT_NameAndType: đại diện cho thông tin 1 trường hoặc 1 phương
thức, bao gồm 2 chỉ số trong bảng constant pool trỏ tới giá trị trong
CONSTANT_Utf8, chỉ số thứ nhất là tên trường/phương thức, chỉ số thứ 2 là
kiểu trường/mô tả của phương thức.
CONSTANT_MethodHandle: phần tử này được dùng để phân giải tham chiếu
biểu tượng tới 1 xử lý phương thức (method handle), bao gồm 1 byte chỉ thị
loại xử lý phương thức (có giá trị từ 1 tới 9) và 2 byte là chỉ số trong bảng
constant pool trỏ tới CONSTANT_Fieldref hoặc CONSTANT_Methodref
hoặc CONSTANT_InterfaceMethodref tùy thuộc vào loại xử lý phương
thức.
CONSTANT_MethodType: trường này để phân giải kiểu của phương thức, bao
gồm 1 chỉ số trong bảng constant pool trỏ tới CONSTANT_Utf8 mô tả kiểu
phương thức.
CONSTANT_InvokeDynamic: phần tử này được chỉ dẫn invokedynamic sử
dụng để xác định một phương thức bootstrap, bao gồm 1 chỉ số của bảng
phương thức bootstrap và 1 chỉ số của bảng constant pool trỏ tới
CONSTANT_NameAndType xác định tên phương thức và mô tả của phương
thức.
Ta có thể tóm tăt cấu trúc các loại phần tử của bảng constant pool qua sơ đồ
dưới đây:
Chú ý: Các loại xử lý phương thức được liệt kê trong bảng sau:
Loại xử lý phương thức
(Method handle kind)
Mô tả
1
2
3
4
5
6
7
8
REF_getField
REF_getStatic
REF_putField
REF_putStatic
REF_invokeVirtual
REF_invokeStatic
REF_invokeSpecial
REF_newInvokeSpecia
l
REF_invokeInterface
9
2.2. Trường và phương thức:
Mỗi trường được mô tả bởi cấu trúc field_info với định dạng sau:
field_info {
u2
access_flags;
u2
name_index;
u2
descriptor_index;
u2
attributes_count;
attribute_info attributes[attributes_count];
}
trong đó
name_index và descriptor_index trỏ tới giá trị CONSTANT_Utf8 gồm
tên và mô tả trường.
attributes_count và attribute[] chứa số lượng thuộc tính và bảng
attributes.
Tương tự phương thức được mô tả bởi cấu trúc method_info với định dạng:
method_info {
u2
access_flags;
u2
name_index;
u2
descriptor_index;
u2
attributes_count;
attribute_info attributes[attributes_count];
}
Lưu ý với phương thức trong attributes sẽ có thuộc tính mã nguồn (code
attribute) chứa mã nguồn của phương thức.
Các cờ truy cập của trường và phương thức:
2.3. Thuộc tính (Attribute)
Mỗi trường, phương thức và lớp đều có các thông tin được định nghĩa thông
qua bảng attribute. Các thông tin đó bao gồm:
Mã nguồn
Biến cục bộ, các giá trị hằng, thông tin về ngăn xếp và ngoại lệ
Các lớp trong, các phương thức bootstrap
Chú thích (annotation)
Thông tin debug/dịch ngược (decompilation)
Các thông tin bổ sung, ví dụ: chú thích lỗi thời (Deprecated), chữ ký
(signature)
Thuộc tính quan trọng nhất là mã nguồn, được mô tả qua sơ đồ dưới:
3. Tải lớp, liên kết và khởi tạo (Loading, Linking and Initializing)
3.1. Tải lớp (Loading)
Các lớp của chương trình được tải lớp thông qua ba bộ tải:
Bộ tải Bootstrap
Bộ tải Extension
Bộ tải Application
Để nạp một lớp, máy ảo cần phải thực hiện:
Tên đầy đủ của lớp, tạo ra 1 luồng dữ liệu nhị phân cho lớp
Phân tích luồng dữ liệu nhị phân vào cấu trúc dữ liệu nội bộ trong phương
thức trống.
Khởi tạo một lớp java.lang.class thể hiện cho kiểu.
Luồng dữ liệu nhị phân được định dạng theo lớp java, nhưng có thể thay đổi
định dạng đó thành các dạng khác. Tất cả máy ảo java phải định dạng tập tin lớp java,
nhưng với các triển khai cá nhân chúng ta có thể định sang kiểu khác.
Một số cách để tạo ra dữ liệu nhị phân cho 1 lớp:
Tải một tệp lớp java từ hệ thống tệp cục bộ
Tải tệp qua mạng
Giải nén tệp tin java từ ZIP, JAR, CAB hoặc tệp lưu trữ khác
Giải nén tệp java từ một cơ sở dữ liệu
Biên dịch file được định dạng vào tệp nguồn của class
Các lớp được nạp thông qua trình nạp bootstrap hoặc thông qua bộ tải do người
dùng định nghĩa. Khi máy ảo khởi động, trình nạp sẽ tải các lớp và interface thuộc về
java API, tiếp đến là tải các lớp thuộc về chương trình.
3.2. Liên kết (Linking)
Liên kết bao gồm 3 bước xác minh, chuẩn bị và phân giải.
3.2.1. Xác minh (Verify)
Sau khi một lớp được nạp, lớp đó đã sẵn sàng để được liên kết. Bước đầu tiên
của quá trình liên kết là xác thực: đảm bảo rằng lớp tuân theo ngữ nghĩa của ngôn ngữ
java và không vi phạm tính toàn vẹn của máy ảo.
Xác minh là một trong những phần tương đối linh hoạt trong cài đặt máy ảo
java: nhà thiết kế có thể quyết định cách thức và thời điểm để xác minh lớp. Các xác
minh được thực hiện bao gồm:
Xác minh cấu trúc của dạng file class hợp lệ.
Kiểm tra các chỉ dẫn:
o Chỉ dẫn bắt đầu với mã opcode hợp lệ?
o Nếu chỉ dẫn có toán hạng hằng số thì toán hạng đó có kiểu hợp lệ
không?
o Nếu chỉ dẫn có toán hạng biến số thì toán hạng đó có nằm trong dải giá
trị cho phép không?
o Nếu chỉ dẫn là chỉ dẫn rẽ nhánh, các nhánh có trỏ tới điểm bắt đầu của
chỉ dẫn khác không?
Kiểm tra ngăn xếp và mảng biến cục bộ của chỉ dẫn
3.2.2. Chuẩn bị
Sau khi một máy ảo Java đã tải một lớp và thực hiện xác minh lớp, bước tiếp
theo là chuẩn bị lớp. Trong giai đoạn chuẩn bị, máy ảo Java cấp phát bộ nhớ cho các
biến lớp và đặt cho chúng các giá trị ban đầu mặc định dựa trên kiểu của biến. Các
biến lớp không được khởi tạo với các giá trị ban đầu thích hợp cho đến giai đoạn khởi
tạo.
Cũng trong giai đoạn chuẩn bị, máy ảo Java có thể phân bổ bộ nhớ cho các cấu
trúc dữ liệu nhằm cải thiện hiệu suất của chương trình đang chạy. Một ví dụ về cấu
trúc dữ liệu như vậy là một bảng phương thức, chứa một con trỏ tới dữ liệu cho mọi
phương thức trong một lớp, kể cả các phương thức thừa kế từ các lớp cha. Một bảng
phương thức cho phép một phương thức thừa kế được gọi trên một đối tượng mà
không cần tìm kiếm trong các lớp cha tại thời điểm gọi.
3.2.3. Phân giải
Sau khi một lớp đã được thông qua hai giai đoạn đầu tiên của liên kết: xác
minh và chuẩn bị, nó đã sẵn sàng cho giai đoạn thứ ba và cuối cùng của liên kết: phân
giải. Phân giải là quá trình xác định vị trí các lớp, giao diện, trường, và các phương
pháp được tham chiếu một cách tượng trưng từ một hằng số của một loại, và thay thế
những tham chiếu tượng trưng đó bằng các tham khảo trực tiếp. Giai đoạn liên kết này
là tùy chọn cho đến khi (và trừ khi) mỗi tham chiếu tượng trưng lần đầu tiên được
chương trình sử dụng.
3.3. Khởi tạo
Bước cuối cùng cần thiết để sẵn sàng sử dụng một lớp hoặc giao diện là khởi
tạo; quá trình thiết lập biến lớp thành các giá trị ban đầu thích hợp, hay chính là giá trị
bắt đầu mong muốn của lập trình viên cho một biến lớp.
Trong mã Java, một giá trị ban đầu thích hợp được xác định thông qua một
trình khởi tạo lớp hoặc khởi tạo tĩnh. Một biến khởi tạo lớp có dạng một dấu bằng và
biểu thức tương đương với một khai báo biến lớp, ví dụ:
class Example1a {
// "= 3 * (int) (Math.random() * 5.0)"
// initializer
static int size = 3 * (int) (Math.random() * 5.0);
}
Một khởi tạo tĩnh là một khối mã được bắt đầu bởi từ khóa static, ví dụ:
class Example1b {
static int size;
// This is the static initializer
static {
size = 3 * (int) (Math.random() * 5.0);
}
}
Tất cả các biến khởi tạo lớp và khởi tạo tĩnh của một kiểu loại được thu thập
bởi trình biên dịch Java và được đặt vào một phương thức đặc biệt. Đối với các lớp,
phương thức này được gọi là phương thức khởi tạo lớp; đối với interface, phương thức
khởi tạo giao diện. Phương thức này chỉ có thể được gọi bởi máy ảo Java để thiết lập
các biến tĩnh của kiểu với các giá trị ban đầu thích hợp của chúng.
Khởi tạo một lớp bao gồm hai bước:
1. Khởi tạo lớp cha trực tiếp của lớp (nếu có).
2. Thực hiện phương thức khởi tạo lớp của lớp (nếu có).
Khi khởi tạo lớp cha trực tiếp của lớp, hai bước trên lại được lặp lại. Kết quả là,
lớp đầu tiên sẽ được khởi tạo sẽ là lớp Object, sau đó tất cả các lớp từ trên xuống theo
phân cấp thừa kế cho lớp đang được khởi tạo.
Máy ảo Java phải đảm bảo rằng quá trình khởi tạo được đồng bộ hóa chính xác.
Nếu nhiều luồng cần phải khởi tạo một lớp, chỉ một luồng sẽ được phép thực hiện
khởi tạo trong khi các luồng khác đợi. Sau khi luồng này hoàn thành quá trình khởi
tạo, phải có thông báo cho các luồng khác đang chờ.
III. KHU VỰC DỮ LIỆU THỰC THI
1. Khu vực dữ liệu thực thi
Máy ảo Java định nghĩa một loạt các khu vực dữ liệu thời gian chạy (run-time
data area) được sử dụng trong quá trình chạy của chương trình. Một số khu vực dữ
liệu được tạo khi JVM bắt đầu chạy và giải phóng khi thoát JVM. Các khu vực dữ liệu
khác thì gắn với từng luồng: khởi tạo khi một luồng được khởi tạo và giải phóng khi
luồng kết thúc.
1.1. Ngăn xếp JVM
Mỗi luồng trong JVM sẽ có 1 ngăn xếp JVM được tạo cùng lúc với luồng.
Ngăn xếp JVM được sử dụng để lưu khung ngăn xếp (stack frame). Mỗi khung ngăn
xếp được dùng để chứa 3 thực thể con:
Mảng các biến cục bộ
Ngăn xếp toán hạng
Dữ liệu khung: chứa các ký hiệu liên quan tới phương thức.
1.2. Khu vực phương thức (Method Area)
Khu vực phương thức là nơ i lưu trữ dữ liệu mức class - tức toàn bộ các dữ
liệu có trong một class sẽ nằm ở đây. Mỗi JVM chỉ có một khu vực phương thức
và nó có thể được sử dụng bởi nhiều tiến trình.
1.3. Khu vực Heap (Heap Area)
Khu vực này lưu trữ các đối tượng và các thứ liên quan như biến, mảng...
Giống như khu vực phương thức, mỗi JVM chỉ có một khu vực Heap. Vì 2 vùng
này được các tiến trình chia sẻ với nhau nên dữ liệu lưu ở đây không đảm bảo
thread-safe.
1.4. Thanh ghi PC (PC Register)
PC là viết tắt của Program Counter - một thanh ghi lưu địa chỉ của lệnh
đang thực thi. Mỗi luồng sẽ sở hữu riêng một PC.
1.5. Ngăn xếp phương thức native
Ngăn xếp giữ các thông tin native của phương thức. Mỗi luồng đều sở
hữu một ngăn xếp phương thức native riêng.
2. Khung ngăn xếp
Khung được sử dụng để lưu trữ một phần dữ liệu và kết quả, cũng như để thực
hiện kết nối động, trả lại các giá trị cho các phương thức và gửi các ngoại lệ.
Một khung mới được tạo ra mỗi khi một phương thức được gọi ra. Khung bị
phá hủy khi cuộc gọi phương thức hoàn thành, cho dù hoàn thành là bình thường hay
đột ngột (nó ném một ngoại lệ không được khai báo). Khung được phân bổ từ khu vực
heap của máy ảo Java của luồng tạo khung. Mỗi khung có mảng các biến cục bộ riêng,
ngăn xếp toán hạng riêng của nó, và một tham chiếu đến các hằng số thời gian chạy
của lớp của phương thức hiện tại.
Một khung có thể được mở rộng với các thông tin cụ thể về thực hiện bổ sung,
như gỡ lỗi thông tin. Kích cỡ của mảng biến cục bộ và ngăn xếp toán hạng được xác
định tại thời gian biên dịch và được cung cấp cùng với mã cho phương pháp liên quan
đến khung. Do đó, kích thước của cấu trúc dữ liệu khung chỉ phụ thuộc vào việc thực
hiện. Bộ nhớ cho các cấu trúc này có thể được phân bổ đồng thời tại thời điểm gọi
phương thức.
Chỉ có một khung duy nhất cho phương thức thực hiện hoạt động tại bất kỳ
điểm nào trong một chuỗi xử lý nhất định. Khung này được gọi là khung hiện tại, và
phương thức của nó được gọi là phương thức hiện tại. Lớp của phương thức hiện tại
được định nghĩa là lớp hiện tại. Các thao tác trên các biến cục bộ và ngăn xếp toán
hạng thường có tham chiếu đến khung hiện tại.
Một khung kết thúc là hiện tại nếu phương thức của nó gọi phương thức khác
hoặc nếu phương thức của nó hoàn thành. Khi một phương thức được gọi, một khung
mới được tạo ra và trở thành hiện tại khi điều khiển chuyển sang phương thức mới.
Khi phương thức trả về, khung hiện tại sẽ truyền lại kết quả của việc gọi phương thức,
nếu có, đến khung trước. Khung hiện tại sau đó sẽ bị loại bỏ và khung trước trở thành
khung hiện tại.
Lưu ý rằng một khung được tạo ra bởi một luồng là cục bộ cho luồng đó và
không thể được tham chiếu bởi bất kỳ luồng khác.
2.1 Các biến địa phương
Mỗi khung chứa một mảng các biến được biết đến dưới dạng các biến cục bộ
của nó. Chiều dài của mảng biến cục bộ của khung được xác định tại thời gian biên
dịch và được cung cấp trong biểu diễn nhị phân của một lớp hoặc giao diện cùng với
mã cho phương thức liên kết với khung.
Một biến địa phương duy nhất có thể giữ một giá trị kiểu boolean, byte,
char, short, int, float, reference, hoặc returnAddress.
Các biến địa phương được tham chiếu bằng cách lập chỉ mục. Chỉ số của
biến địa phương thứ nhất là số không. Số nguyên là chỉ mục hợp lệ của biến cục
bộ nếu và chỉ nếu số nguyên đó nằm giữa số không và một ít hơn kích thước
của mảng biến cục bộ.