Bootstrap类加载器加载的类

5
这个问题是由这篇文章引发的。当运行如下简单程序时:
public class Sample
{
    public static void main(String[] args)
    {
    }
}

使用选项 -verbose:class,在加载该类之前会列出一堆类。

[Opened C:\jdk1.6.0_14\jre\lib\rt.jar]
[Loaded java.lang.Object from C:\jdk1.6.0_14\jre\lib\rt.jar]
[Loaded java.io.Serializable from C:\jdk1.6.0_14\jre\lib\rt.jar]
[Loaded java.lang.Comparable from C:\jdk1.6.0_14\jre\lib\rt.jar]
.
.
.
.
.
.
[Loaded java.security.cert.Certificate from C:\jdk1.6.0_14\jre\lib\rt.jar]
[Loaded Sample from file:/D:/tmp/]
[Loaded java.lang.Shutdown from C:\jdk1.6.0_14\jre\lib\rt.jar]
[Loaded java.lang.Shutdown$Lock from C:\jdk1.6.0_14\jre\lib\rt.jar]

我的问题是,

我的程序从未需要像java.util.CollectionSetList等类。那么为什么Bootstrap类加载器正在加载它们呢?这是JVM规范要求的吗?还是Bootstrap类加载器决定要加载哪些类?

编辑:

另一个方面:

即使您尝试运行不存在的类,程序也会以ClassNotFoundException结束,但不会在没有加载所有先前提到的类的情况下结束。因此,这些类仅在调用JVM时加载!因此,JVM默认加载一组类,但是是什么决定了这种行为

3个回答

4

Class类是java.lang包的一部分,因此引导类加载器会选择它进行加载,但是Class本身需要一些依赖框架类,如ListSet,因此它们也会被加载。

如果您查看JDK中的Class代码,您会发现以下导入:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;

Class源码在内部使用了Collections,所以需要被加载。


但是,即使您尝试运行不存在的类,也会加载相同的一组类!关键在于,这些类是通过简单调用JVM来加载的。 - Santosh
是的,它们会被加载,因为java.lang包类会被引导类加载器自动加载,所有依赖的类也会被加载。无论您输入存在或不存在的类名。 - Narendra Pathai
这也是为什么我们不需要在源代码中导入java.lang包,因为它们已经预加载了。 - Narendra Pathai
1
但是按照这种逻辑,甚至CollectionIO的类也不需要被导入! - Santosh
1
类加载与导入无关,事实上,导入根本没有被翻译成字节码。当常量池中的classRef被取消引用时,类加载发生。话虽如此:类加载始终是动态的,当需要时就会加载。 - bestsss
显示剩余2条评论

3

有些人对引导类加载器存在一些误解:

  • 它解决了鸡和蛋的问题:要想拥有一个类加载器实例,你需要大量的类,包括object/classs/collections/io/nio等
  • 也称为VM内置类加载器
  • 它加载所有的java.类,即以java.开头的类不能被其他任何东西加载(这是安全模型的一部分)
  • 实际上,引导类加载器加载了JRE提供的所有类(例如来自rt.jar的所有内容),尽管如果该包不以java.开头,则可能由应用程序类加载器加载
  • object.getClass().getClassLoader()通常返回null,因为引导类加载器位于本地/C代码中(是JVM的一部分),即它不是一个Java类(因为它不能被自身加载,参见上面的鸡和蛋问题)
  • 它不是任何其他类加载器的直接父类,ClassLoader.getParent()不会返回它。
  • 没有正式的直接方式调用它的方法(因为它不存在作为Java类),

现在来回答直接的问题:“是什么控制了这种行为?”- 当第一次尝试通过'Class.forName'加载类时,通常当前类的类加载器会要求其自身的父类加载器执行此操作。引导类加载器被认为是系统类加载器(负责加载主类)的父类。如果当前类已经被引导类加载器加载,则使用VM来解析所需加载的类。

最后 - 在Java中,类总是在需要时动态加载的,每次尝试调用方法时都会从常量池中获取完全限定的类名,必须进行解析。这种行为独立于类的类加载器。因此,当JVM实例化第一个类时,球开始滚动。


2
JRE可能会进行延迟加载,即仅在需要时才加载类。
然而,在访问主类之前,JRE已经执行了许多其他Java代码,特别是“sun.misc.Launcher”。这就是为什么许多类在您的类之前被加载的原因。
一般来说,JRE有权在任何时候加载它想要的任何类。

http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.1.2

一种实现方式可以在链接一个类或接口时非常早地解析其中的符号引用,甚至可以递归地解析所有进一步引用的类和接口中的符号引用。另一种实现方式则可以选择仅在符号引用被主动使用时才解析它;对于所有符号引用都采用这种策略的一致性使用将代表“最懒惰”的解析形式。
请注意,类的初始化发生在严格指定的时刻;没有“急切”的初始化 - http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接