# JavaSec101 - P3: Java class loading(1)
Tiếp tục với series tò mò về java. **Cảnh báo**: đây chỉ là series mình đi đọc từ nhiều nguồn và dịch lại nguyên văn và copy vào thôi, không có gì cao siêu cả, kiến thức có thể sẽ sai
Cũng đắn đo là có nên viết đăng lên không vì nhiều người lại kêu flex, múa,...
Nhưng mà bị cái là mình lại hay quên nên nếu không viết lại chắc đọc đến đâu quên hết đến đấy, không public thì không có feedback mà không có feedback thì chẳng biết mình sai, thiếu ở đâu :D. Và cũng để vài bữa nữa đọc lại à hóa ra đã từng hiểu thế này

## Compilers và Interpreters
Quá trình biên dịch của hầu hết các ngôn ngữ được vận hành bởi 2 thành phần là Compiler và Interpreters
Compiler (trình biên dịch) là một chương trình chuyển đổi code của các ngôn ngữ bậc cao như (C, C++, Java,...) về các ngôn ngữ bậc thấp hơn (mã máy) mà máy tính có thể hiểu được
Cụ thể trong java thì sau khi viết mã nguồn ở file **sourcecode.java** có phải chúng ta sẽ sử dụng câu lệnh
```bash
javac sourcecode.java
```
Và nó sẽ tạo ra file **sourcecode.class**. Tuy nhiên, file này vẫn chưa thể thực thi được để chạy được nó thì cần phải có Interpreters
Interpreter (trình thông dịch) là một chương trình phần mềm dùng để thực thi trực tiếp mã nguồn (source code) hoặc mã trung gian
```bash
java sourcecode.class
```
## JVM
Java virtual machine (JVM) là một "máy ảo" cho phép chạy code java trên bất kỳ hệ điều hành nào (windows, linux, macos)

Như mô hình trên có thể thấy JVM chia làm 3 phần chính:
- ClassLoader Subsystem
- Runtime Data Area
- Execution Engine
## Cấu trúc của một Class
Cũng giống như các file như PE file hay ELF file,.. thì file class của java cũng có cấu trúc.
Cấu trúc của một file Class sau khi được compile sẽ như sau:
```
ClassFile {
u4 magic; //Magic header
u2 minor_version; //Minor version
u2 major_version; //Major version
u2 constant_pool_count; //Constant pool count
cp_info constant_pool[constant_pool_count-1]; //Constant pool
u2 access_flags; // Class's access flags
u2 this_class; // Point to index of this class (which exists in constant pool)
u2 super_class; // Point to index of parent class (which exists in constant pool)
u2 interfaces_count; // Interfaces count
u2 interfaces[interfaces_count]; // Interfaces list
u2 fields_count; // Fields count
field_info fields[fields_count]; // Fields list
u2 methods_count; // Methods count
method_info methods[methods_count]; // Methods list
u2 attributes_count; // All attributes count
attribute_info attributes[attributes_count]; // All attributes
}
```
- Magic number (tất cả các class đều phải bắt đầu với 4 byte ở dạng hexa **CAFEBABE**)
- minor_version, major_version chứa phiên bản của file class
- Constant Pool (đây là một thành phần quan trọng). Nó chứa các thông tin
+ Tên lớp, phương thức, trường (fields).
+ Chuỗi hằng (string literals).
+ Tham chiếu đến các đối tượng (class references).
+ Các giá trị hằng số (số nguyên, số thực...).
- Access flags (abstract, public,...)
- Tên của class
- Thông tin về kế thừa (như là class cha,..)
- Các Interfaces được implement
- Các fields
- Các attributes
## Cơ chế load class của java

### Loading
Quá trình load class bắt đầu bằng việc nhận đầu vào là một mảng byte. Thường là sẽ đọc từ một file nào đó, nhưng nó hoàn toàn có thể đọc từ URL hoặc vị trí khác (Phụ thuộc vào Path object)
Phương thức **defineClass()** của **ClassLoader** được sử dụng để chuyển từ bytecode thành class object.

Tuy nhiên là phương thức defineClass có access flag là Protected nên không thể gọi từ ngoài.
Hmm ở dưới mình sẽ giải thích kĩ hơn phase loading này vì classloader mình thấy nó hơi khó hiểu =((
Tạm thời bạn có thể hiểu đơn giản là bằng một cách thần kì nào đó JVM đã đưa được byte code về thành object Class =))
### Linking
#### Verifying
* Mục đính chính là đảm bảo thông tin chứa trong byte stream của class đáp ứng đủ yêu cầu của JVM và không gây nguy hiểm về bảo mật theo đánh giá của JVM
* Nó bao gồm 3 bước:
* File format verification (file phải bắt đầu bằng các byte **0xcafebabe**)
* Metadata verification
* Bytecode verification
* Symbol reference verification
* Có thể tắt bước verify bằng cách chạy với tham số **-Xverify:none**
#### Preparation
Ở đoạn này thì JVM sẽ phân bổ bộ nhớ cho các biến static và khởi tạo chúng với giá trị khởi tạo(Các kiểu dữ liệu khác nhau thì có các giá trị khởi tạo mặc định khác nhau, ví dụ như int - 0, boolean - false). Không gian bộ nhớ của các biến này nằm ở **method area**
Ví dụ đi cho dễ hiểu:
```java
public class Main {
public static int n2 = 20;
public int n1 = 0;
public static final int n3 = 10;
public static void main(String[] args) {
System.out.println(n2);
}
}
```
> n1 là thuộc tính instance, không phải static nên nó sẽ không được cấp phát bộ nhớ trong giai đoạn preparation
> n2 là một biến static nên JVM sẽ cấp phát bộ nhớ và khởi tạo nó với giá trị mặc định là = 0 chứ không phải = 20
> n3 là một biến static final nên nó sẽ không bao giờ thay đổi sau khi khởi tạo nên JVM sẽ cấp phát bộ nhớ là khởi tạo nó với giá tri là 10 luôn.

#### Resolution
Trong giai đoạn này, các tham chiếu tới các class, phương thức, và biến tĩnh được giải quyết để trỏ đến các đối tượng thực sự trong bộ nhớ
### Initialization
> 1. JVM không thực thi code trong giai đoạn này. Mà nó sẽ thực thi phương thức <cinit>()
> 2. <cinit>() là phương thức mà compiler đi tìm những phép gán của tất cả các **biến static** trong class và trong **code blocks** theo thứ tự từ trên xuống và gán giá trị cho chúng.
> 3. JVM đảm bảo rằng phương thức <cinit>() của từng class là thuộc tính khóa và đồng bộ hóa trong môi trường đa luồng.
Nếu có nhiều class cùng lúc khởi tạo một class thì chỉ có một thread chạy phương thức <cinit>() của class đó còn tất cả thread còn lại sẽ phải chờ cho đến lượt.
Ví dụ:
```java=
public class Main {
public static void main(String[] args) {
System.out.println(B.num);
}
}
class B{
static {
System.out.println("Khối static của B được load");
num = 300;
}
static int num =100;
public B(){
System.out.println("B() Constructer cua B duoc goi");
}
}
```
Giải thích:
1. Giai đoạn loading: Load class B và tạo một đối tượng class của B
2. Giai đoạn connection: khởi tạo giá trị của biến num = 0 theo mặc định của kiểu int
3. Giai đoạn khởi tạo: Chạy phương thức **<cinit>()**
```java=
cinit(){
System.out.println("Khối static của B được load");
num=300;
num=100;
}
```

### Runtime Data Area

Runtime data area có 5 thành phần:
1. Heap Area
2. Method Area
3. Stack Area
4. PC Registers
5. Native Method Stack
#### Heap Area
Heap Area trong JVM là một vùng trong bộ nhớ **nơi tất cả các đối tượng và mảng được cấp phát**. Đây là một trong những vùng quan trọng nhất vì nó là nơi lưu trữ các đối tượng trong suốt vòng đời của chúng, miễn là chúng còn được tham chiếu
- Heap Area là một phần của bộ nhớ được quản lý bởi JVM.
- Được sử dụng để lưu trữ các đối tượng và dữ liệu liên quan (như mảng).
- Khi bạn sử dụng từ khóa new trong Java để tạo đối tượng hoặc mảng, bộ nhớ cho đối tượng đó được cấp phát trong Heap Area.
**Hay ho**: Có một điều khá hay là heap memory được chia sẻ và sử dụng chung bởi tất cả các threads của JVM, đây chính là điểm mấu chốt để mình phát triển tools rà quét memory shell trong java :D
#### Method Area
Vùng nhớ này cũng được sử dụng chung và có thể tác động vào bởi bất kì threads nào của JVM.
Method Area là một vùng trong bộ nhớ của JVM được sử dụng để lưu trữ thông tin về các lớp, phương thức và dữ liệu liên quan
- Thông tin về các lớp đã được nạp (class metadata).
- Tên lớp, tên các phương thức, và tên các trường (fields).
- Mã bytecode của các phương thức (method bytecode).
- Bảng hằng số (constant pool) chứa các hằng số được sử dụng trong chương trình.
```java=
public class Example {
public static void main(String[] args) {
MyClass obj = new MyClass(); // Tạo đối tượng trong Heap
obj.method(); // Thực thi phương thức
}
}
class MyClass {
static int staticVar = 42; // Lưu trong Method Area
int instanceVar = 10; // Lưu trong Heap
void method() { // Lưu trong Method Area
System.out.println("This is a method.");
}
}
```
#### Stack Area

Vùng stack thì các thread không dùng chung nhau nữa rồi :D. Mỗi threads sẽ có một vùng stack riêng
Lưu trữ dữ liệu tạm thời:
- Các biến cục bộ (local variables).
- Các tham số truyền vào phương thức (method parameters).
- Thông tin điều khiển (như địa chỉ lệnh tiếp theo).
Khi một phương thức được gọi, JVM tạo một khung ngăn xếp (stack frame) trong Stack. Stack frame chứa:
- Local Variables Array: Các biến cục bộ trong phương thức.
- Operand Stack: Dùng để thực hiện các phép toán.
- Frame Data:
+ Thông tin điều khiển (program counter).
+ Tham chiếu đến phương thức gọi (caller).
```java
public class StackExample {
public static void main(String[] args) {
int x = 10; // Lưu trong Stack frame hàm main
int y = sum(x, 20); // Lưu trong Stack frame hàm sum và gọi phương thức sum
System.out.println("Sum: " + y);
}
static int sum(int a, int b) { // a, b cũng lưu trong stack frame hàm sump
int result = a + b; // Lưu trong Stack frame hàm sum
return result;
}
}
```
Mình thấy nó hoạt động cũng giống với stack trong C/C++,ASM
#### Native Method Stack

Native Method Stack được sử dụng để quản lý việc gọi và thực thi các phương thức native. Đây là các phương thức được viết bằng ngôn ngữ lập trình khác ngoài Java (ví dụ: C hoặc C++) và thường được sử dụng để tương tác với hệ điều hành hoặc thư viện bên ngoài.
### Execution Engine
Chịu trách nhiệm thực thi bytecode – mã trung gian mà Java Compiler sinh ra – và chuyển đổi nó thành mã máy (machine code) để CPU hiểu và chạy được.
Hiểu đơn giản:
- Khi bạn chạy một chương trình Java, Execution Engine là nơi:
+ Nhận bytecode (mã trung gian mà JVM hiểu).
+ Chuyển bytecode thành mã máy (CPU hiểu).
+ Thực thi chương trình Java của bạn.
Quá trình hoạt động:
1. Nhận bytecode từ Class Loader:
2. Class Loader nạp bytecode từ .class file vào JVM.
Chuyển đổi bytecode thành mã máy:
- Bytecode được "dịch" sang mã máy bằng cách:
+ Interpretation (Thông dịch): Chuyển từng dòng bytecode sang mã máy và thực thi ngay.
+ Just-In-Time (JIT) Compilation: Chuyển đổi cả đoạn bytecode thành mã máy và lưu lại để tái sử dụng, giúp chương trình chạy nhanh hơn.
Thực thi mã máy:
3. Sau khi bytecode được dịch, Execution Engine giao mã máy cho CPU thực thi.
## Classloader
Từ các byte stream các class sẽ được load vào bộ nhớ thành các object nhờ các ClassLoader
Đối với JVM thì nó chỉ chia classloader làm hai loại:
1. Bootstrap Classloader được viết bằng C++
2. Các ClassLoader khác được kế thừa từ abstract class java.lang.ClassLoader
### Bootstrap Classloader
ClassLoader này được viết bằng C++. Đặc điểm
- Nhiệm vụ chính là load các java library ($JAVA_HOME/lib/*, các class kiểu như java.lang.\*, java.util.\*, java.io.\*, java.net.\*).
- Nó không kế thừa java.lang.ClassLoader (:D vì nó viết bằng C++ mà)
### Extension ClassLoader
Là subclass của **java.lang.ClassLoader**
Chịu trách nhiệm tải thư viện tiện ích mở rộng của java, tải tất cả các lib theo mặc định và tên thư viện thường bắt đầu bằng **javax.**
Các lib nằm trong thư mục **java_home/jre/lib/ext/**
### Application ClassLoader
Classloader này thì người dùng, người code có thể sử dụng, còn 2 cái trên chỉ có JVM nó dùng thôi.
Có một khái niệm quan trọng ở classloader này.
#### **Classpath là gì?**
- Classpath trong Java là một đường dẫn (path) mà JVM (Java Virtual Machine) và Java Compiler sử dụng để tìm kiếm các tệp lớp (class files) và thư viện cần thiết cho chương trình Java của bạn.
Khi bạn chạy hoặc biên dịch một chương trình Java, JVM sẽ cần biết nơi tìm các class mà chương trình yêu cầu. Classpath chính là nơi mà JVM tìm kiếm các class này.
Classpath có thể bao gồm:
- Thư mục: Nơi chứa các tệp .class.
- JAR files: Các tệp lưu trữ Java (Java Archive), thường chứa nhiều class.
- Tệp ZIP: Các thư viện hoặc class đóng gói trong tệp ZIP.
Application ClassLoader sẽ load class các class trong code chúng ta viết từ các **classpath**
Ví dụ load chỉ định classpath:
Nếu chạy **java -cp myapp.jar HelloWorld**
Application ClassLoader sẽ tìm và nạp class HelloWorld từ myapp.jar.
Nếu không chỉ định classpath thì mặc định Application ClassLoader sẽ tìm trong thư mục hiện tại. Ví dụ:
javac HelloWorld.java
java HelloWorld
### URLClassloader
URLClassLoader là một lớp con của ClassLoader trong Java, được sử dụng để nạp các class và tài nguyên từ các URL chỉ định. Không còn gò bó với classpath nữa
```java
import java.net.URL;
import java.net.URLClassLoader;
public class URLClassLoaderFromInternet {
public static void main(String[] args) throws Exception {
// URL trỏ đến tệp JAR trên internet
URL jarUrl = new URL("https://example.com/path/to/your-library.jar");
// Tạo URLClassLoader
try (URLClassLoader classLoader = new URLClassLoader(new URL[]{jarUrl})) {
// Nạp class từ JAR
Class<?> loadedClass = classLoader.loadClass("com.example.MyClass");
// Kiểm tra class đã được nạp
System.out.println("Class Name: " + loadedClass.getName());
}
}
}
```
## Cơ chế ủy quyền và trùm cuối aka ClassLoader

Java sử dụng mô hình ủy quyền để tải các lớp. ý tưởng cơ bản là mọi ClassLoader đều có ClassLoader cha.

>Nếu ClassLoader cha không được chỉ định thì nó sẽ mặc định ClassLoader cha của một ClassLoader bất kì là BootstrapClassLoader
java.lang.ClassLoader và các subclass kế thừa nó sẽ có các phương thức sau
- loadClass (Load class)
- findClass (Tìm kiếm một class)
- findLoadedClass (Tìm class đã được load bởi JVM)
- defineClass (Định nghĩa một class)
- resolveClass (Link một class)
Khi load một class, trước tiên ClassLoader sẽ ủy quyền tìm kiếm class đó cho Classloader cha của nó trước khi cố gắng tìm chính lớp đó.
Đọc code của phương thức **loadClass**

Nó sẽ tìm xem class đã được load chưa, nếu chưa thì nó sẽ ủy quyền việc loadClass cho class cha, class cha không tìm thấy thì nó mới trực tiếp đi tìm.
Trong trường hợp ClassLoader cha hay bản thân ClassLoader đó tìm thấy thì sẽ dùng đến phương thức **defineClass** để đưa từ bytecode về object. Ví dụ như URLClassLoader

Ở đây, URLClassLoader có defineClass khi tìm thấy class. Còn đối với lớp gốc java.lang.ClassLoader nó không hề có defineClass trong hàm loadClass. Mà chỉ có resolveClass ở cuối. Nghĩa là nếu tìm thấy class thì nó đưa vào giai đoạn linking còn nếu không thì nó trả về null.
Vậy nên thường dev sẽ kế thừa java.lang.ClassLoader để viết một custom ClassLoader ví dụ như URLClassLoader ở trên.
Mình sẽ thử viết một Custom Classloader nhé. Ví dụ mình có class
```java
public class Hello {
public void sayHello(){
System.out.println("Hello! ----------> DIYClassLoader");
}
}
```
Và CustomClassLoader của mình như sau
```java
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class DIYClassLoader extends ClassLoader{
private String mylibPath;
public DIYClassLoader(String path) {
mylibPath = path;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
String fileName = getFileName(name);
File file = new File(mylibPath,fileName);
try{
FileInputStream is = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = 0;
try {
while((len = is.read())!= -1){
bos.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] data = bos.toByteArray();
is.close();
bos.close();
return defineClass(name,data,0,data.length);
}catch (Exception e){
e.printStackTrace();
}
return super.findClass(name);
}
private String getFileName(String name) {
int index = name.lastIndexOf('.');
if(index == -1){
return name+".class";
}else{
return name.substring(index)+".class";
}
}
}
```
Mình đã override phương thức findClass và thêm vào defineClass. Vì thế mình chỉ việc chỉ định Classpath là nơi chứa class là đã có thể load
```java
import java.lang.ClassLoader;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) {
DIYClassLoader diyClassLoader = new DIYClassLoader("C:\\Users\\PBUG\\Documents");
try {
Class<?> c = diyClassLoader.loadClass("Hello");
if (c != null) {
try {
Object obj = c.newInstance();
Method method = c.getDeclaredMethod("sayHello", null);
method.invoke(obj, null);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
```

Có lẽ mình sẽ updating tiếp sau hoặc có thể không :V. Ví dụ như BCEL ClassLoader, XALAN Classloader cũng rất hay nhưng mà mình đọc không hiểu gì =( . Hi vọng nó có ích với bạn. Rất mong nhận được feedback.
## Nguồn tham khảo
https://mohibulsblog.netlify.app/posts/java/100daysofjava/day44/
http://ndl.ethernet.edu.et/bitstream/123456789/25998/1/Benjamin%20J.%20Evans.pdf
https://dzone.com/articles/jvm-architecture-explained
https://www.javasec.org/javase/ClassLoader/