前言

呵呵 最近碰到一些 老项目, 使用的传统的 libs 文件夹管理依赖, 显然存在的问题就是 jar 的依赖的问题 

然后 之前呢, 也碰到了一些 依赖的相关问题 

1. 异常:Class net.sf.cglib.core.DebuggingClassWriter overrides final method visit

2. tomcat启动内存堆栈溢出ASN1EncodableVector,DEREncodableVector循环依赖
 

我们这里来看的问题便是 "tomcat启动内存堆栈溢出ASN1EncodableVector,DEREncodableVector循环依赖" 

本文会使用到 classpath中存在多个jar存在同限定名的class classloader会如何加载 相关的知识, 下文会提及放出链接

以下调试 java 层面的调试基于 jdk8 

测试用例

新建一个 web application, 创建 pom.xml, 新建 HelloWorldServlet, 然后配置 tomcat, 配置 artificates 等等相关事项, 这里不赘述 

当然以下用例 和 我们这里核心要描述的东西没有必要的关联, 以下用例只是为了测试 web application 启动正常 

package com.hx.test;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * HelloWorldServlet
 *
 * @author Jerry.X.He 
 * @version 1.0
 * @date 2021-05-22 11:11
 */
@WebServlet("/hello/world")
public class HelloWorldServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(" do get ");
        resp.getOutputStream().println(" do get ");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(" do post ");
        resp.getOutputStream().println(" do post ");
    }

}

pom.xml 文件内容如下 

如下两个依赖 才是导致这里的问题的核心的东西 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hx</groupId>
    <artifactId>HelloWebApplication</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <HXLog.version>0.0.2</HXLog.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk14</artifactId>
            <version>1.38</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.52</version>
        </dependency>
    </dependencies>


</project>

然后启动项目, 偶尔会报错如下, 但是 偶尔的情况下 又不会报错 

呵呵 所以 我们这里所需要明确的问题有两个 

1. 为什么会出现这个循环依赖的报错 ?

2. 为什么是偶尔报错 ?

30-May-2021 09:36:02.767 WARNING [RMI TCP Connection(2)-127.0.0.1] org.apache.tomcat.util.descriptor.web.WebXml.setVersion Unknown version string [4.0]. Default version will be used.
30-May-2021 09:36:12.474 SEVERE [RMI TCP Connection(2)-127.0.0.1] org.apache.catalina.core.ContainerBase.addChildInternal ContainerBase.addChild: start: 
	org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[]]
		at org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:440)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:198)
		at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:743)
		at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:719)
		at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:691)
		at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1767)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:286)
		at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
		at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
		at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:483)
		at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:431)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:286)
		at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
		at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
		at com.sun.jmx.remote.security.MBeanServerAccessController.invoke(MBeanServerAccessController.java:468)
		at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1468)
		at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:76)
		at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1309)
		at java.security.AccessController.doPrivileged(Native Method)
		at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1408)
		at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:829)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:357)
		at sun.rmi.transport.Transport$1.run(Transport.java:200)
		at sun.rmi.transport.Transport$1.run(Transport.java:197)
		at java.security.AccessController.doPrivileged(Native Method)
		at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
		at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
		at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
		at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
		at java.security.AccessController.doPrivileged(Native Method)
		at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
		at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
		at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
		at java.lang.Thread.run(Thread.java:748)
	Caused by: java.lang.IllegalStateException: Unable to complete the scan for annotations for web application [] due to a StackOverflowError. Possible root causes include a too low setting for -Xss and illegal cyclic inheritance dependencies. The class hierarchy being processed was [org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector->org.bouncycastle.asn1.ASN1EncodableVector]
		at org.apache.catalina.startup.ContextConfig.checkHandlesTypes(ContextConfig.java:2071)
		at org.apache.catalina.startup.ContextConfig.processAnnotationsStream(ContextConfig.java:2009)
		at org.apache.catalina.startup.ContextConfig.processAnnotationsJar(ContextConfig.java:1961)
		at org.apache.catalina.startup.ContextConfig.processAnnotationsUrl(ContextConfig.java:1931)
		at org.apache.catalina.startup.ContextConfig.processAnnotations(ContextConfig.java:1887)
		at org.apache.catalina.startup.ContextConfig.processClasses(ContextConfig.java:1186)
		at org.apache.catalina.startup.ContextConfig.webConfig(ContextConfig.java:1093)
		at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:779)
		at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:299)
		at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:123)
		at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5066)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		... 44 more
30-May-2021 09:36:12.478 SEVERE [RMI TCP Connection(2)-127.0.0.1] org.apache.tomcat.util.modeler.BaseModelMBean.invoke Exception invoking method manageApp
	java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[]]
		at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:747)
		at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:719)
		at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:691)
		at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1767)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:286)
		at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
		at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
		at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:483)
		at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:431)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:286)
		at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
		at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
		at com.sun.jmx.remote.security.MBeanServerAccessController.invoke(MBeanServerAccessController.java:468)
		at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1468)
		at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:76)
		at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1309)
		at java.security.AccessController.doPrivileged(Native Method)
		at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1408)
		at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:829)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:357)
		at sun.rmi.transport.Transport$1.run(Transport.java:200)
		at sun.rmi.transport.Transport$1.run(Transport.java:197)
		at java.security.AccessController.doPrivileged(Native Method)
		at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
		at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
		at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
		at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
		at java.security.AccessController.doPrivileged(Native Method)
		at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
		at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
		at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
		at java.lang.Thread.run(Thread.java:748)
30-May-2021 09:36:12.479 SEVERE [RMI TCP Connection(2)-127.0.0.1] org.apache.tomcat.util.modeler.BaseModelMBean.invoke Exception invoking method createStandardContext
	javax.management.RuntimeOperationsException: Exception invoking method manageApp
		at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:294)
		at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
		at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
		at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:483)
		at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:431)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:286)
		at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
		at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
		at com.sun.jmx.remote.security.MBeanServerAccessController.invoke(MBeanServerAccessController.java:468)
		at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1468)
		at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:76)
		at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1309)
		at java.security.AccessController.doPrivileged(Native Method)
		at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1408)
		at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:829)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:357)
		at sun.rmi.transport.Transport$1.run(Transport.java:200)
		at sun.rmi.transport.Transport$1.run(Transport.java:197)
		at java.security.AccessController.doPrivileged(Native Method)
		at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
		at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
		at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
		at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
		at java.security.AccessController.doPrivileged(Native Method)
		at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
		at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
		at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
		at java.lang.Thread.run(Thread.java:748)
	Caused by: java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[]]
[2021-05-30 09:36:12,495] Artifact HelloWebService:war exploded: Error during artifact deployment. See server log for details.
		at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:747)
		at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:719)
		at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:691)
		at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1767)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:286)
		... 35 more
30-May-2021 09:36:12.572 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/Library/ProgramFiles/apache-tomcat-8.5.66/webapps/manager]
30-May-2021 09:36:12.719 INFO [localhost-startStop-1] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
30-May-2021 09:36:12.757 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/Library/ProgramFiles/apache-tomcat-8.5.66/webapps/manager] has finished in [186] ms

当报错的情况下, 访问我们提供的 /hello/world 的请求, 会报告 404, 并且 当前项目的服务均不可用 

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_java

没报错的情况下, 就是当前项目的服务都正常可用 

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_tomcat_02

1. 为什么会出现这个循环依赖的报错 ?

我们根据堆栈信息找到出现 StackOverflow 的地方, 我们这里上下文的 cacheEntry 表示的 org.bouncycastle.asn1.ASN1EncodableVector 相关的信息[图上体现不出来, 但是上一个栈帧可以看到, 这里不多截图]

然后从这里可以看到 org.bouncycastle.asn1.ASN1EncodableVector 的基类是 org.bouncycastle.asn1.DEREncodableVector, 然后 我们继续往下跟  

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_stackoverflow_03

我们继续往下看, org.bouncycastle.asn1.ASN1EncodableVector 的基类是 org.bouncycastle.asn1.DEREncodableVector, 然后上面的处理会递归处理 org.bouncycastle.asn1.DEREncodableVector 

因此 这个栈帧截图, cacheEntry 对应的是 org.bouncycastle.asn1.DEREncodableVector 的关联的信息 

然后我们看一下 ??, 擦 为什么 org.bouncycastle.asn1.DEREncodableVector 的基类又是 org.bouncycastle.asn1.ASN1EncodableVector 这是什么情况 ?? 

1. 这是第一点, 这两个类在一定的情况下 为什么相互继承了对方? 

2. 第二点是 这个无限递归的构成, 从如下代码可以看到, populateSCIsForCacheEntry 的一个必要条件是 cacheEntry.sciSet == null, 再看一点 sciSet 的设置是在 方法的末尾, 处理完成了之后 才设置 

        因此, 这就构成了程序代码上的无限递归, ASN1EncodableVector 初始化的时候发现基类是 DEREncodableVector, 并且 DEREncodableVector 的 sciSet 为 null 

        开始初始化 DEREncodableVector, 初始化的时候发现基类是 ASN1EncodableVector, 并且 ASN1EncodableVector 的 sciSet 为 null, 又来处理 ASN1EncodableVector

        开始初始化 ASN1EncodableVector, 初始化的时候发现基类是 DEREncodableVector, 并且 DEREncodableVector 的 sciSet 为 null, 又来处理 DEREncodableVector

        ...... 省略 n 个步骤

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_stackoverflow_04

这两个类在一定的情况下 为什么相互继承了对方?

我们搜索一下 org.bouncycastle.asn1.ASN1EncodableVector 和 org.bouncycastle.asn1.DEREncodableVector

会发现我们添加的两个依赖里面都有 org.bouncycastle.asn1.ASN1EncodableVector 和 org.bouncycastle.asn1.DEREncodableVector

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_tomcat_05

那么我们来看一下 org.bouncycastle.asn1.ASN1EncodableVector 和 org.bouncycastle.asn1.DEREncodableVector 在这两个不同的包里面的细节 

在 bcprov-jdk14-1.38 版本中 org.bouncycastle.asn1.ASN1EncodableVector 继承自 org.bouncycastle.asn1.DEREncodableVector

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_java_06

在 bcprov-jdk14-1.38 版本中 org.bouncycastle.asn1.DEREncodableVector 继承自 java.lang.Object 

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_apache_07

在 bcprov-jdk15on-1.52 版本中 这两个类的情况是反过来的 

org.bouncycastle.asn1.ASN1EncodableVector 是继承自 java.lang.Object

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_apache_08

bcprov-jdk15on-1.52 版本中 org.bouncycastle.asn1.DEREncodableVector 继承自 org.bouncycastle.asn1.ASN1EncodableVector 

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_stackoverflow_09

从上面的运行时调试情况来说, 我们可以推导 上面加载的 org.bouncycastle.asn1.ASN1EncodableVector 应该是来自于 bcprov-jdk14-1.38.jar 

然后 加载的 org.bouncycastle.asn1.DEREncodableVector 应该是来自于 bcprov-jdk15on-1.52.jar 

然后 才出现了这个我们逻辑上看到的两个 类相互继承的情况 

我们来看一下 加载的 org.bouncycastle.asn1.ASN1EncodableVector 的情况 

可以看出, 果然是来自于 bcprov-jdk14-1.38.jar 

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_java_10

再来看一下 加载 org.bouncycastle.asn1.DEREncodableVector 的情况 

来自于 javaClassCache, 那么这个 javaClassCache 是从哪里添加的 org.bouncycastle.asn1.DEREncodableVector 呢 

是在 populateSCIsForCacheEntry 再之前的一个步骤, populateJavaClassCache, 不过我们这里核心关注如何加载的 org.bouncycastle.asn1.DEREncodableVector 

在 WebappClassloader 里面加载 org.bouncycastle.asn1.DEREncodableVector, 从 classpath 依次选择, 两个 jar 中都有 org.bouncycastle.asn1.DEREncodableVector, 因此这里选择了靠前的 bcprov-jdk15on-1.52.jar  

因此关于 循环依赖的这个情况, 大致就是这样了 

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_apache_11

2. 为什么是偶尔报错 ?

首先我们需要梳理一下 那些地方可能会造成顺序出现不固定 ? 

1. 就是我们上面 processAnnotationsJar 遍历的 jar 的顺序 

2. 就是我们 context.getLoader().getClassLoader().getResourceAsStream(name) 加载类库用到的 jar 

我们上面 processAnnotationsJar 遍历的 jar 的顺序 

经过多次测试 我们这里的额 fragments 的顺序确实有些时候会不一样 

但是看一个细节 fragments 是 LinkedHashSet, 期望应该是保持一定的顺序阿?, 为什么会出现这种情况 

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_java_12

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_apache_13

当然追查这个原因还是花费了不少时间, 呵呵 这里直接进入重点  

这个 otherSet 是一个普通的 HashSet, 并且元素类型为 WebXml, 并且没有重写 hashCode, equals, 因此 偶尔会导致 otherSet 遍历的顺序不固定, 以至于 后面添加进入 orderedFragments 中这两个 jar 的顺序不固定 

从这里的代码, 上下文语义 这里的 orderedFragments 我更倾向于是 jar 和 container fragments 这两大类存在顺序 

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_java_14

再重试一次 这里 otherSet 中元素顺序又不一致, 导致后面的 orderedFragments 中的元素的顺序的不确定 

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_java_15

context.getLoader().getClassLoader().getResourceAsStream(name) 加载类库用到的 jar 

从实际的测试得知, 每次 context.getLoader().getClassLoader().getResourceAsStream(name) 里面得到的 org.bouncycastle.asn1.DEREncodableVector 总是 bcprov-jdk15on-1.52.jar 的 jar 

我们来看下为什么 ?

这里是注册 jar 到 StandardRoot 里面 allResources[3] [等价于jarResources] 的地方, 我们看一下 外面的获取列表的地方 

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_java_16

外层的 jar 列表来自于 listResources("WEB-INF/lib"), 加载的 WEB-INF/lib 下面的各种资源[preResource, mainResource, classResource, jarResources, postResources], 我们这里 着重关注 jarResources

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_stackoverflow_17

最终使用的是 File.list 来加载文件列表 

看 File.list 文档介绍的是不保证顺序, 但是多次测试 返回的 列表都是一致的, 因此我们假设 StandardRoot 中的 jarResources 的顺序一致 

context.getLoader().getClassLoader().getResourceAsStream(name) 加载类库用到的 jar, 每次均使用 bcprov-jdk15on-1.52.jar  

82 关于tomcat启动org.bouncycastle.asn1.ASN1EncodableVector->org.bouncycastle.asn1.DEREncodableVector循环依赖_tomcat_18

参考

tomcat启动内存堆栈溢出ASN1EncodableVector,DEREncodableVector循环依赖
Jenkins自动部署到Tomcat随机失败的问题

classpath中存在多个jar存在同限定名的class classloader会如何加载