JavaRush /Blog Java /Random-VI /Cách các lớp được tải trong JVM
Aleksandr Zimin
Mức độ
Санкт-Петербург

Cách các lớp được tải trong JVM

Xuất bản trong nhóm
Sau khi hoàn thành phần khó khăn nhất trong công việc của lập trình viên và viết xong ứng dụng “Hello World 2.0”, tất cả những gì còn lại là lắp ráp bộ phân phối và chuyển nó cho khách hàng, hoặc ít nhất là đến dịch vụ thử nghiệm. Trong bản phân phối, mọi thứ đều diễn ra như bình thường và khi chúng tôi khởi chạy chương trình của mình, Máy ảo Java sẽ xuất hiện. Không có gì bí mật khi máy ảo đọc các lệnh được trình bày trong các tệp lớp dưới dạng mã byte và dịch chúng dưới dạng hướng dẫn tới bộ xử lý. Tôi đề nghị tìm hiểu một chút về sơ đồ mã byte vào máy ảo.

Trình nạp lớp

Nó được sử dụng để cung cấp mã byte đã biên dịch cho JVM, mã này thường được lưu trữ trong các tệp có phần mở rộng .class, nhưng cũng có thể được lấy từ các nguồn khác, chẳng hạn như được tải xuống qua mạng hoặc do chính ứng dụng tạo ra. Cách các lớp được tải trong JVM - 1Theo đặc tả Java SE, để mã chạy trong JVM, bạn cần hoàn thành ba bước:
  • tải mã byte từ tài nguyên và tạo một thể hiện của lớpClass

    điều này bao gồm tìm kiếm lớp được yêu cầu trong số những lớp được tải trước đó, lấy mã byte để tải và kiểm tra tính chính xác của nó, tạo một phiên bản của lớp Class(để làm việc với nó trong thời gian chạy) và tải các lớp cha. Nếu các lớp cha và giao diện chưa được tải thì lớp được đề cập sẽ được coi là chưa được tải.

  • ràng buộc (hoặc liên kết)

    Theo đặc điểm kỹ thuật, giai đoạn này được chia thành ba giai đoạn nữa:

    • Xác minh , tính chính xác của mã byte nhận được sẽ được kiểm tra.
    • Chuẩn bị , phân bổ RAM cho các trường tĩnh và khởi tạo chúng với các giá trị mặc định (trong trường hợp này, việc khởi tạo rõ ràng, nếu có, đã xảy ra ở giai đoạn khởi tạo).
    • Độ phân giải , độ phân giải của các liên kết tượng trưng của các loại, trường và phương thức.
  • khởi tạo đối tượng nhận được

    ở đây, không giống như các đoạn trước, mọi thứ dường như đã rõ ràng điều gì sẽ xảy ra. Tất nhiên, sẽ rất thú vị nếu hiểu chính xác điều này xảy ra như thế nào.

Tất cả các bước này được thực hiện tuần tự với các yêu cầu sau:
  • Lớp học phải được tải đầy đủ trước khi được liên kết.
  • Một lớp phải được kiểm tra và chuẩn bị đầy đủ trước khi nó được khởi tạo.
  • Lỗi phân giải liên kết xảy ra trong quá trình thực hiện chương trình, ngay cả khi chúng được phát hiện ở giai đoạn liên kết.
Như bạn đã biết, Java thực hiện tải các lớp một cách lười biếng (hoặc lười biếng). Điều này có nghĩa là việc tải các lớp của trường tham chiếu của lớp đã tải sẽ không được thực hiện cho đến khi ứng dụng gặp một tham chiếu rõ ràng đến chúng. Nói cách khác, việc giải quyết các liên kết tượng trưng là tùy chọn và không xảy ra theo mặc định. Tuy nhiên, việc triển khai JVM cũng có thể sử dụng tải lớp năng động, tức là tất cả các liên kết tượng trưng phải được tính đến ngay lập tức. Yêu cầu cuối cùng được áp dụng vào thời điểm này. Cũng cần lưu ý rằng việc giải quyết các liên kết tượng trưng không bị ràng buộc với bất kỳ giai đoạn tải lớp nào. Nói chung, mỗi giai đoạn trong số này tạo nên một nghiên cứu tốt; chúng ta hãy thử tìm ra giai đoạn đầu tiên, cụ thể là tải mã byte.

Các loại trình tải Java

Có ba trình tải tiêu chuẩn trong Java, mỗi trình tải một lớp từ một vị trí cụ thể:
  1. Bootstrap là một trình tải cơ bản, còn được gọi là Trình tải lớp nguyên thủy.

    tải các lớp JDK tiêu chuẩn từ kho lưu trữ rt.jar

  2. Trình tải lớp mở rộng – trình tải mở rộng.

    tải các lớp mở rộng, được đặt trong thư mục jre/lib/ext theo mặc định, nhưng có thể được thiết lập bởi thuộc tính hệ thống java.ext.dirs

  3. System ClassLoader – trình tải hệ thống.

    tải các lớp ứng dụng được xác định trong biến môi trường CLASSPATH

Java sử dụng một hệ thống phân cấp của các trình nạp lớp, trong đó phần gốc tất nhiên là phần cơ sở. Tiếp theo là trình tải mở rộng và sau đó là trình tải hệ thống. Đương nhiên, mỗi trình tải lưu trữ một con trỏ tới trình tải gốc để có thể ủy quyền tải cho nó trong trường hợp bản thân nó không thể thực hiện việc này.

Lớp trừu tượng ClassLoader

Mỗi bộ nạp, ngoại trừ bộ nạp cơ sở, đều là hậu duệ của lớp trừu tượng java.lang.ClassLoader. Ví dụ: việc triển khai trình tải mở rộng là lớp sun.misc.Launcher$ExtClassLoadervà trình tải hệ thống là sun.misc.Launcher$AppClassLoader. Trình tải cơ sở là gốc và việc triển khai nó được bao gồm trong JVM. Bất kỳ lớp nào mở rộng đều java.lang.ClassLoadercó thể cung cấp cách tải các lớp riêng với blackjack và những lớp tương tự. Để làm được điều này cần phải xác định lại các phương pháp tương ứng mà hiện tại tôi chỉ có thể xem xét một cách hời hợt, bởi vì Tôi không hiểu vấn đề này một cách chi tiết. Họ đây rồi:
package java.lang;
public abstract class ClassLoader {
    public Class<?> loadClass(String name);
    protected Class<?> loadClass(String name, boolean resolve);
    protected final Class<?> findLoadedClass(String name);
    public final ClassLoader getParent();
    protected Class<?> findClass(String name);
    protected final void resolveClass(Class<?> c);
}
loadClass(String name)một trong số ít các phương thức công khai, là điểm khởi đầu để tải các lớp. Việc triển khai nó tập trung vào việc gọi một phương thức được bảo vệ khác loadClass(String name, boolean resolve), cần được ghi đè. Nếu bạn xem Javadoc của phương thức được bảo vệ này, bạn có thể hiểu điều gì đó như sau: hai tham số được cung cấp làm đầu vào. Một là tên nhị phân của lớp (hoặc tên lớp đủ điều kiện) cần được tải. Tên lớp được chỉ định cùng với danh sách tất cả các gói. Tham số thứ hai là cờ xác định xem có cần phải phân giải liên kết tượng trưng hay không. Theo mặc định, nó là false , có nghĩa là việc tải lớp lười được sử dụng. Hơn nữa, theo tài liệu, trong quá trình triển khai mặc định của phương thức, một cuộc gọi được thực hiện findLoadedClass(String name)để kiểm tra xem lớp đã được tải trước đó chưa và nếu có sẽ trả về một tham chiếu đến lớp này. Nếu không, phương thức tải lớp của trình tải gốc sẽ được gọi. Nếu không có trình tải nào có thể tìm thấy lớp đã tải, thì mỗi trình tải trong số chúng, theo thứ tự ngược lại, sẽ cố gắng tìm và tải lớp đó, ghi đè lớp findClass(String name). Điều này sẽ được thảo luận chi tiết hơn trong chương “Kế hoạch nạp lớp”. Và cuối cùng nhưng không kém phần quan trọng, sau khi lớp được tải xong, tùy thuộc vào cờ phân giải , người ta sẽ quyết định có tải lớp qua liên kết tượng trưng hay không. Một ví dụ rõ ràng là giai đoạn Nghị quyết có thể được gọi trong giai đoạn tải lớp. Theo đó, bằng cách mở rộng lớp ClassLoadervà ghi đè các phương thức của nó, trình tải tùy chỉnh có thể triển khai logic riêng của nó để phân phối mã byte đến máy ảo. Java cũng hỗ trợ khái niệm trình nạp lớp "hiện tại". Trình tải hiện tại là trình tải lớp hiện đang thực thi. Mỗi lớp biết nó được tải bằng trình tải nào và bạn có thể lấy thông tin này bằng cách gọi String.class.getClassLoader(). Đối với tất cả các lớp ứng dụng, trình tải "hiện tại" thường là trình tải hệ thống.

Ba nguyên tắc tải lớp

  • Phái đoàn

    Yêu cầu tải lớp được chuyển đến trình tải chính và nỗ lực tải chính lớp đó chỉ được thực hiện nếu trình tải chính không thể tìm và tải lớp. Cách tiếp cận này cho phép bạn tải các lớp bằng trình tải càng gần với lớp cơ sở càng tốt. Điều này đạt được khả năng hiển thị lớp học tối đa. Mỗi trình tải giữ một bản ghi về các lớp được nó tải, đặt chúng vào bộ đệm của nó. Tập hợp các lớp này được gọi là phạm vi.

  • Hiển thị

    Trình tải chỉ nhìn thấy các lớp “của nó” và các lớp của “mẹ” và không biết về các lớp được “con” của nó tải.

  • Tính duy nhất

    Một lớp chỉ có thể được tải một lần. Cơ chế ủy quyền đảm bảo rằng trình tải khởi tạo quá trình tải lớp không làm quá tải lớp đã được tải trước đó vào JVM.

Vì vậy, khi viết bộ nạp khởi động của mình, nhà phát triển phải được hướng dẫn theo ba nguyên tắc này.

Sơ đồ tải lớp

Khi lệnh gọi tải một lớp xảy ra, lớp này sẽ được tìm kiếm trong bộ đệm của các lớp đã được tải của trình tải hiện tại. Nếu lớp mong muốn chưa được tải trước đó thì nguyên tắc ủy quyền sẽ chuyển quyền kiểm soát sang trình tải chính, nằm ở một cấp cao hơn trong hệ thống phân cấp. Trình tải gốc cũng cố gắng tìm lớp mong muốn trong bộ đệm của nó. Nếu lớp đã được tải và trình tải biết vị trí của nó thì một đối tượng Classcủa lớp đó sẽ được trả về. Nếu không, quá trình tìm kiếm sẽ tiếp tục cho đến khi đạt đến bộ nạp khởi động cơ sở. Nếu trình tải cơ sở không có thông tin về lớp được yêu cầu (nghĩa là nó chưa được tải), mã byte của lớp này sẽ được tìm kiếm ở vị trí của các lớp mà trình tải đã cho biết và nếu lớp đó không thể được tải, điều khiển sẽ quay trở lại trình tải con, trình tải này sẽ cố tải từ các nguồn đã biết với nó. Như đã đề cập ở trên, vị trí của các lớp dành cho trình tải cơ sở là thư viện rt.jar, đối với trình tải mở rộng - thư mục có phần mở rộng jre/lib/ext, đối với hệ thống một - CLASSPATH, đối với người dùng, nó có thể khác . Do đó, tiến trình tải các lớp đi theo hướng ngược lại - từ trình tải gốc đến trình tải hiện tại. Khi tìm thấy mã byte của lớp, lớp đó sẽ được tải vào JVM và thu được một thể hiện của loại đó Class. Như bạn có thể dễ dàng thấy, sơ đồ tải được mô tả tương tự như cách triển khai phương thức ở trên loadClass(String name). Dưới đây bạn có thể thấy sơ đồ này trong sơ đồ.
Cách các lớp được tải trong JVM - 2

Là một kết luận

Trong những bước đầu tiên học một ngôn ngữ, không có nhu cầu đặc biệt phải hiểu cách các lớp được tải trong Java, nhưng việc biết những nguyên tắc cơ bản này sẽ giúp bạn tránh được tuyệt vọng khi gặp phải các lỗi như ClassNotFoundExceptionhoặc NoClassDefFoundError. Chà, hoặc ít nhất là hiểu đại khái đâu là gốc rễ của vấn đề. Do đó, một ngoại lệ ClassNotFoundExceptionxảy ra khi một lớp được tải động trong quá trình thực thi chương trình, khi các trình tải không thể tìm thấy lớp được yêu cầu trong bộ đệm hoặc dọc theo đường dẫn lớp. Nhưng lỗi NoClassDefFoundErrornghiêm trọng hơn và xảy ra khi lớp được yêu cầu có sẵn trong quá trình biên dịch nhưng không hiển thị trong quá trình thực thi chương trình. Điều này có thể xảy ra nếu chương trình quên đưa thư viện mà nó sử dụng vào. Chà, thực tế là việc hiểu các nguyên tắc cấu trúc của công cụ mà bạn sử dụng trong công việc của mình (không nhất thiết phải hiểu rõ ràng và chi tiết về độ sâu của nó) sẽ làm tăng thêm sự hiểu biết rõ ràng về các quá trình xảy ra bên trong cơ chế này, trong đó, lần lượt, dẫn đến việc sử dụng công cụ này một cách tự tin.

Nguồn

Cách thức hoạt động của ClassLoader trong Java Nhìn chung, đây là một nguồn rất hữu ích với cách trình bày thông tin dễ tiếp cận. Đang tải các lớp, ClassLoader Một bài viết khá dài nhưng nhấn mạnh vào cách thực hiện triển khai trình tải của riêng bạn với các trình tải tương tự này. ClassLoader: tải động các lớp Thật không may, tài nguyên này hiện không có sẵn, nhưng ở đó tôi đã tìm thấy sơ đồ dễ hiểu nhất về sơ đồ tải lớp, vì vậy tôi không thể không thêm nó. Đặc tả Java SE: Chương 5. Tải, liên kết và khởi tạo
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION