Ngành IT hot, cái ngành mà phỏng vấn hỏi trên trời, dưới đất, hỏi còn khó hơn khi đi làm thực tế nữa, vào làm chỉ có CRUD. Cuộc chơi như vậy thì chấp nhận thôi, vòng vòng chắc cũng chỉ quanh quanh 30 câu hỏi. Ráng nhớ thôi, mai mốt mình làm người phỏng vấn, mỉnh hỏi lại các bạn. Kiến thức ngày nay dễ dàng tiếp cận hơn, đó là lợi thế.
Hôm nay mình tìm hiểu về Java Classloader.
Java Classloader là một bộ phận quan trọng của Java Runtime Environment (JRE), thuộc kiến trúc nội bộ của JVM (Java Virtual Machine). Nhiệm vụ cốt lõi của nó là nạp động (dynamically load) các file Class của Java (.class) vào bộ nhớ của JVM khi ứng dụng cần sử dụng đến chúng tại thời điểm runtime, thay vì nạp tất cả cùng một lúc khi khởi động.
Dưới đây là các cơ chế hoạt động, nguyên lý và các loại Classloader chính trong Java:
1. Quy trình hoạt động của Classloader
Khi một class được triệu gọi (ví dụ: qua từ khóa new hoặc gọi một phương thức static), JVM sẽ yêu cầu Classloader thực hiện qua 3 giai đoạn chính:
-
Loading (Nạp): Tìm kiếm file
.classtương ứng (trên đĩa cứng, qua mạng, hoặc trong file JAR) dựa trên tên package đầy đủ, sau đó chuyển đổi nó thành một mảng các byte dữ liệu và tạo ra một đối tượngjava.lang.Classtrong bộ nhớ Heap. -
Linking (Liên kết):
-
Verification (Xác thực): Đảm bảo cấu trúc file lớp tuân thủ các quy tắc an toàn của Java để không gây hại cho hệ thống.
-
Preparation (Chuẩn bị): Cấp phát bộ nhớ cho các biến static của class và gán giá trị mặc định (ví dụ: 0 cho int, null cho Object).
-
Resolution (Phân giải): Thay thế các tham chiếu ký hiệu (Symbolic References) trong class bằng các tham chiếu trực tiếp (Direct References) đến bộ nhớ.
-
-
Initialization (Khởi tạo): Thực thi các khối mã static (
static { ... }) và gán các giá trị thực tế cho các biến static mà lập trình viên đã chỉ định.
2. Ba loại Classloader mặc định trong Java
JVM sử dụng một hệ thống phân cấp gồm 3 Classloader mặc định, mỗi loại chịu trách nhiệm nạp class ở các vị trí khác nhau:
-
Bootstrap Classloader (Null Classloader): Là Classloader gốc, được viết bằng mã máy (C/C++) chứ không phải bằng Java. Nó chịu trách nhiệm nạp các class cốt lõi của hệ thống Java nằm trong các thư mục runtime (như
rt.jartrong các bản Java cũ hoặc các module hệ thống của JDK nhưjava.basetừ Java 9 trở đi). -
Platform Classloader (Trực thuộc Extension Classloader cũ): Chịu trách nhiệm nạp các class mở rộng của nền tảng Java hoặc các gói thư viện chuẩn đi kèm mà không thuộc phần cốt lõi.
-
Application Classloader (System Classloader): Chịu trách nhiệm nạp các class do bạn viết (Application Code) và các thư viện bên thứ ba nằm trong đường dẫn Classpath (hoặc
--module-path) của ứng dụng khi bạn chạy lệnh Java.
3. Nguyên lý hoạt động cơ bản (Delegation Principle)
Classloader trong Java hoạt động theo Nguyên lý ủy quyền (Delegation Model). Khi một Classloader nhận được yêu cầu nạp một lớp, quy trình sẽ diễn ra như sau:
-
Nó sẽ không tự tìm kiếm trước, mà ủy quyền (delegate) yêu cầu đó lên cho Classloader cha của nó.
-
Quá trình ủy quyền này tiếp tục đi ngược lên trên cho đến khi chạm tới đỉnh là Bootstrap Classloader.
-
Nếu Classloader cha tìm thấy lớp, lớp đó sẽ được nạp và trả về.
-
Nếu Classloader cha không tìm thấy, yêu cầu sẽ được trả ngược lại cho Classloader con bên dưới để tự tìm kiếm trong phạm vi của nó. Nếu tất cả đều không tìm thấy, hệ thống sẽ ném ra ngoại lệ
ClassNotFoundExceptionhoặcNoClassDefFoundError.
Tại sao lại cần cơ chế này?
-
Bảo mật: Tránh việc mã nguồn độc hại ghi đè lên các class cốt lõi của hệ thống. Ví dụ, nếu ai đó tự viết một lớp tên là java.lang.String để đánh cắp dữ liệu, cơ chế ủy quyền sẽ luôn ưu tiên gọi lớp java.lang.String chuẩn do Bootstrap Classloader nạp trước, khiến lớp giả mạo bị bỏ qua.
-
Tránh trùng lặp: Đảm bảo một class duy nhất chỉ được nạp một lần vào JVM trong cùng một không gian tên.
4. Ứng dụng thực tế và Custom Classloader
Trong các dự án thực tế, đặc biệt là các phần mềm doanh nghiệp, lập trình viên có thể kế thừa lớp java.lang.Classloader để tạo ra các Custom Classloader riêng. Kỹ thuật này rất phổ biến trong:
-
Web Containers (Tomcat, Jetty): Giúp nạp các ứng dụng web độc lập nhau. Mỗi ứng dụng web (WAR) có một Classloader riêng để các thư viện của ứng dụng này không bị xung đột phiên bản với ứng dụng khác dù chạy chung một JVM.
-
Hot-swapping / Plugin Architecture: Cho phép nạp lại (reload) hoặc cập nhật một tính năng, một class mới vào hệ thống đang chạy mà không cần phải restart lại server.
-
Mã hóa Class: Đọc các file
.classđã bị mã hóa từ ổ đĩa, dùng Custom Classloader để giải mã ngược lại thành mảng byte trong bộ nhớ trước khi nạp vào JVM để bảo vệ bản quyền mã nguồn.
1. Classloader Workflow
When a class is invoked (for instance, via the new keyword or a static method call), the JVM requests the Classloader to execute the process through three main phases:
-
Loading: Finds the corresponding
.classfile (on the hard drive, via network, or within a JAR file) based on its fully qualified package name. It then converts it into a byte array and creates ajava.lang.Classobject in the Heap memory. -
Linking:
-
Verification: Ensures that the class file structure complies with Java's safety regulations so it won't harm the system.
-
Preparation: Allocates memory for the static variables of the class and assigns default values (e.g.,
0for int,nullfor Object). -
Resolution: Replaces symbolic references in the class with direct references to the actual memory locations.
-
-
Initialization: Executes static blocks of code (
static { ... }) and assigns the actual values specified by the developer to the static variables.
2. Three Default Classloader Types in Java
The JVM utilizes a hierarchy consisting of three default Classloaders, each responsible for loading classes from different locations:
-
Bootstrap Classloader (Null Classloader): The root Classloader, written in native code (C/C++) rather than Java. It is responsible for loading core Java runtime classes located in system paths (such as rt.jar in older Java versions or JDK system modules like java.base from Java 9 onwards).
-
Platform Classloader (Replaced the old Extension Classloader): Responsible for loading platform extension classes or bundled standard libraries that are not part of the core runtime.
-
Application Classloader (System Classloader): Responsible for loading your application code and third-party libraries defined in the Classpath (or
--module-path) when you run the Java application.
3. Core Operational Principle: The Delegation Model
Classloaders in Java operate based on the Delegation Model. When a Classloader receives a request to load a class, the process unfolds as follows:
-
It does not look for the class itself first; instead, it delegates the request up to its parent Classloader.
-
This delegation process continues upward until it reaches the top of the hierarchy, the Bootstrap Classloader.
-
If a parent Classloader finds the class, that class is loaded and returned.
-
If the parent Classloaders cannot find it, the request is passed back down to the child Classloader to search within its own scope. If none of them find the class, the system throws a ClassNotFoundException or a NoClassDefFoundError.
Why is this mechanism necessary?
-
Security: It prevents malicious code from overwriting core Java system classes. For example, if someone writes a custom class named java.lang.String to steal data, the delegation mechanism will always prioritize the standard java.lang.String loaded by the Bootstrap Classloader, ignoring the rogue asset.
-
Uniqueness: It ensures that a unique class is loaded into the JVM only once within the same namespace.
4. Practical Applications and Custom Classloaders
In production environments, particularly in enterprise software, developers can extend the java.lang.ClassLoader class to create Custom Classloaders. This technique is widely used in:
-
Web Containers (Tomcat, Jetty): Helps load web applications independently. Each web application (WAR) has its own Classloader, ensuring that library versions do not conflict with one another despite sharing the same JVM.
-
Hot-swapping / Plugin Architecture: Allows the system to reload or update a feature or a new class on the fly while the application is running, without requiring a server restart.
-
Class Encryption: Reads encrypted
.classfiles from the disk, uses a Custom Classloader to decrypt them into a byte array in memory, and then loads them into the JVM to protect source code intellectual property.