WIN10 下 “java -cp“ 命令解析

背景

最近在在项目中遇到了一个类加载的问题,几经周折没有找到合适的解决方式,只能怪自己学艺不精。没办法只好重拾曾今丢掉的 java 知识,尝试从源头开始分析问题。

环境

  • Win 10 企业版
    java version “1.8.0_251”
    Java™ SE Runtime Environment (build 1.8.0_251-b08)
    Java HotSpot™ 64-Bit Server VM (build 25.251-b08, mixed mode)

问题描述:

我所遇到的是一个类重复加载的问题,但是这个问题目前并不能完美复现。因此我希望能够通过查看jvm的类加载过程来判断类重复加载的原因,可以在运行时结束一下参数:

java -verbose:class -cp *.jar com.example.Application

但是此时我又遇到问题了,我的 -cp *.jar参数并没有生效。我的初衷是希望能够加载到当前目录下的所有jar包,但是实际情况是并没有加载到。经过多次试验,在这里记录一下我所犯下的错误以及错误的原因。

问题1. java -verbose:class -cp *.jar com.example.Application 加载失败

在这里插入图片描述
可以从图片看到,jvm只是简单的加载了一些启动所需要的类,而我指定的 jar 包一个也没有加载到。从而导致错误 找不到或无法加载主类 com.example.Application ,这是因为在 windows 环境下 *.jar 的通配符并没有生效,正确的通配符的使用方式为:

java -verbose:class -cp * com.example.Application

可以看到,需要移除*.jar中的.jar,只剩下通配符即可。

问题2. java -verbose:class -cp . com.example.Application

-cp .,小朋友,你是否有很多问号,在初学安装配置 java 时,教程里往往会要求你在环境变量下配置一个 CALSSPATH的环境变量,并且要求值为:

.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;  

还小心翼翼的嘱咐你不要忘记加前面的 .,还会告诉你.代表当前目录,不知道有没有思考过JVM到底从当前目录下加载了什么?反正我是没有思考过,总之是在从网上查-cp 测资料时,我在这里碰壁了。
在这里插入图片描述

经过试验,我可以简单的总结为.所代表的是 JVM 会加载当前目录下的文件,但是并不是全部文件,而是相对应的字节码文件。也就是 JVM 会把当前路径作为 classpath,因此会解析当前path下的 class,至于当前 path 下的 jar包嘛,你不指定的话JVM肯定是当做没看见了。

示例

当前我有如下目录

D:\demo01
│  hutool-all-5.4.3.jar
│
└─com
    └─example
            Main.class
            Main.java

可以看到 demo01 文件夹下有一个至尊神器 hutool-all 的 jar 包,然后对应有一个 com.example.Main.java java 源码文件,还有一个经过命令编译得到的 com.example.Main.class的 java 字节码文件,编译命令如下:

javac -cp hutool-all-5.4.3.jar com/example/Main.java

对应的com.example.Main.java的源码:

package com.example;
import cn.hutool.core.lang.Console;

public class Main{
   public static void main(String[] args){
     System.out.println("Hello World");
     Console.log("hello world by hutool!");
   }
}

可以看到我在 Main.java中引入了hutool-alljar包中的一个类,当我执行如下命令时:

D:\demo01>java -cp . com.example.Main
Hello World
Exception in thread "main" java.lang.NoClassDefFoundError: cn/hutool/core/lang/Console
        at com.example.Main.main(Main.java:7)
Caused by: java.lang.ClassNotFoundException: cn.hutool.core.lang.Console
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 1 more

可以看到在执行 Console.log("hello world by hutool!") 时发生了找不到类的错误,这是因为在指定 classpath时只指定了当前目录,但没有指定 hutool-all 的 jar 包导致的。在这种情况下 JVM只会读取当前目录下所有对应的编译后的类文件并且执行。

而当我换一种方式指定 classpath 时:

D:\demo01>java -cp * com.example.Main
错误: 找不到或无法加载主类 com.example.Main

可以看到,此时JVM处于找不到我的 com.example.Main.class 类的状态,由此可见 -cp *的命令并没有让 JVM 读取到当前目录下的字节码文件,而且由于没有找到入口类,所以也不清楚JVM是否有加载 hutool-alljar包。

至此,可以看到,我们一次也没有让代码正常执行过。现在,我们退而求其次,先不考虑通配符的问题,而是先尝试让我们的代码跑起来。从上面的代码执行可以看到-cp .可以让JVM读取当前目录下的字节码文件。现在我们只需添加指定一个 hutool-all.jar 即可让我们的代码跑起来。在 windows 下,分隔不同 jar 包的分隔符是 ;

D:\demo01>java -cp .;hutool-all-5.4.3.jar com.example.Main
Hello World
hello world by hutool!

可以看到我们的代码正常执行了。

现在,我们再回过头来试试我们的通配符*

D:\demo01>java -cp ".;*.jar" com.example.Main
Hello World
Exception in thread "main" java.lang.NoClassDefFoundError: cn/hutool/core/lang/Console
        at com.example.Main.main(Main.java:7)
Caused by: java.lang.ClassNotFoundException: cn.hutool.core.lang.Console
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 1 more

D:\demo01>java -cp ".;*" com.example.Main
Hello World
hello world by hutool!

可以看到,当我们指定为-cp ".;*.jar"时,会发现*.jar并没有解析到我们对应的jar包,但是响应的 -cp .;*却解析到的对应的jar包,因此通配符*.jar是无效的,这里可能是因为JDK版本的原因或者操作系统的原因吧,不太明白,这里暂且估算一个原因,下面是 java 命令的帮助说明截取:

    -cp <目录和 zip/jar 文件的类搜索路径>
    -classpath <目录和 zip/jar 文件的类搜索路径>
                  用 ; 分隔的目录, JAR 档案
                  和 ZIP 档案列表, 用于搜索类文件。
  • 当指定为目录时(如 .)
    • 搜索目录下的所有字节码文件并解析为类
  • 当指定为 jar 或 zip 压缩包时
    • 读取压缩包并解析其中所有的字节码文件并解析为类
  • 当指定为通配符*
    • 搜索当前或者目录下的所有以zip/jar结尾的压缩包并解析(不会递归解析子目录)

我猜JVM在解析的过程中,当指定为文件或者文件的通配符时,本身只会解析jar包和zip压缩包;当指定为目录时,则会解析目录下的所有字节码文件并加载为类。因此我们不在需要特别指定.jar为文件的后缀了,而如果指定的 .jar 作为文件的后缀时 JVM反而会去尝试解析 hutool-all-5.4.3.jar.jar文件,但由于没有这个文件,自然而然解析不到了。

问题3. 通配符*会不会解析当前目录的子目录下的 jar包?

不会,*只会解析当前目录下的 jar 包,至于子目录下的 jar 包还需要另外指定。

问题4. -cp 参数 与 -jar参数能否一起使用?

简单来说,是不能一起使用的,两者加载 classpath 是不一致的,前者 -cp选项在加载 jar 包和 class 类文件时,是通过后面拼接的参数来加载的,相对的 -jar 选项在加载 jar包时则是获取配置文件中设置的 classpath 从而进行加载。

当指定了 -jar 选项后,JVM不再从 -cp 选项中指定的jar包路径和 类路径 中加载 jar 包,因此同时设置 -cp 参数 和-jar 参数的结果是 -cp 参数相当于没有设置。

问题5.-jar 参数是如何加载 jar包的?

以一个命令为例:

  java -jar main.jar

执行该命令时,JVM 会从 main.jar 包中的检索 META-INF\MANIFEST.MF文件,然后在该文件中,有一个叫Class-Path的参数,它指定了java -jar命令执行的类路径。
以下是一个META-INF.MF清单文件

Manifest-Version: 1.0
Class-Path: . cmd_lib/commons-lang3-3.7.jar
Main-Class: com.yveshe.PackageClass
Name: java/util/

Manifest-Version:

用来定义manifest文件的版本,例如:Manifest-Version: 1.0

Main-Class

定义jar文件的入口类,该类必须是一个可执行的类(包含main方法的类),一旦定义了该属性即可通过 java -jar x.jar来运行该jar文件。
运行Jar: java -jar yveshe.jar
当运行上述命令时JVM将在yveshe.jar文件中的MANIFEST.MF文件中查找Main-Class属性的值,并尝试运行该类。如果在yveshe.jar文件中未包含Main-Class属性,则上述命令将生成错误。

Class-Path

指定jar包的依赖关系,classLoader会依据这个路径来搜索class
默认是相对路径,相对该jar所在的父文件夹.
可以在其manifest 文件中为JAR文件设置CLASSPATH。属性名称叫作类路径,必须在自定义清单文件中指定。 它是一个空格分隔的jar文件,zip文件和目录的列表。(不区分系统都是以空格来分隔多个jar文件)以下是一个属性配置例子:

Class-Path: . hutool-core.jar file:/c:/hutool/hutool-json.jar http://www.example.com/hutool-date.jar

这条命令配置了该main.jar依赖如下jar包:

  • . ---- 表示当前目录下的所有类文件
  • hutool-core ---- 表示当前jar包目录下的 hutool-core.jar jar包;
  • file:/c:/hutool/hutool.-json.jar一个使用文件协议文件指定的 jar 包;
  • http://www.example.com/hutool-date.jar 使用HTTP协议的下载的 jar包;

注意: 当使用java命令使用-jar(比如java -jar main.jar)选项运行JAR文件时
将忽略jarmanifest文件之外的任何CLASSPATH设置。

书写注意

  • 每行的:(冒号)用来分隔键值对,冒号后边一定要跟一个空格。
  • MANIFEST.MF清单文件必须以一个空白行结束。
  • Class-Path里边的内容用空格分隔而不是逗号或者分号
  • 每行不能超过七十多的字符。

参考资料

关于Java -cp引用jar是否支持通配符
java命令 : java -jar 和 java -cp
Setting the class path(官方文档)
关于Java -cp引用jar是否支持通配符
classpath和jar

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值