class loaderjavajvm

Kiến trúc JVM & Class Loading - Từ Source Code đến Class

Khám phá kiến trúc của Java Virtual Machine (JVM) và tìm hiểu cách Class Loader Subsystem nạp class một cách động. Hiểu vai trò của Bootstrap, Platform, Application ClassLoader, Parent Delegation Model và toàn bộ quy trình nạp class trong các ứng dụng Java.

14 thg 7, 202616 min readCập nhật 17 thg 7, 2026
Kiến trúc JVM & Class Loading - Từ Source Code đến Class

Tổng quan

Trong hệ sinh thái Java, việc hiểu rõ JVM và Java Memory Model (JMM) không chỉ giúp bạn viết code chính xác — mà còn giúp bạn hiểu sâu hơn về cách Java vận hành ở cấp độ cốt lõi. JVM không phải là một "hộp đen": nó quản lý bộ nhớ, nạp class, thực thi bytecode và tối ưu hiệu năng thông qua Interpreter, JIT Compiler và Garbage Collector.

JMM đảm bảo tính nhất quán và khả năng hiển thị của bộ nhớ giữa các thread, giúp Java tránh race condition và nhiều lỗi concurrency tinh vi khác.

Trong bài viết này, bạn sẽ tìm hiểu chi tiết về kiến trúc JVM, tập trung cụ thể vào cách JVM nạp và quản lý class. Nền tảng này sẽ giúp bạn hiểu rõ hơn về hiệu năng ứng dụng, tối ưu hệ thống và debug các vấn đề liên quan đến Class Loading hiệu quả hơn.

JVM Core Architect

Để thực sự hiểu JVM làm gì, bạn cần nhìn nó không phải như một "hộp đen chạy Java", mà là một runtime system dựa trên các component, bao gồm ba thành phần cốt lõi:

  • Class Loader System ← Trọng tâm của bài viết này
  • Runtime Data Areas (Heap, Stack, Meta/Perm)
  • Execution Engine (Interpreter + JIT Compiler)

Class Loader Subsystem

Class Loader Subsystem là gì?

Class Loader Subsystem là cơ chế cho phép JVM nạp class vào bộ nhớ theo nhu cầu. Đây là nền tảng cốt lõi tạo nên tính linh hoạt, khả năng Dynamic Loading và Hot Deployment của Java.

Khi một class được JVM sử dụng, nó không thể thực thi trực tiếp file .class từ ổ đĩa mà trước tiên phải nạp file đó vào bộ nhớ vì những lý do sau:

  • VM cần một biểu diễn có cấu trúc của class trong bộ nhớ để có thể thực thi. File .class là một binary artifact tuân theo định dạng ClassFile, cần được JVM chuyển đổi thành các cấu trúc runtime nội bộ, bao gồm:

    • metadata của class (tên class, superclass, các interface được implement...)
    • constant pool đã được parse (danh sách field và method)
    • bytecode của từng method
    • thông tin access flags
    • runtime metadata cần thiết cho Reflection
    • memory layout được sử dụng để cấp phát object trên Heap
  • Hiệu năng thực thi: nếu JVM phải đọc file class từ ổ đĩa mỗi khi một method được gọi, hiệu năng sẽ sụt giảm nghiêm trọng. Sau khi được nạp vào JVM, class có thể:

    • được cache trong Method Area / Metaspace
    • được tối ưu bởi JIT Compiler
    • được truy cập ngay lập tức bởi Reflection API
    • được GC sử dụng để quản lý các object được khởi tạo từ class
    • gọi method của nhau mà không cần truy cập ổ đĩa nhiều lần
  • Hỗ trợ các tính năng runtime như:

    • Reflection
    • Dynamic Proxy
    • JIT Compilation
    • Annotation
    • Class Redefinition (Hot Swap trong quá trình debug)
    • CDI / Spring DI (Class Scanning và Metadata Loading)

Vì những lý do và tính năng nêu trên, các class cần phải tồn tại trong bộ nhớ để JVM và framework có thể đọc cũng như thao tác trên metadata của chúng.

Vậy tại sao JVM lại nạp class theo nhu cầu thay vì nạp toàn bộ class ngay từ đầu rồi chỉ việc sử dụng?

JVM sử dụng mô hình Lazy Loading (Load-on-Demand) để tối ưu việc sử dụng tài nguyên và thời gian khởi động ứng dụng. Thiết kế này tồn tại vì ba lý do chính:

  • Tối ưu bộ nhớ — tránh tiêu tốn RAM không cần thiết. Ứng dụng Java có thể chứa:

    • hàng nghìn class
    • hàng trăm file JAR
    • các framework lớn như Spring Boot và Hibernate

    Trong thực tế, không phải tất cả class trong một ứng dụng Java đều thực sự được sử dụng khi runtime. JVM chỉ nạp một class khi cần thiết, ví dụ:

    • khi tạo một object instance mới
    • khi gọi static method
    • khi truy cập field
    • trong quá trình Reflection Lookup
    • khi JIT Compiler tối ưu execution path

    Nếu toàn bộ class của framework và các file JAR đều được nạp vào JVM khi startup, RAM sẽ giống như một nhà kho bị chất đầy quá mức — phình to và chứa mọi thứ cùng một lúc. Khi đó, các vấn đề sau sẽ xảy ra:

    • Metaspace sẽ tăng lên không cần thiết
    • Thời gian startup sẽ tăng đáng kể
    • Cả tài nguyên bộ nhớ và CPU đều bị lãng phí
  • Tối ưu thời gian khởi động

    Hãy tưởng tượng JVM phải nạp 30.000 class khi khởi động một ứng dụng Spring Boot — thời gian startup sẽ tăng lên đáng kể. Lazy Loading giúp bằng cách:

    • khởi động JVM rất nhanh
    • chỉ nạp những class cần thiết cho lần thực thi ban đầu
    • tránh nạp không cần thiết những class hiếm khi được sử dụng

    Ví dụ: Bạn có thể có 100 REST endpoint, nhưng nếu hôm nay người dùng chỉ gọi 10 endpoint trong số đó, JVM chỉ cần nạp các class liên quan đến 10 endpoint đó.

  • Dynamic Loading

    Java được thiết kế để chạy các ứng dụng lớn, phân tán và có tính module, nơi hệ thống cần phát triển và mở rộng mà không phải dừng ứng dụng:

    • Nạp plugin khi runtime (runtime extensibility)

      • Bạn có thể thêm tính năng mới chỉ bằng cách đặt một file JAR vào thư mục plugin, không cần rebuild, không cần restart ứng dụng.
      • Ví dụ: Các công cụ như IntelliJ IDEA và Jenkins nạp plugin thông qua Dynamic Class Loading.
    • Thay đổi implementation thông qua interface mà không cần restart Bạn có thể:

      • chuyển database driver
      • thay đổi email service provider
      • thay thế template engine

      ... và ứng dụng vẫn tiếp tục chạy bình thường, vì JVM chỉ nạp một implementation khi class tương ứng thực sự được sử dụng.

      Tại đây, một câu hỏi thường gặp được đặt ra: tại sao Spring Boot lại yêu cầu restart khi thay đổi database configuration, email provider hoặc bean? Hành vi này phụ thuộc vào cách Spring Framework hoạt động, không phải bản thân JVM:

      code
        App → ClassLoader → Load classpath → Run
      • Khi bạn thay đổi driver hoặc chuyển implementation, class mới không tồn tại trong classpath cũ.
      • Không có cách nào để unload class cũ (JVM chủ động không cho phép điều này).
      • Spring không tạo một ClassLoader mới cho toàn bộ ứng dụng (ngoại trừ DevTools). → Restart là cách đơn giản và an toàn nhất.
    • Hot Deploy / Hot Swap

      • Nhiều nền tảng hỗ trợ Hot Deploy và Hot Swap, chẳng hạn như:
      • Spring DevTools
      • JRebel
      • Java Instrumentation API

      Các nền tảng này hoàn toàn dựa vào khả năng nạp class mới của JVM để thay thế class hiện có trong khi ứng dụng đang chạy.

    • Module System: OSGi, Java Platform Module System (JPMS) OSGi giới thiệu mô hình Layered ClassLoader, trong đó mỗi bundle có ClassLoader riêng, cho phép module:

      • được nạp và gỡ bỏ độc lập
      • được nâng cấp khi runtime (hot upgrade)
      • tránh dependency conflict (class shadowing / class hiding)
    • Nạp class từ network — triết lý ban đầu của Java (Applet) Trong lịch sử, Java Applet cho phép JVM tải class từ Internet và thực thi chúng ngay lập tức trong trình duyệt. Dynamic Loading vẫn là cơ chế nền tảng đứng sau khả năng này.

    Một ví dụ thực tế: Spring Boot Spring Boot gần như phụ thuộc hoàn toàn vào Dynamic Class Loading:

    • nạp bean khi chúng được tham chiếu hoặc khi Application Context được khởi tạo
    • nạp configuration dựa trên profile hoặc condition (@Conditional)
    • nạp dependency module theo môi trường runtime
    • nạp class từ thư viện bên ngoài (external JAR) thông qua nhiều ClassLoader

    Nếu Spring Boot buộc phải nạp toàn bộ class ngay từ đầu:

    • thời gian startup sẽ rất tệ
    • mức sử dụng bộ nhớ sẽ tăng đột biến
    • Hot Reload và Conditional Loading sẽ không còn khả thi

Mục tiêu của Class Loader:

  • Nạp các file .class chứa bytecode vào JVM và chuyển đổi chúng thành các đối tượng Class trong Heap.

    • Class Loader đọc các file .class (hoặc byte stream từ JAR, network hoặc các nguồn tùy chỉnh) và chuyển đổi chúng thành các đối tượng Class trong bộ nhớ để sử dụng trong quá trình thực thi.
  • Xây dựng dependency graph

    Khi một class được nạp, JVM sẽ tự động nạp:

    • superclass của nó

    • các interface mà nó implement

    • kiểu dữ liệu của các field

    • kiểu tham số và kiểu trả về của các method

      → hình thành một dependency graph giữa các class.

  • Cô lập namespace giữa các module

    Mỗi Class Loader có namespace riêng → hai class có cùng fully qualified name nhưng được nạp bởi hai Class Loader khác nhau sẽ được xem là hai class khác nhau. Điều này cho phép:

    • cô lập ứng dụng (các web application riêng biệt trên Tomcat)
    • kiến trúc plugin (OSGi)
    • reload class mà không ảnh hưởng đến các ứng dụng khác
  • Hỗ trợ sandbox và bảo mật bằng cách kiểm soát nguồn gốc của class

    Class Loader kiểm soát:

    • class được nạp từ đâu (file system, network hoặc custom source)

    • việc có được phép nạp class đó hay không

      → đảm bảo các ranh giới bảo mật của JVM.

  • Hỗ trợ Custom Class Loader (OSGi, Spring Boot, application server như Tomcat/JBoss)

    Developer có thể triển khai Custom Class Loader để:

    • nạp class từ database
    • sinh class động (ByteBuddy, ASM)
    • hỗ trợ hot reload (Spring Boot DevTools, Tomcat)
    • quản lý module (OSGi)

Các loại Class Loader:

  • Bootstrap ClassLoader
  • Extension/Platform ClassLoader
  • Application ClassLoader

Nguyên lý hoạt động:

  • JVM khởi động → Bootstrap Class Loader được khởi tạo ngầm bằng native code.
  • Bootstrap Class Loader tìm và nạp các class cốt lõi của JRE từ rt.jar hoặc Java Runtime Image.
  • Khi một class khác cần được nạp (ví dụ: java.sql.Connection), JVM trước tiên sẽ chuyển yêu cầu tới Bootstrap Class Loader. Nếu không tìm thấy class, yêu cầu sẽ tiếp tục được chuyển tới các Class Loader con (Extension, Application hoặc Custom Class Loader). Cơ chế này tuân theo Parent-First Delegation Model, đảm bảo rằng các class cốt lõi của JVM không thể bị ghi đè hoặc thay đổi bởi các Class Loader khác.

Class Loader Hierachy

Bootstrap Class Loader

Trong kiến trúc JVM, Bootstrap Class Loader (còn được gọi là Primordial Class Loader) là Class Loader nền tảng và ở cấp cao nhất, đóng vai trò là nền móng cho quá trình khởi động JVM. Đây là "viên gạch" đầu tiên thiết lập môi trường Java Runtime, bởi không có thành phần nào trong JVM có thể hoạt động cho đến khi các class cốt lõi được nạp.

Bootstrap Class Loader chịu trách nhiệm nạp các class cốt lõi của Java — cụ thể là những class thiết yếu để JVM hoạt động:

  • java.lang.Object — class gốc của mọi object trong Java
  • java.lang.String — class cốt lõi để xử lý chuỗi
  • Các class thuộc các package như java.lang, java.util, java.io và các package khác thuộc Java Runtime Environment (JRE)

Nói cách khác, mọi Class Loader khác và mọi class khác cuối cùng đều phụ thuộc vào các class được Bootstrap Class Loader nạp đầu tiên.

Các đặc điểm chính:

Đặc điểmGiải thích
Triển khai bằng native codeBootstrap Class Loader được triển khai bằng native code (thường là C/C++), không phải Java, vì JVM cần thành phần này tồn tại trước khi bất kỳ Java class nào được nạp.
Không có Parent Class LoaderĐây là Class Loader gốc của toàn bộ hệ thống và không có parent. Các Class Loader khác trong JVM tuân theo Parent Delegation Model, trong khi Bootstrap Class Loader nằm ở vị trí cao nhất.
Vị trí của các class được nạpJava ≤ 8: nạp class từ rt.jar nằm trong <JAVA_HOME>/jre/lib. Java ≥ 9: sử dụng Java Runtime Image (JRT) thông qua Module System thay vì một instance của java.lang.ClassLoader. Do đó, không thể lấy reference của Bootstrap Class Loader thông qua ClassLoader API.

Vậy tại sao Bootstrap Class Loader lại quan trọng đến vậy?

  • Nó đặt nền móng cho quá trình thực thi của JVM: mọi Class Loader khác đều phụ thuộc vào nó.
  • Nó đảm bảo tính an toàn: các class cốt lõi (java.lang.*) không thể bị ghi đè vì Bootstrap Class Loader nằm ở đỉnh của hierarchy.
  • Nó khởi tạo môi trường runtime: Heap, Stack, Code Cache, GC, JIT và các thành phần runtime khác đều phụ thuộc vào các class cốt lõi được Bootstrap Class Loader nạp.
  • Nó hỗ trợ Module System (Java 9+): các class cốt lõi được quản lý dưới dạng module, giúp JVM nhẹ hơn, được cô lập tốt hơn và an toàn hơn.

Extension/Platform ClassLoader

Trong hệ thống phân cấp ClassLoader của Java, sau Bootstrap Class LoaderExtension Class Loader (được đổi tên thành Platform Class Loader từ Java 9 trở đi). Đây là một thành phần quan trọng chịu trách nhiệm nạp các thư viện mở rộng và các platform module nhằm mở rộng chức năng cốt lõi của Java.

Trước Java 9, các extension được nạp từ thư mục Java extension ($JAVA_HOME/jre/lib/ext). Những class này thường bao gồm:

  • JDBC drivers
  • security providers
  • các thư viện mở rộng chuẩn khác

Từ Java 9 trở đi, Class Loader này được đổi tên thành Platform ClassLoader. Nó chịu trách nhiệm nạp các platform module và là một thành phần cốt lõi của Java Platform Module System (JPMS).

Một số điểm cần lưu ý khi làm việc với Extension / Platform ClassLoader

  • Thứ tự ưu tiên & quản lý phiên bản: Các class được nạp bởi Extension/Platform ClassLoader có độ ưu tiên cao hơn các class được nạp bởi Application ClassLoader. Vì vậy, nếu cùng một class tồn tại ở cả extension/platform layer và application classpath, việc quản lý không đúng cách có thể dẫn đến xung đột phiên bản, bởi JVM sẽ ưu tiên sử dụng phiên bản được nạp bởi Extension/Platform ClassLoader.

  • Ảnh hưởng đến bảo mật: Vì Platform ClassLoader nằm phía trên Application ClassLoader trong hierarchy, JVM xem các class này là những thư viện đáng tin cậy. Chúng cũng có thể được cấp quyền cao hơn so với các class ở tầng ứng dụng. Do đó, khi triển khai ứng dụng, cần đảm bảo rằng các class không đáng tin cậy không được đặt trong extension/platform layer và quyền truy cập đối với các class được Platform ClassLoader nạp phải được kiểm soát chặt chẽ.

Application Class Loader

Trong hệ thống phân cấp ClassLoader của Java, Application ClassLoader (còn được gọi là System ClassLoader) là Class Loader cuối cùng trong Parent Delegation Model. Nó chịu trách nhiệm nạp các class của ứng dụng và các thư viện bên ngoài, đồng thời cũng là Class Loader mà developer tương tác nhiều nhất khi chạy project hoặc làm việc với classpath.

Class Loader này nạp mã nguồn của ứng dụng, bao gồm:

  • Các class được nạp từ classpath:

    • code do bạn viết
    • các thư viện bên ngoài (JAR, thư mục)
  • Các vị trí được khai báo thông qua:

    • biến môi trường CLASSPATH
    • các tùy chọn dòng lệnh: -cp hoặc -classpath

Application ClassLoader cũng hỗ trợ dynamic class loading thông qua các kỹ thuật như:

  • Reflection: Class.forName("com.example.MyClass")
  • Proxy class hoặc sinh bytecode động: Spring AOP, Hibernate, ByteBuddy

Các class này được nạp vào Method Area / Metaspace của JVM; các object instance được cấp phát trên Heap, và stack frame được tạo trên thread stack khi method được gọi.

Là Class Loader cuối cùng trong Parent Delegation Model, Application ClassLoader có thể truy cập các class được nạp bởi các parent ClassLoader, nhưng các class do chính nó nạp không thể được nhìn thấy bởi các parent ClassLoader. Điều này đảm bảo rằng các class cốt lõi và platform module không thể bị ghi đè, đồng thời vẫn cho phép ứng dụng nạp các class cần thiết mà không ảnh hưởng đến JVM.

Ví dụ:

code
java -cp myapp.jar com.example.Main
  • JVM sử dụng Application ClassLoader để tìm myapp.jar trên classpath.
  • Sau đó, nó nạp com.example.Main cùng các class phụ thuộc từ file JAR.
  • Nếu một class không được tìm thấy trong các parent ClassLoader hoặc trên classpath, ClassNotFoundException sẽ được ném ra.

Kết luận

Hiểu rõ Class Loader Subsystem trong JVM là nền tảng để nắm vững kiến trúc của Java. Trong bài viết này, chúng ta đã cùng tìm hiểu:

  • Vì sao Lazy Loading lại quan trọng: JVM tối ưu bộ nhớ và thời gian khởi động bằng cách chỉ nạp class khi thực sự cần, từ đó hỗ trợ các tính năng động như plugin, hot swap và kiến trúc module.
  • Mô hình phân cấp ba tầng của Class Loader: Bootstrap, Extension/Platform và Application ClassLoader phối hợp với nhau thông qua Parent-First Delegation Model, đảm bảo các class cốt lõi luôn được bảo vệ trong khi vẫn giữ được sự linh hoạt cho ứng dụng.
  • Ứng dụng trong thực tế: Các framework như Spring Boot, OSGi và các application server đều dựa vào cơ chế Dynamic Class Loading để mang lại khả năng mở rộng và tính module, những yếu tố giúp Java trở thành một nền tảng mạnh mẽ cho các ứng dụng enterprise.

Sau khi đã hiểu cách JVM nạp và quản lý các class, bạn đã sẵn sàng khám phá các class và object thực sự được lưu trữ ở đâu trong bộ nhớ. Trong bài viết tiếp theo, chúng ta sẽ đi sâu vào Runtime Data Areas của JVM, bao gồm Heap, Stack, Metaspace và cách bộ nhớ được tổ chức để hỗ trợ việc thực thi cũng như Garbage Collection một cách hiệu quả.


📚 Tiếp tục tìm hiểu

Bài tiếp theo trong Series: JVM Memory Management — Heap, Stack và Runtime Data Areas

Tìm hiểu cách JVM tổ chức bộ nhớ, quản lý vòng đời của object và tối ưu hiệu năng thông qua kiến trúc quản lý bộ nhớ.

Các bài viết khác trong Series:


Bài viết này là một phần của JVM Fundamentals Series. Mỗi bài viết sẽ tiếp nối kiến thức của bài trước để giúp bạn có được cái nhìn toàn diện về cách các ứng dụng Java thực sự vận hành phía sau hậu trường.