JVM là một chương trình rất phức tạp và dòng chảy ở một mức độ không thể đoán trước. Ví dụ. dòng chảy bên trong HotSpot JVM là một cái gì đó như sau:
1) phải mất bytecode của bạn và giải thích nó
2) nếu một số phương pháp được thực hiện khá thường xuyên (một số lượng lần trong một số khoảng thời gian) nó được đánh dấu như là một " phương thức "hot" và JVM lập lịch trình biên dịch của nó cho nền tảng mã máy phụ thuộc (đó là những gì bạn đã gọi là mã nhị phân?). Luồng đó trông giống như sau:
ByteCode
--> Hige-level Intermediate Representation (HIR)
--> Middle-level Intermediate Representation (MIR)
--> Low-level Intermediate Representation (LIR)
--> Register Allocation
--> EMIT (platform dependent machine code)
Mỗi bước trong luồng đó là quan trọng và giúp JVM thực hiện một số tối ưu hóa mã của bạn. Nó không thay đổi thuật toán của bạn tất nhiên, tối ưu hóa chỉ có nghĩa là một số chuỗi mã có thể được phát hiện và trao đổi với mã thực hiện tốt hơn (sản xuất cùng một kết quả). Bắt đầu từ giai đoạn LIR, mã sẽ trở thành nền tảng phụ thuộc (!).
Bytecode có thể tốt cho việc giải thích, nhưng không đủ tốt để dễ dàng chuyển đổi thành mã gốc của máy. HIR chăm sóc nó và mục đích của nó là nhanh chóng chuyển đổi bytecode thành một biểu diễn trung gian. MIR biến tất cả các hoạt động thành hoạt động ba toán hạng; Bytecode được dựa trên stack hoạt động:
iload_0
iload_1
iand
đó là bytecode cho thao tác đơn giản and
, và đại diện cấp trung cho điều này sẽ loại các nội dung sau:
and v0 v1 -> v2
LIR phụ thuộc vào nền tảng, có tính đến ví dụ đơn giản của chúng tôi với and
hoạt động, và xác định nền tảng của chúng tôi như x86, sau đó đoạn mã của chúng tôi sẽ là:
x86_and v1 v0 -> v1
x86_move v1 -> v2
vìHoạt độngmất hai toán hạng, đầu tiên là đích, một số khác là nguồn, và sau đó chúng ta đặt giá trị kết quả cho một biến "" khác. Giai đoạn tiếp theo là "đăng ký phân bổ", bởi vì nền tảng x86 (và có lẽ hầu hết những người khác) làm việc với các thanh ghi, chứ không phải các biến (như biểu diễn trung gian), cũng như ngăn xếp (như bytecode). Đoạn mã của chúng tôi phải giống như sau:
x86_and eax ecx -> eax
và tại đây bạn có thể nhận thấy sự vắng mặt của thao tác "di chuyển". Mã của chúng tôi chỉ chứa một dòng và JVM đã tìm ra rằng việc tạo một biến ảo mới không cần thiết; chúng tôi chỉ có thể sử dụng lại đăng ký eax
. Nếu mã đủ lớn, có nhiều biến và làm việc với chúng một cách chuyên sâu (ví dụ:sử dụng eax ở đâu đó bên dưới, vì vậy chúng tôi không thể thay đổi giá trị của nó), sau đó bạn sẽ thấy thao tác di chuyển còn lại trong mã máy. Đó là một lần nữa về tối ưu hóa :)
Đó là dòng JIT, nhưng tùy thuộc vào việc triển khai VM có thể thêm một bước nữa - nếu mã được biên dịch (là "nóng") và vẫn được thực hiện nhiều lần, JVM lên lịch tối ưu hóa mã (ví dụ: sử dụng nội tuyến).
Vâng, kết luận là đường dẫn từ bytecode đến mã máy khá thú vị, một chút không lường trước được và phụ thuộc vào nhiều thứ.
btw, quy trình trên được gọi là "Giải thích chế độ hỗn hợp" (khi JVM đầu tiên giải mã bytecode, và sau đó sử dụng biên dịch JIT), ví dụ về JVM đó là HotSpot. Một số JVM (như JRockit từ Oracle) chỉ sử dụng trình biên dịch JIT.
Đây là một mô tả rất đơn giản về những gì đang diễn ra ở đó. Tôi hy vọng rằng nó sẽ giúp hiểu được dòng chảy bên trong JVM ở mức rất cao, cũng như nhắm vào câu hỏi về sự khác biệt giữa mã byte và mã byte. Để tham khảo và các vấn đề khác không được đề cập ở đây và liên quan đến chủ đề đó, vui lòng đọc chủ đề tương tự "Why are compiled Java class files smaller than C compiled files?".
Cũng cảm thấy tự do để phê phán câu trả lời này, chỉ cho tôi đến sai lầm hoặc hiểu lầm của tôi, tôi luôn sẵn sàng để nâng cao kiến thức của tôi về JVM :)
Trong ngữ cảnh nào bạn thấy thuật ngữ "mã nhị phân?" – templatetypedef
Tôi đọc đâu đó mã java đầu tiên được chuyển thành bytecode độc lập của máy, trong khi một số đã nói, nó được chuyển thành mã nhị phân. Vì vậy, tôi hơi bối rối. –
** Mọi thứ ** là "mã nhị phân" !!! Ôi không!! –