影子的知识库

影子的知识库

  • 知识库
  • GitHub

›Java系列

JVM系列

  • JVM内存区域
  • 对象创建-布局-访问
  • 内存溢出实战
  • 内存区域回收
  • 四大引用
  • 垃圾回收算法
  • HotSpot回收算法细节

Java系列

  • java注解
  • springboot请求参数绑定
  • springboot请求参数校验框架
  • YAML语法
  • 动态代理
  • classpath和java命令
  • springboot-aop编程
  • springboot统一异常处理
  • springboot数据库和事务
  • springboot拦截器
  • springboot中的web配置
  • docker的简单开发
  • springboot自动配置
  • 数据库的隔离级别
  • springboot监控
  • java类加载
  • java-agent的相关内容
  • 类加载器详解
  • java的SecurityManager
  • maven学习

Node

    JS 基础

    • 语法基础和数据类型
    • 数据类型转换
    • 语句 表达式 运算符
    • 变量与对象
    • 函数
    • 数据处理
    • 常用 API
    • 重点知识

    ES6

    • 块级作用域
    • 字符串和正则表达式
    • 函数
    • 对象
    • Symbol
    • Set和Map
    • 迭代器和生成器
    • 类
    • 数组
    • Promise

    Node 基础

    • 模块系统
    • package.json
    • 内置对象
    • npm脚本的使用
    • Buffer
    • Stream
    • 事件循环机制
    • 示例代码

    stream系列

    • 流的缓冲
    • 可读流
    • 可写流
    • 双工流和转换流
    • 自定义流

后期计划

  • 学习计划
  • 专题研究计划
Edit

本文内容

参考

  • https://docs.oracle.com/javase/7/docs/technotes/tools/windows/classpath.html
  • https://www.cnblogs.com/duanxz/p/3482311.html
  • https://www.cnblogs.com/youxia/p/java001.html

classpath 是什么

classpath 就是去寻找我们的 class 文件的路径,这么说当然是没问题的,但是有一个点就是:classpath 并不是一个单独的路径,而是由很多个路径组成的,也就是说,我们可以传入多个路径给 classpath,例如:

java -cp .:/someLib:/anotherLib

这里就包含了 3 个路径:

  • .:表示当前路径
  • /someLib:第二个路径
  • /anotherLib:第三个路径

此时,如果我们的代码里写了一句:Class.forName("com.example.MyClass") 之类的需要 JVM 去加载某个类的语句,那么 JVM 就会去 classpath 里去找到这个类加载(其实这个行为是 AppClassLoader 做的,如果我们不是使用的这个类加载器,就可能会有别的行为)

寻找这个类的过程就是:

  • 去找 ./com/example/MyClass.class,找到了就加载,找不到就下一步
  • 去找 /someLib/com/example/MyClass.class,找到了就加载,找不到就下一步
  • 去找 /anotherLib/com/example/MyClass.class,找到了就加载,找不到就下一步
  • 发现 classpath 里没找到这个类文件,抛出 ClassNotFoundException

使用冒号作为不同的 classpath 的分隔符,在 windows 下使用分号 ; 作为分隔符

AppClassLoader 只去加载一次,并且首先被找到的被加载,因此同一个类名的 class 文件,存在在不同的 classpath 下的话,只有最先被找到的被加载,这也是 jar 包冲突的一种起源了

传入 jar 包或者 zip 包作为 classpath

上面我们传递的都是目录作为 classpath,JVM 会去我们传递的多个目录里寻找我们要加载的 class 文件。当我们传入 jar 包的时候,行为就不一样了,例如:

java -cp .:/someLib:/anotherLib:/libs/myLib.jar

还是以 Class.forName("com.example.MyClass") 为例,寻找的过程如下:

  • 去找 ./com/example/MyClass.class,找到了就加载,找不到就下一步
  • 去找 /someLib/com/example/MyClass.class,找到了就加载,找不到就下一步
  • 去找 /anotherLib/com/example/MyClass.class,找到了就加载,找不到就下一步
  • 去到 /libs/myLib.jar 这个 jar 包内部,以 jar 包内部根节点为起始,去寻找 /com/example/MyClass.class,找到了就加载,找不到就下一步

jar 包实际就是 zip 包,把 jar 包解压后的目录看做是根目录,寻找 com.example.MyClass 的时候,就会去这个根目录下寻找 com/example/MyClass.class

因此我们在 classpath 中应该将 jar 包视作为和目录等同的地位,寻找类文件的时候也是把 jar 包当做目录去寻找的。

传入多个 jar 包

有时候我们有多个 jar 包在一个目录下都想作为第三方依赖,比如说我们将项目需要的所有的 jar 包都放到 /lib 目录下面,如果要手动拼写 classpath 将会非常的长,我们可以使用 * 通配符来传入多个 jar 包作为 classpath,假设现在 /lib 下面有 3 个 jar 包分别是 /lib/a.jar、/lib/b.jar、/lib/c.jar,那么我们可以执行

java -cp .:/lib/*

相当于执行了

java -cp .:/lib/a.jar:/lib/b.jar:/lib/c.jar

也就是传入了如下的 classpath

  • .:当前路径
  • /lib/a.jar
  • /lib/b.jar
  • /lib/c.jar

这种方式只能传递 /lib 下的 jar 包,不包含 lib 下的 class 文件,如果同时还需要 class 文件,需要传入

java -cp /lib:/lib/* 这表示将 /lib 作为一个 class 的根目录,同时把 /lib 下所有的 jar 包也作为 classpath 根目录

这种通配符并不能递归,也就是说,如果我们有 /lib/child/xxx.jar 那么这个 jar 是不会被加入到 classpath 的

classpath 在系统中的定义

java.class.path

我们使用 System.getProperty("java.class.path") 就可以得到我们定义的 classpath,这些将由 AppClassLoader 去加载。

扩展类 classpath

除了我们在 java -cp 中传入的 classpath 之外,JVM 还会加载一些扩展类 jar 包,JVM 默认会去 $JAVA_HOME/jre/lib/ext 下,将所有的 jar 包都使用 ExtClassLoader 去加载,这个路径我们可以使用

java.ext.dirs 来控制,在我的 IDE 环境中如下:

/Users/czp/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java

我们可以使用 java -Djava.ext.dirs=xxx 来修改扩展类的 classpath (一般不建议),或者直接将一些 jar 包丢到 $JAVA_HOME/jre/lib/ext 下,它会被 ExtClassLoader 加载

这个路径是专门加载一些扩展包的,它由 ExtClassLoader 去加载,我们也可以使用自定义类加载器定义别的行为

启动类 classpath

使用 -Xbootclasspath 可以修改启动类加载器,有以下的用法:

  • java -Xbootclasspath:/aaa/bbb/*:将 /aaa/bbb 下的 jar 包替代原先的 -Xbootclasspath,此时核心类的加载完全被替代了,所以很少用
  • java -Xbootclasspath/a:/aaa/bbb/*:表示加载完了核心类之后,还要去加载 /aaa/bbb 下的 jar 包,也就是追加的关系(用的会多一点)
  • java -Xbootclasspath/p:/aaa/bbb/*:表示加载完了 /aaa/bbb 下的 jar 包之后,再去加载核心类,也就是先加载我们定义的类(用的较少)

这个路径定义的类是被 BootstrapClassLoader 加载的,这是 JVM 实现本身的一部分,也就是 C++ 代码

很少替换这个 classpath,但是如果有需要还是可以这么干的

它对应的 java 系统属性是:System.getProperty("sun.boot.class.path")

运行 Java 程序的方式

我们运行 java 程序的时候有两种方式:

  • 一种是使用 -cp 参数,然后传入一大堆 jar 包和文件目录,接着传入主类,最后传入程序参数。例如:

    • java -Dkey1=val1 -Dkey2=val2 -cp a.jar:b.jar:c.jar:/home/xxxUser/project-java aaa.bbb.ccc.Main arg1 arg2 arg3
    • 上述的方式中,-D参数定义的是 Java系统属性,在java中可以通过System.getProperty(key1)的方式读取
    • arg1 arg2 arg3 定义的是程序参数,最后也就是 main 函数的 args 数组参数
    • 运行的主类是 aaa.bbb.ccc.Main,此时 JVM 就会去 a.jar 下寻找 /aaa/bbb/ccc/Main.class 这个 class,找不到就会去 b.jar 下寻找 /aaa/bbb/ccc/Main.class 这个 class,然后是 c.jar,接着是 /home/xxxUser/project-java/aaa/bbb/ccc/Main.class
    • 主要的要点就是,jar 包视作为一个根目录,要加载的类都是按照包名去这些传递进来的根目录去找
    • 因为可以传递多个根路径和 jar 包,那么肯定有可能会出现完全相同的类名,此时,按照 -cp 定义的顺序,最先被找到的那个 class 被首先加载,后面的相同的 class 将不会被加载。这种情况也就是有时候会出现的 jar 包冲突导致 NoSuchMethodError 的问题,因为多个依赖可能也有一些不同版本的底层依赖,然后这个底层依赖的类加载了其中一个版本,导致另一个依赖其他版本的报错了
  • 另一种是使用 -jar 参数,然后传入一个jar包,接着就是程序参数。例如:

    • java -Dkey1=val1 -Dkey2=val2 -jar xxx.jar arg1 arg2 arg3
    • 这种方式只能使用一个jar包,并且 jar 包需要META_INF目录下的manifest.MF文件中定义了Main-Class,会自动去启动这个main类
    • 无法添加多个 jar 作为 cp,这种方式只能启动一个 jar
    • 可以使用 java -Xbootclasspath/a:/usrhome/thirdlib.jar: -jar yourJarExe.jar 的方式,将一些包使用 bootclassLoader 去加载

顺便说一句,System.getEnv()获取的是环境变量,就是通常意义上的进程的环境变量(jvm实质就是个进程),System.getProperty(key1)获取的是java系统属性,这个是java自己定义的一种参数,arg1 arg2是程序参数,这个对应于main函数接收的参数。

Last updated on 11/8/2020
← 动态代理springboot-aop编程 →
  • 参考
  • classpath 是什么
    • 传入 jar 包或者 zip 包作为 classpath
    • 传入多个 jar 包
    • classpath 在系统中的定义
  • 扩展类 classpath
  • 启动类 classpath
  • 运行 Java 程序的方式
影子的知识库
Docs
Getting Started (or other categories)Guides (or other categories)API Reference (or other categories)
Community
User ShowcaseStack OverflowProject ChatTwitter
More
BlogGitHub
Copyright © 2020 Cen ZhiPeng