背景

最近在在项目中遇到了一个类加载的问题,几经周折没有找到合适的解决方式,只能怪自己学艺不精。没办法只好重拾曾今丢掉的 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└─exampleMain.classMain.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/Consoleat com.example.Main.main(Main.java:7)
Caused by: java.lang.ClassNotFoundException: cn.hutool.core.lang.Consoleat 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/Consoleat com.example.Main.main(Main.java:7)
Caused by: java.lang.ClassNotFoundException: cn.hutool.core.lang.Consoleat 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 moreD:\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

WIN10 下 “java -cp“ 命令解析相关推荐

  1. java -cp命令使用

    java -cp命令使用 Mac(Linux)下格式 java -cp .:你的jar包路径 主类的全限定名称 举例 java -cp .:myClass.jar packname.mainclass ...

  2. 页面找不到了无法解析服务器,win10下搜狗浏览器无法解析服务器的dns地址如何解决...

    搜狗浏览器能够给我们带来很棒的网页浏览体验,功能也十分强大.不过,最近一些win10系统用户反馈自己打开网页提示页面找不到了... 错误信息:如法解析服务器的dns地址 导致无法打开网页.这是怎么回事 ...

  3. win10下的Cmd命令的初步认识

    win10下的Cmd命令认识 一. ipconfig命令 1.介绍 2.实操 2.1.使用`ipcongfig /all`查看自己主机IP地址 2.2查看其它电脑IP

  4. win10linux远程命令,IT之家学院:在Win10下管理远程命令行

    原标题:IT之家学院:在Win10下管理远程命令行 升级到Windows10后,我们会发现Tenlnet服务器选项不见了,这样我们怎么愉快地进行远程管理啊?经过一天时间的寻找,我终于找到了一款支持Wi ...

  5. java环境变量设置 win2003,2021-03-09Win10的Java环境配置Win10下Java环境变量配置

    接下来主要讲怎么配置 Java 的环境变量,也是为了以后哪天自己忘记了做个备份 (注:win10的Java环境变量配置和其他的windows版本稍有不同) 在电脑桌面 右键点击 "此电脑&q ...

  6. WIN10下Java环境变量配置

    首先,你应该已经安装了 Java 的 JDK 了(如果没有安装JDK,请跳转到此网址:http://www.oracle.com/technetwork/java/javase/downloads/i ...

  7. 实现Linux下的cp命令

    cp命令的作用:读取源文件写到目标文件 具体实现思路: 1.打开源文件,先判断argc==3,argv[0]为可执行程序的名字,argv[1]为源文件,argv[2]为目标文件 2.当源文件存在的时候 ...

  8. linux下的cp命令

    当遇到如问题时: 是因为将一个目录复制到另一个目录下时使用 -r cp 补充: 示例: 1.将../mary/homework/assign复制到当前目录下: cp ../mary/homework/ ...

  9. linux下使用\cp命令的原因

    2019独角兽企业重金招聘Python工程师标准>>> 有时会看到在拷贝文件的时候习惯使用\cp -rf而不是cp -rf,这两者是有区别的:当使用第一种时系统不会提示是否覆盖,第二 ...

最新文章

  1. MXD文档保存和地图浏览
  2. html5表单动态添加,js动态添加表单实例
  3. 【测评】想买投影仪,预算又不多,该怎么选?——三款高性价比投影仪PK测评
  4. z-wave问题汇总
  5. 书法是什么?书法的美从何说起?
  6. 深入理解PHP之数组(遍历顺序)
  7. Safari浏览器(有时没有图片时,提交会出现问题)。
  8. 虚拟机不能上网以及无法ping通百度的解决方案
  9. 分享-追书神器旧版本-无广告、可换源看小说
  10. Chromium OS 初体验
  11. 使用PHP+LibreOffice实现word转html的功能
  12. 网站优化后如何降低阿里云国际版服务器成本
  13. 广东金融学院大学计算机基础,好投顾网使用说明广东金融学院专用).doc
  14. Coordinatorlayout嵌套滑动,自定义Behavior,听我来讲讲?
  15. Windows 10无法显示无线网络连接
  16. 乾颐堂安德HCIE面试真题系列19(戚ZJ)
  17. 微信小程序 手写签名_你竟然还不知道在微信上就可以手写签名、签文件了~
  18. 五行代码实现图像识别(深入版)
  19. c语言实现图片轮播,纯css实现轮播图
  20. 局域网内打印机打印只能打印一页或是几页的解决办法

热门文章

  1. python通过requests库发送请求
  2. 123. 买卖股票的最佳时机 III ( 三维dp )
  3. 瑞幸咖啡的最终目标并不是做国内市场大哥
  4. 股票入门基础知识37:识别交易中的支撑位和阻力位
  5. 宏观结构分析之语篇模式视角
  6. 计算机专业教师理论培训小结,教师计算机培训心得小结
  7. 多个Excel文件如何根据条件进行汇总求和呢---多个文件根据条件汇总求和工具
  8. rg1 蓝光危害rg0_新国标:你的LED台灯防蓝光危害评估结果是RG0吗?
  9. R语言实现LDA算法(鸢尾花)
  10. javac编译错误: 程序包 com.sun.xxx 不存在