JVM和类
当调用iava命令运行某个Java程序时,该命令将会启动一个Java虚拟机进程,不管该Java程序有多么复杂,该程序启动了多少个线程,它们都处于该Java虚拟机进程里。正如前面介绍的,同一个M的所有线程、所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。当系统出现以下几种情况时,IM进程将被终止。
程序运行到最后正常结束
程序运行到使用System.exit()或Runtime.getRuntime().exit()代码处结束程序。
程序执行过程中遇到未捕获的异常或错误而结束。
程序所在平台强制结束了IM进程。
类的加载
定义
- 将类的 .class 字节码文件加载到内存中,并生成对应的 java.lang.Class 对象(存储在方法区)。
关键步骤
- 定位字节码:通过类加载器(ClassLoader)查找 .class 文件(如文件系统、JAR 包、网络等)。
- 读取字节码:将字节码转换为二进制数据流。
- 创建 Class 对象:在方法区生成类的运行时数据结构(字段、方法、接口等元数据)。
类加载器
- 类的加载由类加载器完成,类加载器通常由JVM提供,JVM提供的这些类加载器通常被称为系统类加载器。
- 开发者可以通过继承ClassLoader基类来创建自己的类加载器。
- 触发条件:首次主动使用类时触发(如 new、静态方法调用、反射等)。
连接(Linking)
连接阶段分为三个子阶段:验证(Verification)、准备(Preparation) 和 解析(Resolution)。
验证(Verification)
- 目的:确保字节码符合 JVM 规范,防止恶意代码破坏虚拟机。
- 检查内容:
- 文件格式(魔数、版本号)。
- 元数据(类继承关系、方法重写)。
- 字节码指令(操作数类型、跳转指令合法性)。
准备(Preparation)
- 目的:为类的静态变量分配内存并设置默认初始值。
- 关键点:
- 静态变量的内存分配在方法区。
- 初始值为类型默认值(如 int 初始化为 0,引用类型初始化为 null)。
- final static 常量在此阶段直接赋值(若为字面量)。
解析(Resolution)
- 目的:将符号引用(符号化的类、方法、字段名称)转换为直接引用(内存地址)。
- 解析对象:
- 类或接口的符号引用。
- 字段和方法的符号引用。
- 延迟解析:某些解析可能在初始化后发生(如动态绑定)。
初始化(Initialization)
定义
执行类的 () 方法(由编译器自动生成),为静态变量赋实际值并执行静态代码块。
关键规则
- 主动触发条件:类的首次主动使用(如创建实例、访问静态字段、调用静态方法)。
- 父类优先:保证父类的 () 先于子类执行。
- 线程安全:() 方法由 JVM 加锁同步,确保只执行一次。
注意
- 被动引用不会触发初始化:
- 通过子类引用父类的静态字段(仅初始化父类)。
- 通过数组定义引用类(如 MyClass[] arr = new MyClass[10])。
- 访问类的 final static 常量(已在准备阶段赋值)。
类初始化的时机
当Java程序首次通过下面6种方式来使用某个类或接口时,系统就会初始化该类或接口。
- 创建类的实例。为某个类创建实例的方式包括:使用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例。
- 调用某个类的类方法(静态方法)
- 访问某个类或接口的类变量,或为该类变量赋值。
- 使用反射方式来强制创建某个类或接口对应的iava.lang.Class对象。
类加载器
Java 类加载器(ClassLoader)是 JVM 的重要组成部分,负责将类的字节码文件(.class)加载到内存中,并生成对应的 java.lang.Class 对象。类加载器采用双亲委派模型,确保类的唯一性和安全性。
类加载器的分类
Java 类加载器分为以下四类:
启动类加载器(Bootstrap ClassLoader)
- 职责:加载 JVM 核心类库(如 java.lang.、java.util.),位于 $JAVA_HOME/jre/lib 目录。
- 实现:由 C/C++ 实现,是 JVM 的一部分。
- 特点:
- 是唯一没有父类加载器的加载器。
- 无法通过 Java 代码直接访问。
扩展类加载器(Extension ClassLoader)
- 职责:加载扩展类库(如 javax.*),位于 $JAVA_HOME/jre/lib/ext 目录。
- 实现:由 sun.misc.Launcher$ExtClassLoader 实现。
- 特点:
- 父类加载器是 Bootstrap ClassLoader。
- 可以通过 ClassLoader.getSystemClassLoader().getParent() 获取。
应用程序类加载器(Application ClassLoader)
- 职责:加载用户类路径(classpath)下的类。
- 实现:由 sun.misc.Launcher$AppClassLoader 实现。
- 特点:
- 父类加载器是 Extension ClassLoader。
- 是默认的系统类加载器,可以通过 ClassLoader.getSystemClassLoader() 获取。
自定义类加载器(Custom ClassLoader)
- 职责:用户自定义的类加载器,用于加载非标准路径的类(如网络、加密文件)。
- 实现:继承 java.lang.ClassLoader,重写 findClass() 方法。
- 特点:
- 父类加载器是 Application ClassLoader。
- 可以实现热部署、模块化加载等功能。
双亲委派模型(Parent Delegation Model)
双亲委派模型是类加载器的工作机制,确保类的唯一性和安全性。
工作原理
- 委派:当一个类加载器收到加载请求时,首先将请求委派给父类加载器。
- 加载:如果父类加载器无法加载(如找不到类),则由当前类加载器尝试加载。
- 唯一性:确保同一个类只被加载一次,避免重复加载。
代码实现
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 2. 委派给父类加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 3. 父类加载器为 null,委派给 Bootstrap ClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 4. 父类加载器无法加载,由当前类加载器尝试加载
c = findClass(name);
}
}
// 5. 解析类(可选)
if (resolve) {
resolveClass(c);
}
return c;
}
}
优势
- 避免重复加载:确保类的唯一性。
- 安全性:防止核心类库被篡改(如自定义 java.lang.String 类)。
- 隔离性:不同类加载器加载的类相互隔离。
自定义类加载器
自定义类加载器通常用于以下场景:
- 动态加载类(如插件化、热部署)。
- 加载加密或网络资源中的类。
- 实现模块化加载。
实现步骤
- 继承 ClassLoader。
- 重写 findClass() 方法。
- 在 findClass() 中读取字节码并调用 defineClass()。
示例代码
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 1. 读取字节码文件
byte[] data = loadClassData(name);
// 2. 定义类
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
private byte[] loadClassData(String name) throws IOException {
String path = classPath + File.separatorChar + name.replace('.', File.separatorChar) + ".class";
try (InputStream is = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
}
}
}
使用示例
public class Main {
public static void main(String[] args) throws Exception {
MyClassLoader loader = new MyClassLoader("/path/to/classes");
Class<?> clazz = loader.loadClass("com.example.MyClass");
Object obj = clazz.newInstance();
System.out.println(obj.getClass().getClassLoader()); // 输出 MyClassLoader
}
}
反射
反射(Reflection)是Java的一种机制,允许程序在运行时检查和操作类、方法、属性等元数据。通过反射,可以在运行时动态创建对象、调用方法、访问属性,而不需要在编译时知道这些类的具体信息。
反射的核心类
Java反射的核心类位于java.lang.reflect包中,主要包括:
- Class:表示一个类或接口。
- Field:表示类的成员变量。
- Method:表示类的方法。
- Constructor:表示类的构造方法。
获取Class对象
获取目标类的Class对象
获取Class对象的方式有三种:
- 类名.class:Class<?> clazz = MyClass.class;
- 调用某个类的class属性来获取该类对应的Class对象。
- 对象.getClass():Class<?> clazz = myObject.getClass();
- 该方法是iava.lang.0biet类中的一个方法,该方法将会返回该对象所属类对应的Class对象。
- Class.forName():Class<?> clazz = Class.forName(“com.example.MyClass”);
- 使用Class类的forName(String clazzName)静态方法。
- 该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须添加完整包名)。
Class类介绍
获取Class对应类所包含的构造器。
方法 | 说明 |
---|---|
ConnstrnuctorgetConstructor(Class<?>…parameterTypes) | 返回此Class对象对应类的、带指定形参列表的public构造器 |
Constructor<?>[lgetConstructors() | 返回此Class对象对应类的所有public构造器 |
ConstructorgetDeclaredConstructor (Class<?>…parameterTypes) | 返回此Class对象对应类的、带指定形参列表的构造器,与构造器的访问权限无关。 |
Constructor<?>getDeclaredConstructors | 返回此Class对象对应类的所有构造器,,与构造器的访问权限无关。 |
获取方法
方法 | 说明 |
---|---|
Method getMethod (String name, Class<?>…parameterTypes) | 返回此Class对象对应类的、带指定形参列表的public方法。 |
Method[lgetMethods() | 返回此Class对象所表示的类的所有public方法。 |
Method getDeclaredMethod(String name,Clas<?>…parameterTypes) | 返回此Class对象对应类的、带指定形参列表的方法,与方法的访问权限无关。 |
MethodgetDeclaredMethods() | 返回此Class对象对应类的全部方法,与方法的访问权限无关。 |
获取成员变量
方法 | 说明 |
---|---|
Field getField (String name) | 返回此Class对象对应类的、指定名称的public成员变量 |
Field[]getFields() | 返回此Class对象对应类的所有public成员变量 |
Field getDeclaredField(String name) | 返回此Class对象对应类的、指定名称的成员变量,与成员变量的访问权限无关。 |
Field[lgetDeclaredFields) | 返回此Class对象对应类的全部成员变量,与成员变量的访问权限无关。 |
获取Annotation
方法 | 说明 |
---|---|
< A extends Annotation>A getAnnotation (Class < A > annotationClass):Annotation | 尝试获取该Class对象对应类上存在的、指定类型的;如果该类型的注解不存在,则返回null。 |
< A extends Annotation>A getDeclaredAnnotation(Class< A>annotationClass) | Java 8新增的方法,该方法尝试获取直接修饰该Class对象对应类的、指定类型的Annotation;如果该类型的注解不存在,则返回null。 |
Annotation[] getAnnotations) | 返回修饰该Class对象对应类上存在的所有Annotation |
Annotation[] getDeclaredAnnotations() | 返回直接修饰该Class对应类的所有Annotation。 |
判断该类是否为接口、枚举、注解类型等
方法 | 说明 |
---|---|
boolean isAnnotation() | 返回此Class对象是否表示一个注解类型(由@interface定义)。 |
boolean isAnnotationPresent(Class <?extends Annotation>annotationClass) | 判断此Class对象是否使用了Annotation修饰, |
boolean isAnonymousClass() | 返回此Class对象是否是一个名类。 |
boolean isArray() | 返回此Class对象是否表示一个数组类。 |
boolean isEnum | 返叫此Class对象是台表示一个枚举(由enum关键字定义)。 |
boolean isInterface() | 返回此Class对象是否表示一个接口(使用interface定义)。 |
boolean isInstance(Object obj) | 判断obi是否是此Class对象的实例,该方法可以完全代替imnstanceof操作符 |
创建对象
通过反射创建对象有两种方式:
- 使用无参构造方法
Class<?> clazz = Class.forName("com.example.MyClass");
Object obj = clazz.newInstance();
- 使用带参构造方法
Class<?> clazz = Class.forName("com.example.MyClass");
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("example", 123);
访问字段
通过反射可以访问和修改类的字段
- 获取字段
Field field = clazz.getDeclaredField("fieldName");
- 访问私有字段
field.setAccessible(true);
- 读取字段值
Object value = field.get(obj);
- 设置字段值
field.set(obj, newValue);
调用方法
- 获取方法
Method method = clazz.getDeclaredMethod("methodName", String.class, int.class);
- 调用方法
Object result = method.invoke(obj, "example", 123);
访问私有方法
要访问私有方法,需要先设置访问权限:
method.setAccessible(true);
import java.lang.reflect.*;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 获取Class对象
Class<?> clazz = Class.forName("com.example.MyClass");
// 创建对象
Object obj = clazz.newInstance();
// 访问字段
Field field = clazz.getDeclaredField("fieldName");
field.setAccessible(true);
field.set(obj, "newValue");
// 调用方法
Method method = clazz.getDeclaredMethod("methodName", String.class, int.class);
method.setAccessible(true);
Object result = method.invoke(obj, "example", 123);
System.out.println(result);
}
}
JDK动态代理
JDK动态代理是Java提供的一种基于接口的代理机制,通过反射和java.lang.reflect.Proxy类来实现。它可以在运行时动态创建代理对象,并将方法调用转发到指定的InvocationHandler中。
JDK动态代理的核心类
- java.lang.reflect.Proxy:
- 用于创建动态代理类和代理对象。
- 提供了静态方法newProxyInstance来生成代理对象。
- java.lang.reflect.InvocationHandler:
- 是一个接口,定义了invoke方法。
- 代理对象的方法调用会被转发到InvocationHandler的invoke方法中。
实现步骤
- 定义接口:代理对象需要实现的接口。
public interface UserService {
void addUser(String name);
void deleteUser(String name);
}
- 实现接口:创建接口的具体实现类。
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
@Override
public void deleteUser(String name) {
System.out.println("删除用户: " + name);
}
}
- 实现InvocationHandler:编写代理逻辑。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class UserServiceInvocationHandler implements InvocationHandler {
// 目标对象(被代理的对象)
private Object target;
public UserServiceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("方法调用前: " + method.getName());
// 调用目标对象的方法
Object result = method.invoke(target, args);
// 后置增强
System.out.println("方法调用后: " + method.getName());
return result;
}
}
- 生成代理对象:使用Proxy.newProxyInstance创建代理对象。
import java.lang.reflect.Proxy;
public class DynamicProxyExample {
public static void main(String[] args) {
// 创建目标对象
UserService userService = new UserServiceImpl();
// 创建InvocationHandler
InvocationHandler handler = new UserServiceInvocationHandler(userService);
// 生成代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(), // 类加载器
userService.getClass().getInterfaces(), // 接口列表
handler // InvocationHandler
);
// 通过代理对象调用方法
proxy.addUser("Alice");
proxy.deleteUser("Bob");
}
}