ojdbc11.jar能直接跑在虚拟线程上,因其已适配JVM挂起/恢复机制,阻塞I/O调用不再固定载体线程,且移除了对isVirtual()的规避逻辑,无需额外开关即可响应jvm.VirtualThreadPinned事件。
oracle jdbc 21c 驱动(即 ojdbc11.jar,对应 jdk 21)原生支持虚拟线程,但需配合 jvm 层调度与连接池策略才能真正释放 vt 优势;单独升级驱动不等于自动获得百万并发能力。
为什么 ojdbc11.jar 能直接跑在虚拟线程上
Oracle JDBC Driver 21c(版本 21.10+)已将所有阻塞 I/O 调用(如 SocketInputStream.read()、SocketOutputStream.write())适配 JVM 的虚拟线程挂起/恢复机制。当虚拟线程执行 Connection.createStatement() 或 ResultSet.next() 等可能触发网络等待的操作时,JVM 会自动将其从载体线程卸载,不阻塞底层 OS 线程。
关键前提是:必须使用 JDK 21+(非预览模式),且驱动类路径中为 ojdbc11.jar(不是 ojdbc8.jar 或更老版本)。
- 老版本驱动(如 ojdbc8)在虚拟线程中调用
executeQuery()会强制“固定”(pin)到载体线程,导致 VT 失效,退化为平台线程行为 - ojdbc11 内部已移除对
Thread.currentThread().isVirtual()的规避逻辑,能正确响应jdk.VirtualThreadPinnedJFR 事件 - 无需额外设置
oracle.jdbc.useVirtualThreads=true这类开关——它不存在,也不需要
压测时最容易踩的坑:连接池没换,VT 白搭
即使 JDBC 驱动和 JVM 都就绪,若仍用 HikariCP 或 Druid 默认配置,VT 的并发密度会立刻被连接池掐死:
-
HikariCP默认maximumPoolSize=10,10 个连接 + 10 万个虚拟线程 = 99990 个线程在排队等连接,吞吐卡在连接获取阶段 - 连接池本身是平台线程安全的,但其内部锁(如
ConcurrentBag)在高 VT 并发下会成为热点,getConnection()耗时飙升 - 正确做法是改用无池或极轻量连接管理:例如压测脚本中直接
DriverManager.getConnection()(仅限短连接、低延迟 DB 场景),或启用 Oracle 自研的Universal Connection Pool (UCP)并开启connectionWaitTimeout=0
示例(压测客户端):
立即学习“Java免费学习笔记(深入)”;
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 50_000; i++) {
executor.submit(() -> {
try (var conn = DriverManager.getConnection(
"jdbc:oracle:thin:@//localhost:1521/XEPDB1",
"scott", "tiger")) {
try (var stmt = conn.createStatement();
var rs = stmt.executeQuery("SELECT 1 FROM DUAL")) {
rs.next();
}
}
});
}
}
JDBC 虚拟线程压测的关键监控项
光看 QPS 和响应时间会误判。真实瓶颈往往藏在 JVM 底层调度细节里:
- 观察
jdk.VirtualThreadPinnedJFR 事件频次:> 0 表示有 VT 被强制 pinned,说明某处调用了不兼容的 native 方法或 synchronized 块过长 - 检查
java.lang.Thread.activeCount()—— 它返回的是平台线程数(通常 ≈ CPU 核心数),而非 VT 总数;VT 总数应查jdk.VirtualThreadStart累计数 - 禁用连接池后,注意 Oracle 服务端的
processes参数:默认 300,5 万 VT 并发直连会触发 ORA-12516;需调大或改用共享服务器模式
真正难的不是让 JDBC 跑在 VT 上,而是让整个数据访问链路(驱动 → 连接获取 → SQL 执行 → 结果解析)全程不触发 pinning、不争抢共享锁、不突破服务端资源上限。这些点一旦漏掉一个,压测数据就会从“提升 6 倍”跌回“和线程池差不多”。


















