1. Trang chủ
  2. » Công Nghệ Thông Tin

Động lực học lập trình Java, Phần 1: Các lớp Java và việc nạp các lớp Quan sát các lớp và những gì xảy ra khi chúng được một JVM nạp docx

13 462 1
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 13
Dung lượng 1 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Các chủ đề gồm từ cấu trúc cơ sở của định dạng tệp lớp nhị phân Java, thông qua truy cập siêu dữ liệu trong thời gian chạy bằng cách sử dụng sự phản chiếu, tất cả các cách để sửa đổi và

Trang 1

Động lực học lập trình Java, Phần 1: Các lớp Java và việc nạp các lớp

Quan sát các lớp và những gì xảy ra khi chúng được một JVM nạp

Dennis Sosnoski, Nhà tư vấn, Sosnoski Software Solutions, Inc

Tóm tắt: Hãy xem xét những gì xảy ra ở hậu trường về việc thực hiện ứng dụng

Java của bạn trong loạt bài viết mới về các khía cạnh động của lập trình Java Chuyên gia Java doanh nghiệp Dennis Sosnoski đưa ra tin số dẻo về định dạng lớp nhị phân Java và những gì xảy ra với các lớp bên trong JVM Trong bài này, ông còn trình bày các vấn đề nạp lớp nằm trong phạm vi từ số lượng các lớp cần thiết

để chạy một ứng dụng Java đơn giản đến các xung đột trình nạp lớp, mà chúng có thể gây ra các vấn đề trong J2EE và các kiến trúc phức tạp tương tự

Bài viết này mở đầu một loạt bài viết mới trình bày một họ các chủ đề mà tôi gọi

là động lực học lập trình Java Các chủ đề gồm từ cấu trúc cơ sở của định dạng

tệp lớp nhị phân Java, thông qua truy cập siêu dữ liệu trong thời gian chạy bằng cách sử dụng sự phản chiếu, tất cả các cách để sửa đổi và xây dựng các lớp mới trong thời gian chạy Các chủ đề chung chạy xuyên suốt tất cả tài liệu này là ý tưởng trong đó việc lập trình nền tảng Java là năng động nhiều hơn làm việc với các ngôn ngữ biên dịch thẳng với mã gốc Nếu bạn hiểu những khía cạnh năng động này, bạn có thể làm nhiều thứ với lập trình Java mà không thể khớp với bất

kì ngôn ngữ lập trình chủ đạo nào khác

Trong bài viết này, tôi trình bày một số các khái niệm cơ bản làm nền tảng cho các tính năng động này của nền tảng Java Các khái niệm này xoay quanh định dạng nhị phân được sử dụng để biểu diễn các lớp Java, gồm cả những gì sẽ xảy ra khi những lớp này được nạp vào trong JVM Tài liệu này không chỉ cung cấp một nền móng cho phần còn lại của các bài viết trong loạt này, mà nó còn giải thích một số các mối quan tâm rất thiết thực cho các nhà phát triển đang làm việc trên nền tảng Java

Một lớp dưới dạng mã nhị phân

Các nhà phát triển đang làm việc trong ngôn ngữ Java thường không phải bận tâm đến các chi tiết về những gì xảy ra với mã nguồn của họ khi nó được chạy qua trình biên dịch Trong loạt bài này, tôi sắp trình bày rất nhiều các chi tiết ở hậu trường liên quan đến việc sẽ xảy ra từ mã nguồn để thực hiện chương trình, tuy nhiên, tôi sẽ bắt đầu xem xét tại các lớp nhị phân do một trình biên dịch tạo ra Định dạng lớp nhị phân trên thực tế được đặc tả JVM xác định Bình thường, từ

mã nguồn của ngôn ngữ Java, một trình biên dịch tạo ra các cách biểu diễn các lớp

Trang 2

này và chúng thường được lưu trữ trong các tệp có phần mở rộng class Tuy vậy,

cả hai tính năng này không cần thiết Các ngôn ngữ lập trình khác đã được phát triển có sử dụng định dạng lớp nhị phân Java và với một số mục đích, các cách biểu diễn lớp mới được xây dựng và ngay lập tức được nạp trong lúc thực hiện JVM Đối với JVM , phần quan trọng không phải là mã nguồn hoặc cách nó được lưu trữ, mà là chính định dạng của nó

Vì vậy, trên thực tế lớp này định dạng trông như thế nào? Liệt kê 1 đưa ra các mã nguồn cho một lớp (rất) ngắn, cùng với một phần hiển thị hệ đếm mười sáu của kết quả tệp lớp của trình biên dịch:

Liệt kê 1 Mã nguồn và (một phần) mã nhị phân cho tệp Hello.java

public class Hello

{

public static void main(String[] args) {

System.out.println("Hello, World!");

}

}

0000: cafe babe 0000 002e 001a 0a00 0600 0c09

0010: 000d 000e 0800 0f0a 0010 0011 0700 1207

0020: 0013 0100 063c 696e 6974 3e01 0003 2829 .<init> ()

0030: 5601 0004 436f 6465 0100 046d 6169 6e01 V Code main

0040: 0016 285b 4c6a 6176 612f 6c61 6e67 2f53 ([Ljava/lang/S

0050: 7472 696e 673b 2956 0c00 0700 0807 0014 tring;)V

0060: 0c00 1500 1601 000d 4865 6c6c 6f2c 2057 .Hello, W

Trang 3

0070: 6f72 6c64 2107 0017 0c00 1800 1901 0005 orld!

0080: 4865 6c6c 6f01 0010 6a61 7661 2f6c 616e Hello java/lan

0090: 672f 4f62 6a65 6374 0100 106a 6176 612f g/Object java/

00a0: 6c61 6e67 2f53 7973 7465 6d01 0003 6f75 lang/System ou

Bên trong mã nhị phân

Điều đầu tiên trong việc biểu diễn lớp nhị phân được thể hiện trong Liệt kê 1 là chữ kí "quán cafe babe" để xác nhận định dạng lớp nhị phân Java (và ngẫu nhiên dùng như là một nhưng phần lớn không được thừa nhận chứng cứ bền vững

cho những người pha cà phê (baristas) làm việc chăm chỉ, những người giữ tinh

thần cho các nhà phát triển đang xây dựng nền tảng Java) Chữ kí này là một cách

dễ dàng để xác minh rằng một khối dữ liệu thực sự đưa ra yêu cầu là một cá thể

của định dạng lớp Java Mỗi lớp nhị phân Java, thậm chí một lớp không có trên hệ thống tệp tin đó, cần bắt đầu bằng bốn byte này

Đừng bỏ lỡ phần còn lại của loạt bài này

Phần 2, "Giới thiệu sự phản chiếu" (June 2003)

Phần 3, "Ứng dụng sự phản chiếu" (07.2003)

Phần 4, "Chuyển đổi lớp bằng Javassist" (09.2003)

Phần 5, "Việc chuyển các lớp đang hoạt động" (02.2004)

Phần 6, "Các thay đổi hướng-khía cạnh với Javassist" (03.2004)

Phần 7, "Kỹ thuật bytecode với BCEL" (04.2004)

Phần 8, "Thay thế sự phản chiếu bằng việc tạo mã" (06.2004)

Phần còn lại của dữ liệu ít thú vị Sau chữ kí là một cặp các số phiên bản định dạng lớp (trong trường hợp này, phiên bản phụ 0 và phiên bản chính 46 hệ đếm mười sáu 0x2e như được tạo ra bởi các javac 1.4.1), sau đó một số đếm các lối

Trang 4

vào trong nhóm hằng số Số đếm lối vào (trong trường hợp 26 này hoặc 0x001a) tiếp theo là dữ liệu của nhóm hằng số thực sự Đây là nơi lưu trữ tất cả các hằng số được sử dụng bởi các định nghĩa lớp Nó gồm lớp và các tên phương thức, các chữ

kí và các chuỗi (mà bạn có thể nhận ra trong phần giải thích bằng văn bản ở bên phải của kết quả theo hệ đếm mười sáu), cùng với các giá trị nhị phân khác nhau

Các mục trong nhóm hằng số có chiều dài thay đổi, với byte đầu tiên của mỗi mục xác định kiểu mục và cách giải mã nó Ta sẽ không đi vào chi tiết của tất cả thứ có

ở đây có rất nhiều tài liệu tham khảo có sẵn nếu bạn quan tâm, bắt đầu với đặc

tả JVM thực sự Điểm mấu chốt là nhóm hằng số có tất cả các tham chiếu đến các lớp khác và các phương thức được lớp này sử dụng, cùng với những định nghĩa thực sự cho lớp này và các phương thức của nó Nhóm hằng số có thể dễ dàng chiếm một nửa hoặc phần lớn hơn của kích thước lớp nhị phân, mặc dù tỷ lệ trung bình có lẽ là thấp hơn

Tiếp theo nhóm hằng số là một vài mục tham chiếu các lối vào nhóm hằng số cho chính lớp đó, siêu lớp và các giao diện của nó Các mục này được kế tiếp bởi các thông tin về các trường và các phương thức, chính chúng được biểu diễn như là các cấu trúc phức tạp Các mã thực hiện cho các phương thức được trình bày dưới

dạng các thuộc tính mã được chứa trong các định nghĩa phương thức Mã này có trong dạng hướng dẫn cho JVM, thường được gọi là mã byte (bytecode), đây là

một trong những chủ đề cho phần tiếp theo

Hỏi chuyên gia: Dennis Sosnoski về các vấn đề JVM và bytecode

Đối với các ý kiến hay các câu hỏi về tài liệu được trình bày trong loạt bài này, cũng như bất cứ điều gì khác có liên quan đến Java bytecode, định dạng lớp nhị phân Java hoặc các vấn đề JVM chung, hãy truy cập vào diễn đàn thảo luận JVM

và Bytecode, do Dennis Sosnoski kiểm soát

Các thuộc tính được sử dụng cho một số các mục đích đã xác định trong định dạng

lớp Java, bao gồm cả bytecode đã nói trên, giá trị hằng số cho các trường, xử lý ngoại lệ và thông tin gỡ rối Dẫu sao các mục đích này không phải chỉ có khả năng

sử dụng cho các thuộc tính Từ đầu, đặc tả JVM đã yêu cầu các JVM bỏ qua các thuộc tính của các kiểu không rõ Yêu cầu này đưa ra tính linh hoạt để cho việc

mở rộng sử dụng các thuộc tính để phục vụ cho các mục đích khác trong tương lai, như là việc cung cấp siêu-thông tin được các khung công tác cần để làm việc với các lớp của người sử dụng một cách tiếp cận mà các ngôn ngữ C# có nguồn gốc Java đã sử dụng rộng rãi Thật không may, vẫn chưa có các kết nối nào được cung cấp để tiến hành sử dụng tính linh hoạt này ở mức người dùng

Trang 5

Bytecode và các ngăn xếp

Bytecode chiếm phần thực thi của tệp lớp trên thực tế là mã máy cho một loại máy

tính đặc biệt JVM Máy này được gọi là một máy ảo vì nó được thiết kế để thực

hiện trong phần mềm hơn là phần cứng Mỗi JVM được sử dụng để chạy các ứng dụng nền tảng Java được xây dựng xung quanh một việc triển khai thực hiện của máy này

Máy ảo này thực sự khá đơn giản Nó sử dụng một kiến trúc ngăn xếp, có nghĩa là các toán hạng lệnh được nạp vào một ngăn xếp bên trong trước khi chúng được sử dụng Tập lệnh gồm tất cả các phép toán số học và logic bình thường, cùng với các nhánh có điều kiện và không điều kiện, nạp/lưu trữ, gọi/trả về, thao tác ngăn xếp

và một số các kiểu lệnh đặc biệt Một số lệnh gồm các giá trị toán hạng cụ thể được mã hóa trực tiếp vào trong các lệnh Những cái khác trực tiếp tham chiếu các giá trị từ nhóm hằng số

Mặc dù máy ảo đơn giản, những việc thực hiện không nhất thiết phải như vậy Các JVM ban đầu (thế hệ đầu tiên) về cơ bản đã là các trình dịch cho bytecode của máy ảo Trên thực tế chúng tương đối đơn giản, nhưng bị mắc phải vấn đề về hiệu năng nghiêm trọng ; việc thông dịch mã luôn luôn sẽ mất nhiều thời gian hơn so với thực hiện mã gốc Để giảm các vấn đề hiệu năng này, các JVM thế hệ thứ hai

được bổ sung việc dịch ngay (JIT) Kỹ thuật JIT biên dịch bytecode Java thành mã

gốc trước khi thực hiện nó cho lần đầu tiên, cho phép hiệu năng tốt hơn với nhiều lần thực hiện lặp lại Các JVM thế hệ hiện nay thậm chí còn đi xa hơn nữa, bằng cách sử dụng các kỹ thuật thích nghi để theo dõi việc thực hiện chương trình và chọn lựa tối ưu hóa các mã sử dụng nhiều

Nạp các lớp

Các ngôn ngữ như C và C++ biên dịch thành mã gốc thường đòi hỏi một bước liên kết sau khi mã nguồn được biên dịch Quá trình liên kết này kết hợp mã từ các tệp

mã nguồn đã biên dịch tách biệt nhau, cùng với mã thư viện dùng chung, để tạo thành một chương trình thực hiện Ngôn ngữ Java có khác Với ngôn ngữ Java, các lớp được trình biên dịch tạo ra nói chung vẫn giữ nguyên như chúng có cho đến khi chúng đang nạp vào một JVM Ngay cả việc xây dựng một tệp JAR từ các tệp lớp không thay đổi điều này JAR chỉ là một thùng chứa cho các tệp lớp đó

Trang 6

Thay vì là một bước riêng rẽ, việc liên kết các lớp là một phần của một công việc được JVM thực hiện khi nó nạp chúng vào trong bộ nhớ Điều này bổ sung thêm chi phí hoạt động khi các lớp được nạp lúc đầu, nhưng cũng tạo ra một mức linh hoạt cao hơn cho các ứng dụng Java Ví dụ, các ứng dụng có thể được viết để sử dụng các giao diện với các việc thực hiện thực sự mà chúng đã để lại không xác

định cho đến khi đang chạy Cách tiếp cận liên kết cuối này để lắp ráp một ứng

dụng được sử dụng rộng rãi trong nền tảng Java, với các servlet đang là một ví dụ phổ biến

Các quy tắc để nạp các lớp được giải thích một cách chi tiết trong đặc tả JVM Nguyên tắc cơ bản là các lớp chỉ được nạp khi cần thiết (hoặc ít nhất xuất hiện để được nạp theo cách này JVM có một số tính linh hoạt trong việc nạp thực tế, nhưng phải duy trì trình tự khởi tạo lớp cố định) Mỗi lớp đã nạp có thể có các lớp khác mà nó phụ thuộc vào, do đó, quá trình nạp là quá trình đệ quy Các lớp trong Liệt kê 2 cho thấy việc nạp đệ quy này hoạt động như thế nào Lớp Demo gồm một phương thức main (chính) đơn giản để tạo ra một cá thể Greeter Người đón khách) và gọi phương thức greet Hàm tạo Greeter tạo ra một cá thể Message (Thông báo), nó sau đó sử dụng trong cuộc gọi phương thức greet

Liệt kê 2 Mã nguồn với chứng thực việc nạp lớp

public class Demo

{

public static void main(String[] args) {

System.out.println("**beginning execution**");

Greeter greeter = new Greeter();

System.out.println("**created Greeter**");

greeter.greet();

}

}

Trang 7

public class Greeter

{

private static Message s_message = new Message("Hello, World!");

public void greet() {

s_message.print(System.out);

}

}

public class Message

{

private String m_text;

public Message(String text) {

m_text = text;

}

public void print(java.io.PrintStream ps) {

ps.println(m_text);

}

}

Trang 8

Thiết lập tham số -verbose:class trên dòng lệnh java in ra dấu vết của quá trình nạp lớp Liệt kê 3 cho thấy một phần kết quả từ lúc chạy chương trình của Liệt kê 2 với tham số này:

Liệt kê 3 Một phần kết quả của -verbose:class

[Opened /usr/java/j2sdk1.4.1/jre/lib/rt.jar]

[Opened /usr/java/j2sdk1.4.1/jre/lib/sunrsasign.jar]

[Opened /usr/java/j2sdk1.4.1/jre/lib/jsse.jar]

[Opened /usr/java/j2sdk1.4.1/jre/lib/jce.jar]

[Opened /usr/java/j2sdk1.4.1/jre/lib/charsets.jar]

[Loaded java.lang.Object from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]

[Loaded java.io.Serializable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]

[Loaded java.lang.Comparable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]

[Loaded java.lang.CharSequence from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]

[Loaded java.lang.String from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]

[Loaded java.security.Principal from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]

[Loaded java.security.cert.Certificate

from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]

[Loaded Demo]

**beginning execution**

[Loaded Greeter]

[Loaded Message]

Trang 9

**created Greeter**

Hello, World!

[Loaded java.util.HashMap$KeySet

from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]

[Loaded java.util.HashMap$KeyIterator

from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]

Đây chỉ là một phần danh sách của các phần quan trọng nhất dấu vết đầy đủ gồm 294 dòng, mà với danh sách này tôi đã xóa hầu hết Tập đầu tiên của việc nạp lớp (trong trường hợp này là 279) tất cả được kích hoạt bằng nỗ lực để nạp lớp Demo Đây là các lớp cốt lõi được tất cả các chương trình Java sử dụng, không kể nhỏ bao nhiêu Ngay cả việc loại bỏ tất cả các mã khỏi phương thức Demo main không ảnh hưởng đến trình tự nạp ban đầu này Tuy vậy, số lượng và tên của các lớp được bao hàm sẽ khác giữa một phiên bản của thư viện lớp này với một phiên bản của thư viện lớp khác

Phần liệt kê sau khi lớp Demo được nạp sẽ thú vị hơn Trình tự ở đây cho thấy lớp Greeter được nạp sẽ thú vị hơn Trình tự ở đây cho thấy lớp Greeter sử dụng một

cá thể tĩnh của lớp Message, nên trước khi một cá thể của lớp trước có thể được tạo ra, các lớp sau này cũng cần phải được nạp

Có nhiều việc xảy ra bên trong JVM khi một lớp được nạp và được khởi tạo, bao gồm việc giải mã định dạng lớp nhị phân, kiểm tra tính tương thích với các lớp khác, xác minh chuỗi các phép toán bytecode và cuối cùng là xây dựng một cá thể java.lang.Class để biểu diễn lớp mới Đối tượng Class này trở thành cơ sở cho tất

cả các cá thể của lớp mới được JVM tạo ra Đó cũng là trình định danh cho lớp đã

tự nạp bạn có thể có nhiều bản sao của cùng một lớp nhị phân đã nạp trong một JVM, mỗi bản sao có cá thể Class riêng của nó Mặc dù tất cả các bản sao này chia

sẻ cùng một tên lớp, nhưng chúng sẽ là các lớp riêng biệt với JVM

Tắt đường dẫn (lớp) theo lối mòn (beaten)

Các trình nạp lớp (class loaders) điều khiển lớp đang nạp vào một JVM Có một trình nạp lớp tự mồi (bootstrap) được xây dựng bên trong JVM, nó có trách nhiệm

nạp các lớp thư viện của lớp Java cơ bản Trình nạp lớp đặc biệt này có một số

Trang 10

tính năng đặc biệt Với một điều là, nó chỉ nạp các lớp được tìm thấy trên đường dẫn lớp khởi động Bởi vì đó là những lớp hệ thống tin cậy, trình nạp bootstrap bỏ qua phần lớn việc xác nhận hợp lệ được thực hiện cho các lớp bình thường (không tin cậy)

Bootstrap không phải là trình nạp lớp duy nhất Đối với người mới bắt đầu, một

JVM định nghĩa một trình nạp lớp mở rộng (extension) để nạp các lớp từ các API

mở rộng Java tiêu chuẩn và lớp hệ thống (system) để nạp các lớp từ đường dẫn lớp

chung (gồm cả các lớp ứng dụng của bạn) Các ứng dụng cũng có thể định nghĩa các trình nạp lớp riêng của chúng cho các mục đích đặc biệt (chẳng hạn như nạp lại các lớp trong thời gian chạy) Các trình nạp lớp được thêm vào như vậy có nguồn gốc từ lớp java.lang.ClassLoader (có thể gián tiếp), lớp này cung cấp sự hỗ trợ cốt lõi để xây dựng một sự biểu diễn lớp trong một cá thể java.lang.Class từ một mảng các byte Mỗi lớp được xây dựng theo một số ý nghĩa "có sở hữu" bằng trình nạp lớp đã nạp nó Các trình nạp lớp thường giữ bản đồ các lớp mà chúng đã nạp, để có khả năng tìm một lớp theo tên nếu nó được yêu cầu lại

Mỗi trình nạp lớp cũng giữ một tham chiếu đến một trình nạp lớp cha mẹ, khi xác định một cây của các trình nạp lớp với trình nạp bootstrap tại gốc Khi cần một cá thể của một lớp đặc biệt (được xác định theo tên), bất cứ trình nạp lớp nào lúc đầu

xử lý yêu cầu thường kiểm tra trình nạp lớp cha mẹ của nó đầu tiên trước khi cố gắng nạp lớp trực tiếp Điều này áp dụng theo cách đệ quy nếu có nhiều tầng của

các trình nạp lớp, sao cho nó có nghĩa là một lớp thường sẽ nhìn thấy không chỉ

trong trình nạp lớp đã nạp nó, mà còn cho tất cả các trình nạp lớp con cháu Nó cũng có nghĩa là nếu một lớp có thể được nạp bởi nhiều hơn một trình nạp lớp theo một chuỗi, một lớp xa nhất lên đến cây đó sẽ là một lớp nạp nó thực sự

Có rất nhiều trường hợp ở đó các trình nạp lớp (classloaders) của nhiều ứng dụng được các chương trình Java sử dụng Một ví dụ là trong khung công tác J2EE Mỗi ứng dụng J2EE được nạp bởi khung công tác cần phải có một trình nạp lớp riêng

để ngăn chặn các lớp trong một ứng dụng khỏi cản trở các ứng dụng khác Mã của khung công tác tự nó sẽ sử dụng một hoặc nhiều trình nạp lớp khác, cũng để ngăn chặn sự cản trở đến hoặc từ các ứng dụng Tập các trình nạp lớp hoàn chỉnh tạo nên một hệ thống phân cấp có cấu trúc cây với các kiểu khác nhau của các lớp ở mỗi cấp

Các cây của các trình nạp

Như ví dụ của một hệ thống phân cấp của một trình nạp lớp trong lúc hành động, Hình 1 cho thấy hệ thống phân cấp của một trình nạp lớp được máy servlet

Tomcat xác định Ở đây trình nạp lớp Chung (Common) nạp từ các tệp JAR trong một thư mục cụ thể của quá trình cài đặt Tomcat dành cho những mã dùng chung giữa máy chủ và tất cả các ứng dụng Web Trình nạp Catalina dành cho các lớp

Ngày đăng: 07/08/2014, 10:22

HÌNH ẢNH LIÊN QUAN

Hình 2. Biến động nhận dạng lớp - Động lực học lập trình Java, Phần 1: Các lớp Java và việc nạp các lớp Quan sát các lớp và những gì xảy ra khi chúng được một JVM nạp docx
Hình 2. Biến động nhận dạng lớp (Trang 12)

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w