2021年5月

JDBC 无法建立到 Oracle 数据库的连接

A 程序使用和 B 程序一样的数据库访问用户名, 密码, 主机名, 端口等配置, 可是 A 程序无法建立到数据库的连接, B 程序却可以.

A 程序的代码在建立连接的时候, 只是打印了一句: 无法建立连接, 再也没任其它信息.

首先, 怀疑是不是防火墙问题 -> 使用 telnet 和 curl 测试了一下端口, 发现端口完全可以建立正常的 tcp 连接;

虽然可以建立 tcp 连接, 可是并不一定不是防火墙问题, 因为防火墙可能根据协议规则等建立, 允许基本的 tcp 端口连接;

下一步, 在建立连接的时候, 进行 tcp 抓包, 重要找到了问题所在: 原来在建立连接之后的协商阶段, 发生了错误->

...AUTH_PID...1234....AUTH_SID.  /ORA-28040: No matching authentication protocol

原来是认证协议不匹配, 还给出了 Oracle 官方的错误 ID.

根据这个错误 ID, 我们搜索一下, 发现基本是客户端JDBC 版本和服务器版本不一致造成的.

最近Oracle 服务器升级到 19c, 可是客户端还是用的适配 11c 的 JDBC 连接, 所以导致出现了这个错误.

为什么程序 B 没问题呢? 程序 B 使用的是 JDK 8, 它的 jdbc 驱动也升级了, 所以完全没问题. 然而程序 A 是一个基于 JDK 7 的程序, 它无法升级到最新的 JDBC 驱动, 因为 Oracle 最新的 JDBC 驱动只支持最低 JDK 8.

所以, 为了解决这个问题, 程序 A 应该升级 JDK 最低为 JDK8.

Java ForkJoinPool 的 worker thread 是如何自我终止的?

最近研究一个 Java 应用程序的性能问题, 当观察该应用程序的线程的时候, 发现有很多 ForkJoinPool 的 worker 线程, 这种线程数量很大, 并从线程的编号来看, 该应用创建了非常多的这样线程, 然后它们又逐渐被销毁.

下面是一个 Thread dump 的截图, 从截图中可以看到, 这种线程基本处于 2 种状态: TERMINATED 和 TIMED_WAITING. 理论来讲, 应该还有 Runnable 状态的, 只是没有出现在我们的 thread dump 中.
bad_method.png

通过 Btrace 代码注入, 我们很容易找到了错误使用线程池的代码:
forkJoin.png

这段代码在一个 Stream 的代码块中, 使用 Executors.newWorkStealingPool() 创建了一个 ForkJoinPool. 意味着每次调用这个方法, 都要创建一个新的 ForkJoinPool. 然而这正是线程池本来设计要解决的问题: 不要重复创建线程, 要复用.

所以, 解决办法就是替换这个线程池的创建和使用方式, 使用应用生命周期内复用线程的方法.

虽然上面的问题解决了, 那么还有个疑惑的地方: 我们看到上面线程池的编号是一个非常大的数字, 说明它已经被创建了这么多线程池了, 可是我们现在活着的总的线程数才几千, 说明早期创建的线程池都已经消亡了. 从那些 worker 的状态: TERMINATED 来看, 这些线程正在死亡, 最终线程池也会消亡.

一般情况下, 想让一个线程池终止, 我们都是通过调用线程池的 shutdown() 方法或者 shutdownNow() 方法. 可是我们观察上面线程池创建的地方, 并没有任何对象引用这个线程池, 那么是哪里调用这个 shutdown() 或者 shutdownNow() 方法的呢? 看上去这是一个矛盾的问题: 没用人调用 shutdown() 或者 shutdownNow(), 但是这些智能的 worker 线程却在慢慢消亡.

为了查清 worker 线程智能消亡的问题, 我们写了如下的 Btrace 代码来查找 worker 线程消亡的原因:

import static org.openjdk.btrace.core.BTraceUtils.jstackStr;
import static org.openjdk.btrace.core.BTraceUtils.println;
import org.openjdk.btrace.core.annotations.BTrace;
import org.openjdk.btrace.core.annotations.OnMethod;

@BTrace 
public class ForkJoinPoolTrace {

    /**
     * 用来查找那里创建了 ForkJoinPool
    @OnMethod( clazz="/java\\.util\\.concurrent\\.ForkJoinPool/", method="/<init>/" )
    public static void createPool() {
        println(jstackStr());
    }**/
    
    @OnMethod( clazz="/java\\.util\\.concurrent\\.ForkJoinPool/", method="/shutdown/" )
    public static void shutdownPool() {
        println(jstackStr());
    }
    
    @OnMethod( clazz="/java\\.util\\.concurrent\\.ForkJoinPool/", method="/tryTerminate/" )
    public static void tryTerminate() {
        println(jstackStr());
    }
    
    @OnMethod( clazz="/java\\.util\\.concurrent\\.ForkJoinPool/", method="/shutdownNow/" )
    public static void shutdownPoolNow() {
        println(jstackStr());
    } 
}

最终找到了让 worker 线程消亡的原因: 是在 awaitWorker() 方法中使用了 tryTerminate() 方法.

java.util.concurrent.ForkJoinPool.tryTerminate(ForkJoinPool.java)
java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1807)
java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1693)
java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:175)

其实终止的原因就是: 如果 worker 线程发现很久没有活可以干, 那么就 自我终止吧.
ForkJoinPool.awaitWork 方法:
whyT.png

可能有人会问, 线程消亡了, 那么创建的那些 ForkJoinPool 呢? 这些对象要靠 Thread 活着, 如果那些 Worker Thread 都终止了, 那么这些对象必然会被回收掉.