一次 JVM 类加载异常

文章目录

    • 1. JVM 类加载异常
      • 1. 出现问题
      • 2. 解决过程
        • 1. JDK 7 版本过老
        • 2. JDK 8 小版本无关
        • 3. 缺少 SunEC version 1.8 加密包
        • 4. 强制添加加密包无效
        • 5. 找到 JVM 类加载问题
      • 3. 解决方案

1. JVM 类加载异常

1. 出现问题

  • 使用 HTTP 协议对接第三方服务,本地测试可以正常成功,测试环境连接第三方失败,项目使用的 maven 是 httpclient-4.5.1
  • 抛出的异常为
    Javax.net.ssl.SSLException: Received fatal alert: protocol_version
    Javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

2. 解决过程

1. JDK 7 版本过老

  • 通过异常得知 SSL 加密协议版本问题,查询第三方文档,得知仅支持 HTTPS 使用 TLS 1.2 加密方式
  • 排查发现本地使用 JDK 1.8.0_221,线上使用的是 JDK 7,通过搜索可知 JDK 7 调用第三方 HTTPS 接口、默认使用的是 TLS1 加密方式,而非 TLS 1.2 加密方式,JDK 1.8 默认 TLS 1.2
  • 细看 HTTP 调用工具类使用了 SSLContext.getInstance(“TLSv1.2”) 强制改变加密方式,但是依然异常,加 System.setProperty("javax.net.debug", "all"); 打印出网络调试日志,发现调用异常且还是使用的 TLS 1
    一次 JVM 类加载异常_第1张图片

2. JDK 8 小版本无关

  • 改动业务到使用了 jdk1.8.0_131 的微服务上,仍有异常、但异常改变为
    Caused by: Java.security.NoSuchAlgorithmException: Cannot find any provider supporting DESede/CBC/PKCS5Padding
  • 仔细对比本地使用的是 jdk1.8.0_321,通过 Oracle 官方文档可知 8u211 相对 8u202 有较大更新,因此测试环境尝试升级 jdk1.8.0_131,还是相同异常,发现与小版本无关

3. 缺少 SunEC version 1.8 加密包

  • 根据异常,本地 debug 查到 Javax.crypto.Cipher 类,通过在线 JDK8 文档查到加密类型是 Java 平台均支持的(意思和本地使用 Mac,测试环境使用 Linux 无关),但确实在加密时无法获取 DESede/CBC/PKCS5Padding ,同时也尝试其他加密方式异常相同

一次 JVM 类加载异常_第2张图片

  • 后续通过搜索查到添加:System.out.println(Arrays.toString(Security.getProviders())); 发现 Linux 环境缺少了 SunEC version 1.8 加密包
  • 本地 Mac:[SUN version 1.8, SunRsaSign version 1.8, SunEC version 1.8, SunJSSE version 1.8, SunJCE version 1.8, SunJGSS version 1.8, SunSASL version 1.8, XMLDSig version 1.8, SunPCSC version 1.8, Apple version 1.8]
  • 测试 Linux:[SUN version 1.8, SunRsaSign version 1.8, SunJSSE version 1.8, SunJGSS version 1.8, SunSASL version 1.8, XMLDSig version 1.8, SunPCSC version 1.8]

4. 强制添加加密包无效

  • 然后测试环境强制添加:Security.addProvider(new com.sun.crypto.provider.SunJCE());
  • 抛出异常,还是找不到 jar 包
    com.sun.crypto.provider.SunJCE() 2022-04-19 17:16:09,144 \[// - - -\] ERROR com.alibaba.dubbo.rpc.filter.ExceptionFilter - \[DUBBO\] Got unchecked and undeclared exception which called by xxx.xxx.xxx.xxx. service: com.xxx.xxx, method: xxx, exception: Java.lang.NoClassDefFoundError: com/sun/crypto/provider/SunJCE, dubbo version: x.x.x, current host: xxx.xxx.xxx.xxx Java.lang.NoClassDefFoundError: com/sun/crypto/provider/SunJCE
  • Linux 下载 JDK 查对应包,是存在的
    \$JAVA_HOME/jre/lib The 4 jar packages in the / directory are as follows: jce.jar security/US_export_policy.jar security/local_policy.jar ext/sunjce_provider.jar

5. 找到 JVM 类加载问题

  • 随后想到,问题就是 JVM 类加载时无法加载到这个类,查到这个类具体在 JRE 的:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunec.jar
  • 隐约想起《码出高效》上面写过,Extension ClassLoader 加载了 jre/lib/ext/*.jar
  • 正好对上了,那就在项目中搜索这几个关键词,发现启动脚本使用了:-DJava.ext.dirs=$DEPLOY_DIR/lib
  • 搜索发现,这个命令会覆盖 Java 本身的 ext 包的加载,而去加载指定 pom 文件的 jar 包,找到 jar 目录发现其他加密包均存在,果然少了 SunJCE 加密包
  • 因此使用此加解密算法就会抛出异常

3. 解决方案

  • 有三种解决方案
    • 将 ext 相关 jar 包复制到当前 lib 目录,重启加载此 jar 包
    • -DJava.ext.dirs 配置多个目录,重启加载此 jar 包
    • 将 jar 包打包发布到 Nexus Maven 私服,使用 Maven 依赖即可

你可能感兴趣的:(解决真实问题,jvm,java,linux,jdk)