JVM classpath中jar包加载顺序


发布于 2025-06-05 / 3 阅读 / 0 评论 /
Java进程中,jar包的加载顺序

SetClassPath

JVM在初始化时,会设置一个环境变量java.class.path,用于记录当前classpath中的所有jar包、配置文件等,且是有顺序的。对应源码在java.base/share/native/libjli/java.c文件中,对应的方法为SetClassPath,如下所示:

static void
SetClassPath(const char *s)
{
    char *def;
    const char *orig = s;
    static const char format[] = "-Djava.class.path=%s";
    /*
     * usually we should not get a null pointer, but there are cases where
     * we might just get one, in which case we simply ignore it, and let the
     * caller deal with it
     */
    if (s == NULL)
        return;
    s = JLI_WildcardExpandClasspath(s);
    if (sizeof(format) - 2 + JLI_StrLen(s) < JLI_StrLen(s))
        // s is became corrupted after expanding wildcards
        return;
    size_t defSize = sizeof(format)
                       - 2 /* strlen("%s") */
                       + JLI_StrLen(s);
    def = JLI_MemAlloc(defSize);
    snprintf(def, defSize, format, s);
    AddOption(def, NULL);
    if (s != orig)
        JLI_MemFree((char *) s);
    _have_classpath = JNI_TRUE;
}

JLI_WildcardExpandClasspath

JLI_WildcardExpandClasspath 是 Java 启动器中用于处理类路径通配符(*)的核心函数,定义在java.base/share/native/libjli/wildcard.c文件中。源码如下:

const char *
JLI_WildcardExpandClasspath(const char *classpath)
{
    const char *expanded;
    JLI_List fl;

    if (JLI_StrChr(classpath, '*') == NULL)
        return classpath;
    fl = JLI_List_split(classpath, PATH_SEPARATOR);
    expanded = FileList_expandWildcards(fl) ?
        JLI_List_join(fl, PATH_SEPARATOR) : classpath;
    JLI_List_free(fl);
    if (getenv(JLDEBUG_ENV_ENTRY) != 0)
        printf("Expanded wildcards:\n"
               "    before: \"%s\"\n"
               "    after : \"%s\"\n",
               classpath, expanded);
    return expanded;
}

主要功能有:

(1)展开类路径中的通配符*,将其替换为匹配的JAR文件列表

(2)只匹配以.jar或.JAR结尾的文件

(3)不递归处理子目录

(4)文件匹配顺序不保证特定顺序

FileList_expandWildcards

FileList_expandWildcards 是 Java 启动器中用于展开类路径通配符的核心函数,定义在java.base/share/native/libjli/wildcard.c文件中。源码如下:

static int
FileList_expandWildcards(JLI_List fl)
{
    size_t i, j;
    int expandedCnt = 0;
    for (i = 0; i < fl->size; i++) {
        if (isWildcard(fl->elements[i])) {
            JLI_List expanded = wildcardFileList(fl->elements[i]);
            if (expanded != NULL && expanded->size > 0) {
                expandedCnt++;
                JLI_MemFree(fl->elements[i]);
                JLI_List_ensureCapacity(fl, fl->size + expanded->size);
                for (j = fl->size - 1; j >= i+1; j--)
                    fl->elements[j+expanded->size-1] = fl->elements[j];
                for (j = 0; j < expanded->size; j++)
                    fl->elements[i+j] = expanded->elements[j];
                i += expanded->size - 1;
                fl->size += expanded->size - 1;
                /* fl expropriates expanded's elements. */
                expanded->size = 0;
            }
            JLI_List_free(expanded);
        }
    }
    return expandedCnt;
}

主要功能是:

(1)遍历字符串列表(JLI_List),查找并展开包含通配符(*)的路径元素

(2)将每个通配符路径替换为匹配的JAR文件列表

(3)返回展开操作的数量

wildcardFileList

wildcardFileList 函数中,文件是通过 WildcardIterator_next 逐个读取并添加到列表中的。源码如下:

static JLI_List
wildcardFileList(const char *wildcard)
{
    const char *basename;
    JLI_List fl = JLI_List_new(16);
    WildcardIterator it = WildcardIterator_for(wildcard);

    if (it == NULL)
    {
        JLI_List_free(fl);
        return NULL;
    }

    while ((basename = WildcardIterator_next(it)) != NULL)
        if (isJarFileName(basename))
            JLI_List_add(fl, wildcardConcat(wildcard, basename));
    WildcardIterator_close(it);
    return fl;
}

无论是 Windows 的 FindNextFile 还是 Unix 的 readdir,获取的文件顺序都是依赖于文件系统的实现,没有额外的排序逻辑。

classpath顺序说明

在java.base/share/native/libjli/wildcard.c文件中,有这样一段注释:

 * The order in which the jar files in a directory are enumerated in
 * the expanded class path is not specified and may vary from platform
 * to platform and even from moment to moment on the same machine.  A
 * well-constructed application should not depend upon any particular
 * order.  If a specific order is required then the jar files can be
 * enumerated explicitly in the class path.

也就是说,如果通过通配符加目录的形式指定classpath,JVM会解析这个目录下的所有jar文件,遍历顺序是不固定的,存在偶然性。

如果一个class文件在a.jar和b.jar中,如果在java.class.path中a.jar

JVM在class加载过程中,从java.class.path的jar包按从前往后的顺序加载,加载成功class后,就停止往后继续遍历。