Jar加载顺序的简介说明
下文笔者讲述java中jar加载顺序的简介说明,如下所示
今天遇到奇怪的现象 一个应用在一台服务器上运行正常, 另一台服务器上运行异常 仔细查看日志 原来就是 java.lang.NoSuchMethodError 加载jar异常,导致数据jar异常
那么如何处理此类异常
排查两个系统是否一致 1.linux版本 2.jdk版本 3.tomcat版本 4.程序版本 5.系统环境变量是否一致 最后测试发现类的加载顺序不同 一台机器上加载正确的版本,另一台机器加载了一个错误版本的jar包 所以才会出现异常
为什么不同机器,加载类不同呢?
类加载入口
/** * Load the class with the specified name. This method searches for * classes in the same manner as <code>loadClass(String, boolean)</code> * with <code>false</code> as the second argument. * * @param name The binary name of the class to be loaded * * @exception ClassNotFoundException if the class was not found */ @Override public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } 首次class文件从jar包中找到的过程 调用StandardRoot.getResourceInternal寻找class 顺序就是循环allResources(格式:list<List<WebResourceSet>>) protected final WebResource getResourceInternal(String path, boolean useClassLoaderResources) { WebResource result = null; WebResource virtual = null; WebResource mainEmpty = null; for (List<WebResourceSet> list : allResources) { for (WebResourceSet webResourceSet : list) { if (!useClassLoaderResources && !webResourceSet.getClassLoaderOnly() || useClassLoaderResources && !webResourceSet.getStaticOnly()) { result = webResourceSet.getResource(path); if (result.exists()) { return result; } if (virtual == null) { if (result.isVirtual()) { virtual = result; } else if (main.equals(webResourceSet)) { mainEmpty = result; } } } } } ... } 集合classResources存了WEB-INF/lib目录的Jar资源 在Tomcat启动时调用processWebInfLib()方法初始化。 /** protected void processWebInfLib() throws LifecycleException { WebResource[] possibleJars = listResources("/WEB-INF/lib", false); for (WebResource possibleJar : possibleJars) { if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) { createWebResourceSet(ResourceSetType.CLASSES_JAR, "/WEB-INF/classes", possibleJar.getURL(), "/"); } } } 最终在DirResourceSet类list(String path)方法 其实调用的是java.io.File类list()方法 list调的是UnixFileSystem的native的list()方法 无法保证结果数组中的名称字符串将以任何特定的顺序出现 尤其不能保证它们按字母顺序出现。
OpenJDK
jdk8对应的OpenJDK源码,UnixFileSystem的list方法,调用的是目录操作函数opendir.JNIEXPORT jobjectArray JNICALL Java_java_io_UnixFileSystem_list(JNIEnv *env, jobject this, jobject file) { DIR *dir = NULL; struct dirent64 *ptr; struct dirent64 *result; int len, maxlen; jobjectArray rv, old; WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) { dir = opendir(path); } END_PLATFORM_STRING(env, path); if (dir == NULL) return NULL; ptr = malloc(sizeof(struct dirent64) + (PATH_MAX + 1)); if (ptr == NULL) { JNU_ThrowOutOfMemoryError(env, "heap allocation failed"); closedir(dir); return NULL; } ... }
Linux操作系统
继续向下查操作系统,opendir返回值定义struct dirent { ino_t d_ino; /* Inode number */ off_t d_off; /* Not an offset; see below */ unsigned short d_reclen; /* Length of this record */ unsigned char d_type; /* Type of file; not supported by all filesystem types */ char d_name[256]; /* Null-terminated filename */ }; 对readdir()的连续调用读取文件名的顺序取决于文件系统实现 不太可能以任何方式对文件名进行排序。 命令ll -f与opendir函数readdir顺序相同
文件系统
CentOS 6使用的是Ext4 文件顺序与目录文件的大小是否超过一个磁盘块和文件系统计算的Hash值有关
由于Java语言的跨平台特性 在class首次从jar中找到对应的文件时 查找的顺序是文件操作系统实现决定 与inode值无关 将不同的版本进行打包时,就会出现相应的问题
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。