Java类加载

了解Java类加载

你想写ClassLoaders吗?或者你是否遇到
“意外的”ClassCastException或LinkageError以及
“加载器约束违规”的奇怪消息。那么,现在该仔细看看
Java类加载过程。

什么是ClassLoader以及它如何加载?

Java类由java.lang.ClassLoader 类的实例加载。
java.lang.ClassLoader本身是一个抽象类,所以一个类加载器只能有
一个java.lang.ClassLoader的具体子类的实例。如果是这种情况,哪个类加载器加载
java.lang.ClassLoader类本身?(经典的“谁将加载loader”引导问题)。
事实证明,JVM中内置了一个引导类加载器。引导
加载器加载java.lang.ClassLoader和许多其他Java平台类。

为了加载特定的Java类,比如说com.acme.Foo,JVM调用java.lang.ClassLoader的loadClass
方法(实际上,JVM寻找loadClassInternal方法 - 如果发现
它使用该方法,否则JVM使用loadClass。 loadClassInternal方法调用loadClass)。
loadClass接收要加载的类的名称并返回表示
加载的类的java.lang.Class实例。实际上,loadClass方法查找.class文件(或URL)的实际字节,并调用
defineClass
方法来构造byte数组外的java.lang.Class。在其上调用loadClass的Loader被
称为启动加载器(即,JVM使用该加载器启动加载)。但是,启动加载程序
不需要为类直接找到byte [],而是可以将类加载委派给
另一个类加载器(例如,它的父加载器) -
本身可以委托给另一个加载器等等。最后
,委托链中的一些类加载器对象最终调用defineClass方法来实际加载相关的类(com.acme.Foo)。
这个特别的程序叫定义装载机的com.acme.Foo。在运行时,Java类
由该对唯一标识 - 该类的完全限定名称以及加载它的定义加载器。
如果相同的命名(即相同的完全限定名称)类由两个不同的装载器定义,则这些类
是不同的 - 即使.class字节相同并从相同的位置(URL)加载。

多少个类加载器以及它们从哪里加载?

即使有一个很好的旧简单的“hello world”Java程序,也有至少3个类装载机。

引导加载程序
加载平台类(如java.lang.Object,java.lang.Thread等)
从rt.jar加载类( JREHOME/lib/rt.jarXbootclasspathXbootclasspath/pXbootclasspath/a/使SunSystemsun.boot.class.pathJavanulljava.lang.Object.classgetClassLoadernulljava.lang.Integerjava.awt.Framejava.sql.DriverManager J R E H O M E / l i b / r t . j a r ) − X b o o t c l a s s p a t h 可 用 于 更 改 引 导 类 路 径 − X b o o t c l a s s p a t h / p : 和 − X b o o t c l a s s p a t h / a : 可 用 于 预 先 添 加 / 追 加 其 他 引 导 目 录 − 这 样 做 时 非 常 谨 慎 。 在 大 多 数 情 况 下 , 您 希 望 避 免 使 用 引 导 类 路 径 进 行 播 放 。 在 S u n 的 实 现 中 , 只 读 S y s t e m 属 性 s u n . b o o t . c l a s s . p a t h 被 设 置 为 指 向 引 导 类 路 径 。 请 注 意 , 在 运 行 时 不 能 更 改 此 属 性 − 如 果 更 改 了 无 效 的 值 。 该 加 载 器 由 J a v a n u l l 表 示 。 即 , 例 如 , j a v a . l a n g . O b j e c t . c l a s s 。 g e t C l a s s L o a d e r ( ) 会 返 回 n u l l ( 所 以 对 于 其 他 引 导 类 如 j a v a . l a n g . I n t e g e r , j a v a . a w t . F r a m e , j a v a . s q l . D r i v e r M a n a g e r 等 ) 扩 展 类 加 载 器 从 已 安 装 的 可 选 包 中 加 载 类 从 JRE_HOME / lib / ext目录下的jar文件加载类
系统属性java.ext.dirs可能被设置为
使用-Djava.ext.dirs命令行选项更改扩展目录。
在Sun的实现中,这是sun.misc.Launcher $ ExtClassLoader的一个实例(实际上它是
sun.misc.Launcher类的内部类)。
以编程方式,您可以读取(只读!)系统属性java.ext.dirs以查找
哪些目录用作扩展目录。请注意,您无法
在运行时更改此属性 - 如果更改了无效的值。
应用类加载器
从应用程序类路径加载类
应用程序类路径使用
环境变量CLASSPATH(或)
带有Java启动器的-cp或-classpath选项

如果CLASSPATH和-cp都丢失,则“。” (当前目录)被使用。
只读System属性java.class.path具有
应用程序类路径的值。请注意,您无法在运行时更改此属性 -
如果更改了无效的值。
java.lang.ClassLoader.getSystemClassLoader()
返回这个加载器
这个加载器也被(称为“系统类加载器”)
(不会与加载Java“系统”类的引导加载器混淆)。
这是加载Java应用程序的“主”类(使用主要方法的类)的加载器。
在Sun的实现中,这是sun.misc.Launcher $ AppClassLoader的一个实例(实际上它是
sun.misc.Launcher类的内部类)。
默认的应用程序加载器使用扩展加载器作为其父装载器。
您可以通过命令行选项-Djava.system.class.loader更改应用程序类加载器。该值
指定java.lang.ClassLoader类的子类的名称。首先默认的应用程序
加载器加载命名的类(并且这个类必须在CLASSPATH或-cp中)并创建
它的一个实例。新创建的加载器用于加载应用程序主类。

典型的类装载流程

让我们假设你正在运行一个“hello world”java程序。我们将如何加载类。JVM使用
“应用程序类加载器” 加载主类。如果您运行以下程序

class Main {
public static void main(String[] args) {
            System.out.println(Main.class.getClassLoader());javax.swing.JFrame f = new javax.swing.JFrame();
            f.setVisible(true);SomeAppClass s = new SomeAppClass();
}
}

它打印类似的东西
sun.misc.Launcher$AppClassLoader@17943a4
每当必须从Main类中解析对其他类的引用时,JVM就会
使用Main类的定义加载器 - 应用程序类加载器 - 作为启动加载器。
在上面的示例中,要加载类javax.swing.JFrame,JVM将使用应用程序
类加载器作为启动加载器。即JVM将
在应用程序类加载器上调用loadClass()(loadClassInternal)。应用程序类加载器将其委托给扩展
类加载器。扩展类加载器检查它是否是引导类
(使用私有方法 - ClassLoader.findBootstrapClass),并且引导加载器
定义该类从rt.jar加载它。当必须解析对SomeAppClass的引用时,
JVM遵循相同的流程 - 它使用应用程序类加载器作为启动加载器
。应用程序加载器将其委托给扩展加载器。扩展加载程序使用
引导加载程序进行检查 Bootstrap加载程序不会找到“SomeAppClass”。然后扩展装载器检查
“SomeAppClass”是否在任何扩展器中,并且它不会找到任何扩展器。然后应用程序
类加载器检查应用程序CLASSPATH中的.class字节。如果发现,它定义
相同。如果不是,则会抛出NoClassDefFoundError。

概要

类是通过定义加载器和完全限定名来唯一标识的。
即使从
文件系统中相同位置的相同.class字节加载类,如果定义的加载程序不同,类也是不同的。
类加载器将加载委托给父装载器。
为了加载从“Bar”类引用的类“Foo”,JVM使用Bar的定义加载器
作为启动加载器。JVM将在Bar的定义加载器上调用loadClass(“Foo”)。
JVM缓存 - >每次
启动加载时的运行时类记录。JVM将在随后的解决方案中使用缓存。即不会
为每个引用调用loadClass 。这确保了时间不变 - 即,
不会允许为相同的类名加载不同的.class字节的ClassLoader 。它被
缓存保存。编写好的类加载器必须通过ClassLoader.findLoadedClass()
调用来检查缓存。

原文地址:https://blogs.oracle.com/sundararajan/understanding-java-class-loading

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值