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值无关 将不同的版本进行打包时,就会出现相应的问题
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。


