上一篇 下一篇 回到顶部 目录 返回首页
目录

Java 从入门到精通:高级特性与底层原理实战全指南

发表于
更新于
2 64.4~82.8 分钟 28991

前言

很多开发者写了几年 Java,却只停留在"会用 Spring 注解、会写 CRUD 接口"的阶段。一旦遇到框架源码、高并发瓶颈、内存泄漏、类加载冲突等问题,就无从下手。

本文以 Java 官方文档和最新 JEP(JDK Enhancement Proposal)为基准,深入讲解 Java 的高阶内容:泛型、反射、注解、并发编程、JVM 内存模型、垃圾回收、新特性(Java 21-26)。掌握这些,你才能从"会用框架"走向"理解底层"。


第一部分:泛型(Generics)

1.1 为什么需要泛型

没有泛型之前:

// JDK 5 之前:类型不安全
List list = new ArrayList();
list.add("Hello");
list.add(123);  // 编译不报错
String s = (String) list.get(1);  // 运行时报 ClassCastException

有了泛型之后:

List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123);  // 编译直接报错,类型安全在编译期保证

1.2 泛型类与泛型方法

// 泛型类
public class Box<T> {
    private T content;
    public void put(T content) { this.content = content; }
    public T get() { return content; }
}

// 泛型方法:类型参数在返回值之前声明
public static <T> T firstNonNull(T a, T b) {
    return a != null ? a : b;
}

// 调用时类型推断
String result = firstNonNull(null, "default");  // 自动推断 T = String

1.3 通配符:? extends T 与 ? super T

这是 Java 泛型最 confusing 的部分,核心在于 PECS 原则Producer Extends, Consumer Super。

// ? extends T:上界通配符(生产者,只读)
// 表示"某种 T 或 T 的子类"
List<? extends Number> numbers = new ArrayList<Integer>();
Number n = numbers.get(0);  // ✅ 可以读(一定是 Number 或子类)
// numbers.add(1);  // ❌ 编译错误:不知道具体是哪种 Number,不能写

// ? super T:下界通配符(消费者,只写)
// 表示"某种 T 或 T 的父类"
List<? super Integer> integers = new ArrayList<Number>();
// Number n = integers.get(0);  // ❌ 编译错误:只能保证读出来是 Object
integers.add(1);  // ✅ 可以写(Integer 一定是列表元素的子类)

// 无界通配符 ?
List<?> unknown = new ArrayList<String>();
Object obj = unknown.get(0);  // 只能读为 Object

PECS 口诀

  • 如果参数化类型产生 T(你从里面读),用 ? extends T

  • 如果参数化类型消费 T(你往里面写),用 ? super T

  • 如果既读又写,不要用通配符,用精确的 T

// 标准库中的经典例子
public static <T extends Comparable<? super T>> void sort(List<T> list) {
    // T 需要能被比较(产生 Comparable 值),所以用 extends
    // Comparable 消费 T(compareTo 接收 T 参数),但 Comparable 本身用 ? super T
    // 这样父类的 Comparator 也能用于子类排序
}

1.4 类型擦除(Type Erasure)

Java 的泛型是伪泛型,编译后类型参数会被擦除:

// 源码
Box<String> stringBox = new Box<>();
Box<Integer> intBox = new Box<>();

// 编译后等价于
Box stringBox = new Box();
Box intBox = new Box();

System.out.println(stringBox.getClass() == intBox.getClass());  // true
// 因为运行时都是 Box.class

类型擦除的副作用

// ❌ 不能创建泛型数组
// List<String>[] array = new List<String>[10];  // 编译错误

// ❌ 不能做泛型类型的运行时检查
// if (obj instanceof List<String>) { }  // 编译错误

// ✅ 可以用无界通配符检查
if (obj instanceof List<?>) { }  // 合法

// ❌ 不能捕获泛型类型的异常
// catch (T e) { }  // 编译错误

1.5 泛型约束

// 单一约束
public class Container<T extends Number & Comparable<T>> {
    // T 必须同时是 Number 的子类且实现 Comparable<T>
}

// 多个约束用 where(Java 没有 where,用 & 连接)
// 注意:& 后面如果跟接口,可以有多个;如果跟类,只能有一个且必须在第一个

// 方法签名中的约束
public static <T extends Comparable<T>> T max(Collection<T> collection) {
    return collection.stream().max(Comparator.naturalOrder()).orElse(null);
}

第二部分:反射(Reflection)

2.1 反射的核心概念

“反射是框架设计的灵魂。” 它允许程序在运行期获取任意类的信息(构造器、方法、字段、注解),并动态创建实例、调用方法、读写字段。

// 获取 Class 对象的三种方式
Class<?> clazz1 = Class.forName("com.example.User");  // 最常见
Class<?> clazz2 = User.class;                         // 编译期已知类型
Class<?> clazz3 = userInstance.getClass();            // 运行时获取

// 获取构造器
Constructor<?> constructor = clazz1.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);  // 跳过访问检查(访问 private 构造器)
User user = (User) constructor.newInstance("Alice", 25);

// 获取方法
Method method = clazz1.getDeclaredMethod("getName");
String name = (String) method.invoke(user);

// 获取字段
Field field = clazz1.getDeclaredField("age");
field.setAccessible(true);
int age = field.getInt(user);
field.setInt(user, 26);  // 修改字段值

2.2 反射在框架中的应用

Spring 的依赖注入本质就是反射

// Spring 内部简化伪代码
public Object createBean(Class<?> beanClass) throws Exception {
    // 1. 找到合适的构造器
    Constructor<?> constructor = beanClass.getDeclaredConstructor();

    // 2. 创建实例
    Object instance = constructor.newInstance();

    // 3. 扫描字段上的 @Autowired 注解
    for (Field field : beanClass.getDeclaredFields()) {
        if (field.isAnnotationPresent(Autowired.class)) {
            Object dependency = getBean(field.getType());
            field.setAccessible(true);
            field.set(instance, dependency);  // 注入依赖
        }
    }

    // 4. 调用 @PostConstruct 标注的方法
    for (Method method : beanClass.getDeclaredMethods()) {
        if (method.isAnnotationPresent(PostConstruct.class)) {
            method.setAccessible(true);
            method.invoke(instance);
        }
    }

    return instance;
}

动态代理(AOP 的基础)

public class LoggingProxy {
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T target) {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            (proxy, method, args) -> {
                System.out.println("Before: " + method.getName());
                Object result = method.invoke(target, args);
                System.out.println("After: " + method.getName());
                return result;
            }
        );
    }
}

// 使用
UserService realService = new UserServiceImpl();
UserService proxy = LoggingProxy.createProxy(realService);
proxy.createUser("Alice");
// 输出:
// Before: createUser
// After: createUser

2.3 反射的性能问题

反射调用比直接调用慢 10-50 倍,原因是:

  1. 方法查找需要安全检查

  2. 参数类型检查和装箱拆箱

  3. 访问控制检查(setAccessible

优化方案

// 方式一:缓存 Method 对象
private static final Method cachedMethod;
static {
    try {
        cachedMethod = User.class.getMethod("getName");
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    }
}
// 后续调用直接使用缓存的 Method
String name = (String) cachedMethod.invoke(user);

// 方式二:使用 MethodHandle(JDK 7+,更快)
MethodHandle handle = MethodHandles.lookup()
    .findVirtual(User.class, "getName", MethodType.methodType(String.class));
String name = (String) handle.invoke(user);

// 方式三:使用 VarHandle(JDK 9+,字段访问最快)
// 适合字段级别的高性能反射访问

2.4 JDK 26 的变化:Final Will Actually Mean Final

JEP 500 是 JDK 26 的重大变更:禁止通过反射修改 private final 字段。

// JDK 26 之前
Field field = User.class.getDeclaredField("id");
field.setAccessible(true);
field.set(user, "hacked-id");  // 可以修改 private final 字段

// JDK 26 起
field.set(user, "hacked-id");  // 抛出 IllegalAccessException
// private final 字段一旦初始化,就真的不可变了

这对序列化库(Jackson、Gson)和框架有影响,需要使用官方提供的替代方案(ObjectInputStream 的反序列化机制、MethodHandle 等)。


第三部分:注解(Annotations)

3.1 注解的本质

注解是 Java 的元数据机制,本身不改变代码行为,必须配合处理器(注解处理器、反射、字节码工具)才能发挥作用。

// 内置注解
@Override          // 编译期检查方法是否正确重写
@Deprecated        // 标记废弃,调用处会警告
@SuppressWarnings  // 压制编译器警告
@SafeVarargs       // 标记可变参数方法类型安全
@FunctionalInterface  // 编译期检查是否为合法函数式接口

// 元注解(用来定义注解的注解)
@Target(ElementType.METHOD)      // 该注解可以用在什么地方
@Retention(RetentionPolicy.RUNTIME)  // 保留到运行时(可反射读取)
@Documented                      // 包含在 Javadoc 中
@Inherited                       // 子类自动继承该注解
@Repeatable                      // 允许在同一位置重复使用

3.2 自定义注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
    String value() default "";        // 默认值
    LogLevel level() default LogLevel.INFO;
    boolean recordArgs() default true;
}

public enum LogLevel { DEBUG, INFO, WARN, ERROR }

3.3 运行时注解处理(反射方式)

public class LogExecutionAspect {
    public static <T> T createProxy(T target) {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            (proxy, method, args) -> {
                LogExecution log = method.getAnnotation(LogExecution.class);
                if (log != null && log.recordArgs()) {
                    System.out.println("[LOG] " + method.getName() +
                        " args: " + Arrays.toString(args));
                }
                try {
                    Object result = method.invoke(target, args);
                    return result;
                } catch (Exception e) {
                    if (log != null && log.level() == LogLevel.ERROR) {
                        System.err.println("[ERROR] " + method.getName() + ": " + e.getMessage());
                    }
                    throw e;
                }
            }
        );
    }
}

// 使用
@LogExecution(recordArgs = true, level = LogLevel.ERROR)
public void deleteUser(String userId) {
    // 业务逻辑
}

3.4 编译期注解处理器(APT)

编译期注解处理器在 javac 编译阶段运行,不依赖反射,零运行时开销。Lombok、Dagger、MapStruct 都基于此。

// 编译期注解处理器(简化示例)
@SupportedAnnotationTypes("com.example.AutoToString")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class AutoToStringProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(AutoToString.class)) {
            // 在编译期生成 toString() 方法的字节码/源码
            generateToStringMethod(element);
        }
        return true;  // 注解已处理
    }
}

运行时注解 vs 编译期注解

  • 运行时注解:需要 @Retention(RUNTIME),通过反射读取,有性能开销

  • 编译期注解:在编译期生成新代码(APT),零运行时开销,但实现复杂

  • 现代框架趋势:能用编译期就不用运行时(MapStruct 替代 ModelMapper,Lombok 替代 getter/setter)

3.5 重复注解

@Repeatable(Schedules.class)
@interface Schedule {
    String day();
}

@interface Schedules {
    Schedule[] value();
}

// 使用
@Schedule(day = "Monday")
@Schedule(day = "Wednesday")
@Schedule(day = "Friday")
public void meeting() { }

// 读取
Schedule[] schedules = method.getAnnotationsByType(Schedule.class);

第四部分:并发编程

4.1 线程基础

// 方式一:继承 Thread
new Thread() {
    @Override public void run() { System.out.println("Running"); }
}.start();

// 方式二:实现 Runnable(推荐)
new Thread(() -> System.out.println("Running")).start();

// 方式三:Callable + Future(有返回值)
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<String> future = executor.submit(() -> {
    Thread.sleep(1000);
    return "Done";
});
String result = future.get();  // 阻塞等待结果

4.2 java.util.concurrent 核心组件

线程池

// 推荐的线程池创建方式(手动控制参数,避免 OOM)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    4,              // corePoolSize:核心线程数
    8,              // maximumPoolSize:最大线程数
    60, TimeUnit.SECONDS,  // 空闲线程存活时间
    new ArrayBlockingQueue<>(100),  // 任务队列
    new ThreadFactory() {
        private final AtomicInteger counter = new AtomicInteger();
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "worker-" + counter.incrementAndGet());
            t.setDaemon(false);
            return t;
        }
    },
    new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略:调用者线程执行
);

为什么不推荐 Executors 的快捷方法

  • Executors.newFixedThreadPool() → 无界队列,可能 OOM

  • Executors.newCachedThreadPool() → 无界线程数,可能 OOM

  • Executors.newSingleThreadExecutor() → 无界队列,可能 OOM

阿里巴巴 Java 开发手册明确禁止使用 Executors 创建线程池。

CompletableFuture

// 链式异步编排
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 异步任务 1
    return fetchDataFromApi();
}).thenApply(data -> {
    // 处理结果
    return data.toUpperCase();
}).thenAccept(result -> {
    // 消费结果
    System.out.println(result);
}).exceptionally(ex -> {
    // 异常处理
    System.err.println("Error: " + ex.getMessage());
    return null;
});

// 多个 CompletableFuture 组合
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World");

// 全部完成后执行
CompletableFuture.allOf(f1, f2).thenRun(() -> {
    String combined = f1.join() + " " + f2.join();
    System.out.println(combined);  // "Hello World"
});

// 任一完成后执行
CompletableFuture.anyOf(f1, f2).thenAccept(result -> {
    System.out.println("First completed: " + result);
});

4.3 并发集合

集合

特点

适用场景

ConcurrentHashMap

分段锁/CAS,高并发下性能优秀

高并发缓存

CopyOnWriteArrayList

写时复制,读无锁

读多写少(如配置列表)

ConcurrentLinkedQueue

无锁队列,CAS 实现

高并发生产者-消费者

ArrayBlockingQueue

有界阻塞队列

线程池任务队列

LinkedBlockingQueue

可选有界的阻塞队列

生产者-消费者

DelayQueue

延迟队列,元素到期才能取

定时任务、超时处理

4.4 锁机制

synchronized vs ReentrantLock

// synchronized:内置锁,JVM 级别优化(偏向锁 → 轻量级锁 → 重量级锁)
public synchronized void increment() {
    count++;
}

// ReentrantLock:API 级别锁,更灵活
private final ReentrantLock lock = new ReentrantLock();

public void doSomething() {
    lock.lock();
    try {
        // 临界区
    } finally {
        lock.unlock();  // 必须在 finally 中释放
    }
}

// ReentrantLock 高级功能
ReentrantLock lock = new ReentrantLock();

// 尝试获取锁(不阻塞)
if (lock.tryLock()) {
    try { /* ... */ } finally { lock.unlock(); }
}

// 超时等待
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try { /* ... */ } finally { lock.unlock(); }
}

// 公平锁(按请求顺序分配)
ReentrantLock fairLock = new ReentrantLock(true);

ReadWriteLock

ReadWriteLock rwLock = new ReentrantReadWriteLock();

public String read() {
    rwLock.readLock().lock();
    try {
        return data;  // 多个读线程可并发
    } finally {
        rwLock.readLock().unlock();
    }
}

public void write(String newData) {
    rwLock.writeLock().lock();  // 写锁互斥
    try {
        this.data = newData;
    } finally {
        rwLock.writeLock().unlock();
    }
}

4.5 CAS 与原子类

CAS(Compare-And-Swap)是一种无锁并发机制,通过硬件指令实现:

// AtomicInteger:基于 CAS 的原子整数
AtomicInteger counter = new AtomicInteger(0);

counter.incrementAndGet();      // 原子自增,线程安全
counter.compareAndSet(5, 10);   // 如果当前值是5,则设为10
counter.getAndUpdate(x -> x * 2);  // 原子更新

// 原子引用
AtomicReference<User> userRef = new AtomicReference<>();
userRef.compareAndSet(null, new User("Alice"));

// LongAdder:高并发下比 AtomicLong 更快(分段计数)
LongAdder longAdder = new LongAdder();
longAdder.increment();
long total = longAdder.sum();  // 求总和

4.6 虚拟线程(Virtual Threads)—— Java 21+

虚拟线程是 Java 21 引入的革命性特性,也是 JDK 25 进一步完善的核心功能。它将线程映射到操作系统线程的方式从"一对一"变为"多对一",一个 OS 线程可以运行成千上万个虚拟线程。

// 传统线程:每个线程绑定一个 OS 线程,内存开销大(约 1MB/线程)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    // 虚拟线程:轻量级,每个仅几百字节,可以轻松创建百万级
    IntStream.range(0, 1_000_000).forEach(i -> {
        executor.submit(() -> {
            Thread.sleep(Duration.ofSeconds(1));  // 挂起时不占用 OS 线程
            return i;
        });
    });
}  // 自动等待所有虚拟线程完成

虚拟线程 vs 平台线程

特性

平台线程(传统)

虚拟线程

OS 线程映射

一对一

多对一(M:N)

内存开销

~1MB/线程

~几百字节/线程

创建成本

高(系统调用)

低(JVM 内部)

数量上限

几千

百万级

适用场景

CPU 密集型

I/O 密集型

阻塞行为

阻塞 OS 线程

卸载(unmount)释放 OS 线程

生产环境建议(2026 年):

  • JDK 25 对虚拟线程做了进一步优化,生产环境已经可以大规模使用

  • 虚拟线程最适合 I/O 密集型应用(Web 服务、数据库调用、HTTP 请求)

  • CPU 密集型任务仍然建议使用平台线程池

  • 不要使用线程池来管理虚拟线程,让调度器自动调度

4.7 结构化并发(Structured Concurrency)—— Java 25/26 Preview

结构化并发让多线程代码像结构化代码一样清晰:

// JEP 525(Java 26 第六次预览)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    // 启动子任务
    Subtask<User> userTask = scope.fork(() -> findUser(userId));
    Subtask<Order> orderTask = scope.fork(() -> fetchOrder(orderId));

    // 等待所有子任务完成,任一失败则取消其余
    scope.join().throwIfFailed();

    // 处理结果
    User user = userTask.get();
    Order order = orderTask.get();
    return new Response(user, order);
}  // 作用域结束,所有子任务保证已完成或已取消

第五部分:JVM 内存模型与垃圾回收

5.1 JVM 内存结构

JVM 运行时数据区
├── 线程私有
│   ├── 程序计数器(PC Register):当前线程执行的字节码行号
│   ├── 虚拟机栈(JVM Stack):方法调用栈帧(局部变量表、操作数栈)
│   └── 本地方法栈(Native Method Stack):Native 方法调用
├── 线程共享
│   ├── 堆(Heap):对象实例、数组(GC 的主要区域)
│   │   ├── 新生代(Young Generation)
│   │   │   ├── Eden 区(新对象分配)
│   │   │   └── Survivor 区(S0/S1,Minor GC 后存活对象)
│   │   └── 老年代(Old Generation)
│   │       └── 经历多次 GC 后仍然存活的对象
│   └── 元空间(Metaspace):类元数据、常量池、方法信息
│       └── JDK 8 替代了永久代(PermGen),使用本地内存
└── 直接内存(Direct Memory):NIO 的 ByteBuffer.allocateDirect()

5.2 对象的生命周期

// 1. 类加载阶段:JVM 将 .class 文件加载到元空间
Class<?> clazz = Class.forName("com.example.User");

// 2. 对象创建:new 指令在堆上分配内存
User user = new User("Alice");  // Eden 区分配

// 3. 对象访问:通过引用访问对象
String name = user.getName();

// 4. 对象可达性分析:GC 从 GC Roots 开始追踪
//    - GC Roots 包括:栈帧中的局部变量、静态变量、JNI 引用等
//    - 无法从 GC Roots 到达的对象 → 可回收

// 5. 对象晋升:Eden → Survivor → Old Generation
//    - 默认经过 15 次 Minor GC 后晋升到老年代(-XX:MaxTenuringThreshold)
//    - 大对象直接进入老年代(-XX:PretenureSizeThreshold)

5.3 垃圾回收算法

算法

原理

优点

缺点

标记-清除

标记存活对象,清除未存活

简单

内存碎片

标记-复制

将存活对象复制到另一个区域

无碎片,分配快

浪费一半空间

标记-整理

将存活对象向一端移动

无碎片

移动开销大

分代收集

新生代用复制,老年代用标记-整理

结合实际,高效

实现复杂

5.4 主流垃圾收集器

G1 Garbage Collector(JDK 9+ 默认)

G1 将堆划分为多个大小相等的 Region,不再区分新生代/老年代的物理边界。
优势:可预测的停顿时间(-XX:MaxGCPauseMillis=200)
# 启用 G1 并配置参数
java -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:G1HeapRegionSize=4m \
     -Xms4g -Xmx4g \
     -jar app.jar

ZGC(JDK 15+ 生产可用,JDK 21 分代 ZGC)

ZGC 使用染色指针和读屏障技术,停顿时间不超过 1ms。
JDK 21 引入分代 ZGC(Generational ZGC),结合分代回收优势,吞吐量大幅提升。
# 启用 ZGC(JDK 21+)
java -XX:+UseZGC -Xms4g -Xmx4g -jar app.jar

# 分代 ZGC(默认启用)
java -XX:+UseZGC -XX:+ZGenerational -Xms4g -Xmx4g -jar app.jar

收集器

JDK 版本

停顿时间

吞吐量

适用场景

G1

JDK 9+ 默认

10-200ms

通用场景

ZGC

JDK 15+ 生产,JDK 21 分代

< 1ms

中-高

低延迟要求

Shenandoah

JDK 12+

< 1ms

中-高

大内存场景

Parallel

JDK 8 默认

较长

最高

批处理

Serial

32 位系统

小内存/客户端

5.5 内存泄漏排查

# 1. OOM 时自动 dump 堆
java -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/tmp/heapdump.hprof \
     -jar app.jar

# 2. 手动触发 dump
jmap -dump:format=b,file=heap.hprof <pid>

# 3. 分析 dump 文件
# 使用 MAT(Memory Analyzer Tool)、JProfiler、YourKit 等工具

# 4. 查看 GC 日志
java -Xlog:gc*:file=gc.log:time,uptime,level,tags \
     -jar app.jar

# 5. 实时监控
jstat -gcutil <pid> 1000  # 每秒输出 GC 统计
jcmd <pid> GC.run         # 手动触发 GC

常见内存泄漏场景

// 场景一:静态集合未清理
public class Cache {
    private static final Map<String, Object> cache = new HashMap<>();
    public static void put(String key, Object value) {
        cache.put(key, value);  // 永远不删除,最终 OOM
    }
}

// 场景二:未关闭资源
InputStream is = new FileInputStream("file.txt");
// is.close();  // 忘记关闭
// 正确做法:
try (InputStream is = new FileInputStream("file.txt")) {
    // try-with-resources 自动关闭
}

// 场景三:ThreadLocal 未清理
private static final ThreadLocal<Context> context = new ThreadLocal<>();
context.set(new Context());
// context.remove();  // 线程池复用时,ThreadLocal 不会自动清理

5.6 JIT 编译

JVM 通过 JIT(Just-In-Time)编译器将热点代码编译为机器码:

// C1(Client Compiler):快速编译,优化较少,适合启动阶段
// C2(Server Compiler):深度优化,编译慢,适合运行阶段
// GraalVM AOT:提前编译为本地机器码,启动即高性能

// 查看 JIT 编译信息
java -XX:+PrintCompilation -jar app.jar
// 输出: 123  456    3  java.lang.String::hashCode (55 bytes)
//       ^   ^     ^  ^
//     编译ID 编译方法级别 编译字节数

// 方法内联:JVM 自动将小方法内联到调用点
// -XX:MaxInlineSize=35  // 最大内联字节码大小(默认 35)
// -XX:FreqInlineSize=325  // 频繁调用方法的最大内联大小

5.7 AOT 缓存(JDK 26 新特性)

JDK 26 引入了 AOT Caching,支持所有 GC 类型:

# 生成 AOT 缓存
java -XX:AOTMode=store -jar app.jar
# 下次启动时自动加载 AOT 缓存,跳过 JIT 预热阶段
# 启动时间显著缩短,特别适合容器化部署

第六部分:类加载机制

6.1 类加载过程

1. 加载(Loading):将 .class 文件字节码读入 JVM,创建 Class 对象
2. 链接(Linking)
   ├── 验证(Verification):字节码合法性检查
   ├── 准备(Preparation):为 static 字段分配内存,赋默认值
   └── 解析(Resolution):符号引用转为直接引用
3. 初始化(Initialization):执行 static 代码块和 static 字段初始值

6.2 双亲委派模型

Bootstrap ClassLoader(启动类加载器)
    ├── 加载 Java 核心类库(java.lang.*, java.util.* 等)
    ├── C++ 实现,Java 代码无法引用
    │
    ├──> Extension ClassLoader(扩展类加载器)
    │       ├── 加载 ext 目录中的类
    │       ├── sun.misc.Launcher$ExtClassLoader
    │       │
    │       └──> Application ClassLoader(应用类加载器)
    │               ├── 加载 classpath 中的类
    │               ├── sun.misc.Launcher$AppClassLoader
    │               │
    │               └──> Custom ClassLoader(自定义类加载器)

双亲委派原则:当类加载器收到加载请求时,先委托给父类加载器,只有父类加载器无法加载时,才尝试自己加载。

破坏双亲委派的场景

  • SPI 机制(Service Provider Interface):JDBC 驱动、JNDI 等通过 Thread.getContextClassLoader() 加载

  • Tomcat:每个 Web 应用独立 ClassLoader,优先加载应用自己的类

  • OSGi:模块化热部署,每个 Bundle 独立 ClassLoader

6.3 自定义类加载器

public class MyClassLoader extends ClassLoader {
    private final Path classPath;

    public MyClassLoader(Path classPath) {
        super(MyClassLoader.class.getClassLoader());  // 父加载器
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 父加载器先加载(双亲委派)
        try {
            return getParent().loadClass(name);
        } catch (ClassNotFoundException e) {
            // 父加载器无法加载,自己加载
        }

        try {
            Path path = classPath.resolve(name.replace('.', '/') + ".class");
            byte[] bytes = Files.readAllBytes(path);
            return defineClass(name, bytes, 0, bytes.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        }
    }
}

第七部分:Java 新特性(Java 21-26)

7.1 Record 类(Java 16+)

不可变数据类,自动生成 equalshashCodetoString、构造器和 getter:

// 传统方式需要几十行代码
public record User(String name, int age, String email) {
    // 紧凑构造函数(用于参数校验)
    public User {
        if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
    }

    // 自定义方法
    public String greeting() {
        return "Hello, " + name;
    }
}

// 使用
User user = new User("Alice", 25, "alice@example.com");
System.out.println(user.name());      // getter 是 name() 而不是 getName()
System.out.println(user.greeting());  // "Hello, Alice"

7.2 Pattern Matching(模式匹配)

instanceof 模式匹配(Java 16+)

// 旧写法
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

// 新写法
if (obj instanceof String s) {
    System.out.println(s.length());  // s 在 if 块中自动可用
}

switch 模式匹配(Java 21+)

// 旧写法
String formatted = switch (obj.getClass().getSimpleName()) {
    case "Integer" -> "int: " + ((Integer) obj).intValue();
    case "Long"    -> "long: " + ((Long) obj).longValue();
    case "String"  -> "string: " + ((String) obj);
    default        -> "unknown";
};

// 新写法
String formatted = switch (obj) {
    case Integer i -> "int: " + i.intValue();
    case Long l    -> "long: " + l.longValue();
    case String s  -> "string: " + s;
    case null      -> "null";
    default        -> "unknown: " + obj;
};

Record 模式匹配(Java 21+)

sealed interface Shape permits Circle, Rectangle, Triangle { }
record Circle(double radius) implements Shape { }
record Rectangle(double width, double height) implements Shape { }
record Triangle(double base, double height) implements Shape { }

double area(Shape shape) {
    return switch (shape) {
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle t -> 0.5 * t.base() * t.height();
    };  // 编译器保证穷尽,无需 default
}

7.3 Sealed Classes(密封类,Java 17+)

限制哪些类可以继承当前类:

// 密封类:只允许指定的类继承
public sealed interface Result permits Success, Failure { }

public final class Success implements Result {
    private final Object data;
    public Success(Object data) { this.data = data; }
    public Object getData() { return data; }
}

public final class Failure implements Result {
    private final Throwable error;
    public Failure(Throwable error) { this.error = error; }
    public Throwable getError() { return error; }
}

// 不能有其他类实现 Result
// public class Unknown implements Result { }  // 编译错误

密封类 + 模式匹配是 Kotlin 的 sealed class 和 Rust 的 enum 的 Java 版本。

7.4 String Templates(字符串模板,Java 21+ Preview)

String name = "Alice";
int age = 25;

// 旧写法
String info = String.format("Name: %s, Age: %d", name, age);

// 新写法(STR 是内置模板处理器)
String info = STR."Name: \{name}, Age: \{age}";

// 可以自定义模板处理器
String json = JSON."""
    {
        "name": "\{name}",
        "age": \{age}
    }
    """;

7.5 外部函数与内存 API(Foreign Function & Memory API,Java 22+)

替代 JNI 的安全高效方式,用于调用 C 代码和访问堆外内存:

// 调用 C 标准库的 strlen
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
    stdlib.find("strlen").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);

try (Arena arena = Arena.ofConfined()) {
    MemorySegment cString = arena.allocateUtf8String("Hello, World!");
    long len = (long) strlen.invoke(cString);
    System.out.println("Length: " + len);  // 12
}

7.6 Java 25(LTS)核心特性

2025 年 9 月发布的 LTS 版本包含 18 个 JEP:

JEP

特性

说明

各种

虚拟线程优化

性能调优,生产就绪

各种

紧凑对象头

减少对象内存占用

各种

G1/ZGC 性能优化

提升吞吐量和延迟

各种

外部内存 API

安全的堆外内存管理

各种

向量 API

SIMD 指令加速

各种

抗量子密码学

新的安全算法

7.7 Java 26 核心特性

2026 年 3 月发布的非 LTS 版本:

JEP

特性

说明

JEP 500

Final Will Actually Mean Final

禁止反射修改 private final 字段

JEP 525

结构化并发(第六次预览)

更清晰的多线程编程模型

JEP 526

惰性常量(第二次预览)

延迟初始化的常量

各种

AOT 缓存

所有 GC 都支持 AOT 缓存

各种

HTTP/3 支持

原生 HTTP/3 客户端


第八部分:设计模式与最佳实践

8.1 Builder 模式(Lombok 辅助)

// Lombok 方式
@Builder
public class User {
    private String name;
    private int age;
    private String email;
}

// 使用
User user = User.builder()
    .name("Alice")
    .age(25)
    .email("alice@example.com")
    .build();

8.2 策略模式(函数式替代)

// 传统策略模式
interface DiscountStrategy {
    double applyDiscount(double price);
}

class NoDiscount implements DiscountStrategy {
    public double applyDiscount(double price) { return price; }
}

class PercentageDiscount implements DiscountStrategy {
    private final double percentage;
    public PercentageDiscount(double p) { this.percentage = p; }
    public double applyDiscount(double price) { return price * (1 - percentage); }
}

// 函数式替代(推荐)
public class PriceCalculator {
    public static double apply(double price, UnaryOperator<Double> strategy) {
        return strategy.apply(price);
    }
}

// 使用
double finalPrice = PriceCalculator.apply(100.0, p -> p * 0.9);  // 9折

8.3 工厂模式 + Record

public sealed interface Message permits TextMessage, ImageMessage, VideoMessage {
    String content();
}

public record TextMessage(String text) implements Message {
    public String content() { return text; }
}
public record ImageMessage(String url, int width, int height) implements Message {
    public String content() { return "[Image: " + url + "]"; }
}
public record VideoMessage(String url, int duration) implements Message {
    public String content() { return "[Video: " + url + "]"; }
}

// 工厂方法
public class MessageFactory {
    public static Message create(String type, String content) {
        return switch (type) {
            case "text"  -> new TextMessage(content);
            case "image" -> new ImageMessage(content, 800, 600);
            case "video" -> new VideoMessage(content, 120);
            default -> throw new IllegalArgumentException("Unknown type: " + type);
        };
    }
}

8.4 Optional 的正确用法

// ❌ 错误用法
Optional<User> optionalUser = userRepository.findById(id);
if (optionalUser.isPresent()) {
    User user = optionalUser.get();
    System.out.println(user.getName());
}

// ✅ 正确用法
userRepository.findById(id)
    .ifPresent(user -> System.out.println(user.getName()));

// 有默认值
String name = userRepository.findById(id)
    .map(User::getName)
    .orElse("Unknown");

// 抛异常
User user = userRepository.findById(id)
    .orElseThrow(() -> new UserNotFoundException(id));

// 链式操作
Optional<String> email = userRepository.findById(id)
    .filter(u -> u.isActive())
    .map(User::getEmail)
    .filter(e -> e.endsWith("@example.com"));

第九部分:常见问题 FAQ

Q1: Java 泛型为什么用类型擦除而不是具体化?

为了向后兼容。Java 5 引入泛型时,需要保证泛型代码与 JDK 1.4 及之前的二进制兼容。如果像 C++ 的模板那样在编译期生成具体类型的代码,就会破坏这一兼容性。C# 的泛型是具体化的(运行期保留类型信息),但 C# 没有历史包袱。

Q2: 什么时候用虚拟线程,什么时候用传统线程池?

场景

推荐方案

Web 请求处理

虚拟线程

HTTP 客户端调用

虚拟线程

数据库查询(阻塞 JDBC)

虚拟线程(或 R2DBC + 协程)

CPU 密集型计算(图像处理、加密)

平台线程池

需要控制并发度的场景

平台线程池(虚拟线程的并发度由调度器控制)

Q3: JVM 默认使用哪个垃圾收集器?

  • JDK 8:Parallel GC

  • JDK 9+:G1 GC

  • JDK 21+ 大内存场景:考虑分代 ZGC

Q4: 内存泄漏如何定位?

  1. 开启 -XX:+HeapDumpOnOutOfMemoryError

  2. 使用 MAT 分析 dump 文件,查找 Dominator Tree

  3. 看哪些对象占用最多内存,追踪其引用链

  4. 常见原因:静态集合、未关闭的连接、ThreadLocal 未清理、监听器未注销

Q5: JDK 26 的 “Final Will Actually Mean Final” 会影响哪些库?

所有使用反射修改 private final 字段的库都会受影响,包括:

  • Jackson / Gson 的字段注入反序列化

  • Mockito 的 Mock 对象创建

  • 某些序列化/ORM 框架

这些库已经适配了新的 API(使用 MethodHandleUnsafe 的替代方案或新的序列化协议)。

Q6: Record 类可以继承其他类吗?

不能。Record 隐式继承 java.lang.Record,Java 不支持多重继承。但 Record 可以实现接口

Q7: 什么时候升级到 Java 21/25?

  • 如果你还在用 Java 8:强烈建议升级到 Java 21(LTS)或 Java 25(最新 LTS)

  • Java 21 引入了虚拟线程、模式匹配、Record 模式匹配、String Templates 等大量特性

  • Spring Boot 3.x 要求最低 Java 17,Spring Boot 4.x 将要求 Java 21

  • Java 8 → Java 21 的迁移路径已经很成熟,通常不需要改代码


附录:JVM 调优参数速查

# 内存设置
-Xms2g                    # 初始堆大小
-Xmx4g                    # 最大堆大小
-XX:MetaspaceSize=256m    # 元空间初始大小
-XX:MaxMetaspaceSize=512m # 元空间上限

# GC 选择
-XX:+UseG1GC              # 使用 G1(JDK 9+ 默认)
-XX:+UseZGC               # 使用 ZGC
-XX:MaxGCPauseMillis=200  # G1 最大停顿目标(毫秒)

# 调试
-XX:+HeapDumpOnOutOfMemoryError  # OOM 时自动 dump
-XX:HeapDumpPath=/tmp/heap.hprof # dump 文件路径
-Xlog:gc*:file=gc.log      # GC 日志(JDK 9+)
-XX:+PrintGCDetails        # 详细 GC 日志(JDK 8)

# 性能
-XX:+UseStringDeduplication  # G1 字符串去重
-XX:+AlwaysPreTouch          # 启动时预分配内存(减少首次 GC 延迟)
-XX:MaxInlineSize=35         # 方法内联阈值

总结

Java 的高级特性可以分为三个层次:

  1. 语言层:泛型、反射、注解、Record、模式匹配、密封类 —— 让代码更安全、更简洁

  2. 并发层:线程池、CompletableFuture、虚拟线程、结构化并发 —— 让程序充分利用多核和 I/O

  3. JVM 层:内存模型、垃圾回收、类加载、JIT 编译 —— 理解运行时行为,调优排查

从 Java 8 到 Java 26,语言本身在快速进化。虚拟线程、模式匹配、Record、密封类等特性正在改变我们写 Java 的方式。理解这些高级特性,不仅能让你写出更好的代码,也能让你在使用 Spring、MyBatis 等框架时不再"知其然而不知其所以然"。