返回符杰博客列表

tiny集成oracle驱动在ibm jdk下启动报错的分析

发布于 9月前

前一阵子用websphere部署项目的遇到了一个很诡异的问题,websphere错误堆栈如下:

[17-10-9 13:57:33:503 GMT+08:00] 000000fe SystemErr     R Exception in thread "annotation-muti-file-processor-thread-1" java.lang.NoClassDefFoundError: sun.security.krb5.internal.KdcErrException
[17-10-9 13:57:33:503 GMT+08:00] 000000fe SystemErr     R 	at java.lang.Class.getDeclaredMethodsImpl(Native Method)
[17-10-9 13:57:33:503 GMT+08:00] 000000fe SystemErr     R 	at java.lang.Class.getDeclaredMethods(Class.java:1008)
[17-10-9 13:57:33:503 GMT+08:00] 000000fe SystemErr     R 	at org.tinygroup.annotation.impl.AnnotationExecuteManagerImpl.processMethodProcessor(AnnotationExecuteManagerImpl.java:131)
[17-10-9 13:57:33:504 GMT+08:00] 000000fe SystemErr     R 	at org.tinygroup.annotation.impl.AnnotationExecuteManagerImpl.processClassFileObject(AnnotationExecuteManagerImpl.java:96)
[17-10-9 13:57:33:504 GMT+08:00] 000000fe SystemErr     R 	at org.tinygroup.annotation.fileresolver.AnnotationClassFileProcessor$1.callBack(AnnotationClassFileProcessor.java:55)
[17-10-9 13:57:33:504 GMT+08:00] 000000fe SystemErr     R 	at org.tinygroup.fileresolver.impl.FileProcessorThread.action(FileProcessorThread.java:45)
[17-10-9 13:57:33:504 GMT+08:00] 000000fe SystemErr     R 	at org.tinygroup.threadgroup.AbstractProcessor.run(AbstractProcessor.java:50)
[17-10-9 13:57:33:504 GMT+08:00] 000000fe SystemErr     R 	at java.lang.Thread.run(Thread.java:785)
[17-10-9 13:57:33:504 GMT+08:00] 000000fe SystemErr     R Caused by: java.lang.ClassNotFoundException: sun.security.krb5.internal.KdcErrException
[17-10-9 13:57:33:504 GMT+08:00] 000000fe SystemErr     R 	at java.net.URLClassLoader.findClass(URLClassLoader.java:609)
[17-10-9 13:57:33:505 GMT+08:00] 000000fe SystemErr     R 	at com.ibm.ws.bootstrap.ExtClassLoader.findClass(ExtClassLoader.java:243)
[17-10-9 13:57:33:505 GMT+08:00] 000000fe SystemErr     R 	at java.lang.ClassLoader.loadClassHelper(ClassLoader.java:850)
[17-10-9 13:57:33:505 GMT+08:00] 000000fe SystemErr     R 	at java.lang.ClassLoader.loadClass(ClassLoader.java:829)
[17-10-9 13:57:33:505 GMT+08:00] 000000fe SystemErr     R 	at com.ibm.ws.bootstrap.ExtClassLoader.loadClass(ExtClassLoader.java:134)
[17-10-9 13:57:33:505 GMT+08:00] 000000fe SystemErr     R 	at java.lang.ClassLoader.loadClass(ClassLoader.java:809)
[17-10-9 13:57:33:505 GMT+08:00] 000000fe SystemErr     R 	at com.ibm.ws.classloader.ProtectionClassLoader.loadClass(ProtectionClassLoader.java:62)
[17-10-9 13:57:33:505 GMT+08:00] 000000fe SystemErr     R 	at com.ibm.ws.classloader.ProtectionClassLoader.loadClass(ProtectionClassLoader.java:58)
[17-10-9 13:57:33:505 GMT+08:00] 000000fe SystemErr     R 	at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:586)
[17-10-9 13:57:33:506 GMT+08:00] 000000fe SystemErr     R 	at java.lang.ClassLoader.loadClass(ClassLoader.java:809)
[17-10-9 13:57:33:506 GMT+08:00] 000000fe SystemErr     R 	at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:586)
[17-10-9 13:57:33:506 GMT+08:00] 000000fe SystemErr     R 	at java.lang.ClassLoader.loadClass(ClassLoader.java:809)
[17-10-9 13:57:33:506 GMT+08:00] 000000fe SystemErr     R 	... 8 more

这。。。。我用sun的jdk跑着好好的怎么一到ibm的jdk就报错,从错误日志看到是由于tiny的注解解析器调用了Class.getDeclaredMethods获取所有method引起的,但是从日志中无法看出是哪个class文件抛出来的这个异常。好吧,只能采用万能的debug模式了(ps:websphere的启动是真的巨慢无比,每次debug都相当的让人抓狂啊),经过漫长的debug之后(解析的class文件实在太多),终于发现是oracle.net.ano.AuthenticationService这个类在作祟,原来是ojdbc的驱动包的问题。

    把oracle.net.ano.AuthenticationService这个class反编译出来之后发现其中有个方法:

 private final byte[] a(final GSSContext gssContext, final byte[] array) throws KdcErrException, KrbApErrException, KrbCryptoException, Asn1Exception, RealmException, IOException

确实是这个方法抛出了kdcErrException这个异常,那么问题来了,难道ibm的jdk没有这个class?想到这里,我把sun jdk的rt.jar和ibm jdk的rt.jar做了个对比,发现确实有一些不同。

blob.png

ibm这一块确实缺失了一些class,那么ibm关于krb5这一块的包在哪呢,最后在ibmjgssprovider.jar中发现了

blob.png

然而ojdbc的驱动包指名道姓的import sun.security.krb5.internal.KdcErrException;自然在ibm的环境就找不到class了。问题的根源是发现了,不过好像没办法从根源来解决,毕竟ojdbc是官方的东西。鉴于目前项目貌似没用到这个注解解析器,注释掉可以暂时解决这个问题。

问题到这已经告一段落了,不过有个问题我还是觉得蛮奇怪的,按照我之前的理解,class在装载进jvm的时候并不会同时装载该class中import的所有class,为了证实这一点,我做了个测试:

public class Main {
    public static void main(String[] args) {
        try {
            Class.forName("oracle.net.ano.AuthenticationService");
            //oracle.net.ano.AuthenticationService.class.getDeclaredMethods();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

我在执行上述代码的时候并不会报错,也就是说只有当我执行getDeclaredMethods()这个方法时,跟方法有关的class才会被装载进jvm(虽然我只是获取了method对象,并没有执行)。为了了解getDeclaredMethods这个方法真正如何被执行的,我追踪了一下class的源码(下面是sun的class源码和ibm的有点区别不过大体一致):

@CallerSensitive
public Method[] getDeclaredMethods() throws SecurityException {
    checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
    return copyMethods(privateGetDeclaredMethods(false));
}

private Method[] privateGetDeclaredMethods(boolean publicOnly) {
    checkInitted();
    Method[] res;
    ReflectionData<T> rd = reflectionData();//ReflectionData保存了class的Fields,Methods,Constructor等信息
    if (rd != null) {
       res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
       if (res != null) return res;
    }
    // No cached value available; request value from VM
    res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
    if (rd != null) {
        if (publicOnly) {
            rd.declaredPublicMethods = res;
        } else {
            rd.declaredMethods = res;
        }
    }
    return res;
}
private ReflectionData<T> reflectionData() {
    SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;//这里将reflecttiondata作为一个软引用,以便在堆内存不足时可以释放这一部分的内存
    int classRedefinedCount = this.classRedefinedCount;
    ReflectionData<T> rd;
    if (useCaches &&
        reflectionData != null &&
        (rd = reflectionData.get()) != null &&
        rd.redefinedCount == classRedefinedCount) {
        return rd;
    }
    // else no SoftReference or cleared SoftReference or stale ReflectionData
    // -> create and replace new instance
    return newReflectionData(reflectionData, classRedefinedCount);
}

可以看出当有缓存时,method是直接从cache中获取的,否则调用jvm的native方法getDeclaredMethods0,因为sun关于hotpot这部分不开源,只能从网上找了open jdk的hotpot的源码来看,想来应该不会差很多。追踪进来发现该方法的申明在jvm.cpp里:

JVM_ENTRY(jobjectArray, JVM_GetClassDeclaredMethods(JNIEnv *env, jclass ofClass, jboolean publicOnly))
{
  JVMWrapper("JVM_GetClassDeclaredMethods");
  return get_class_declared_methods_helper(env, ofClass, publicOnly,
                                           /*want_constructor*/ false,
                                           SystemDictionary::reflect_Method_klass(), THREAD);
}
JVM_END

static jobjectArray get_class_declared_methods_helper(
                                  JNIEnv *env,
                                  jclass ofClass, jboolean publicOnly,
                                  bool want_constructor,
                                  Klass* klass, TRAPS) {
    JvmtiVMObjectAllocEventCollector oam;
    ......
    for (int i = 0; i < num_methods; i++) {
        methodHandle method(THREAD, k->method_with_idnum(idnums->at(i)));
        if (method.is_null()) {
          // Method may have been deleted and seems this API can handle null
          // Otherwise should probably put a method that throws NSME
          result->obj_at_put(i, NULL);
        } else {
          oop m;
          if (want_constructor) {
            m = Reflection::new_constructor(method, CHECK_NULL);
          } else {
            m = Reflection::new_method(method, UseNewReflection, false, CHECK_NULL);//这里将每一个method的实例new了出来
          }
          result->obj_at_put(i, m);
        }
    }
    ......
}

从上面可以看出重点就在Reflection中的new_method方法:

oop Reflection::new_method(methodHandle method, bool intern_name, bool for_constant_pool_access, TRAPS) {
    ......
    oop return_type_oop = NULL;
    objArrayHandle parameter_types = get_parameter_types(method, parameter_count, &return_type_oop, CHECK_NULL);//获取参数类型和返回类型
    if (parameter_types.is_null() || return_type_oop == NULL) return NULL;
    Handle return_type(THREAD, return_type_oop);
    objArrayHandle exception_types = get_exception_types(method, CHECK_NULL);//获取exception的类型
    if (exception_types.is_null()) return NULL;
    ......
}

c++的代码好歹之前还是学过的,虽然只能看懂个大概。从上可以看出对于每一个method实例会获取其参数类型,返回类型和异常类型,所以与此相关的class都会被加载,这也就证实了为什么没有调用oracle.net.ano.AuthenticationService的相关方法也会加载kdrrException的原因。至于ibm jdk采用j9vm想必这块的实现方法差异也不是很大所以才导致了上述的错误。

 
相关信息
     标签
       附件
      文件 标题 创建者

      评分 0次评分

      日程