当前位置 : IT培训网 > Java开发 > Java培训 > 谈谈有关Java类加载的基础知识

谈谈有关Java类加载的基础知识

时间:2017-07-08 14:16:40  来源:Java培训网  作者:IT培训网  已有:名学员访问该课程
标签(Tag):   Java类加载基础(1)
谈及Java类加载,大伙并不陌生,有关JAVA类加载的一些基础知识,认真学习,对大家的Java学习入门是非常有帮助的。 在最初执行java这个命令时,便会调用 ClassLoader 的 getSystemClassLoader 方法显式或者隐式加载 main 方法所在的类及其

谈及Java类加载,大伙并不陌生,有关JAVA类加载的一些基础知识,认真学习,对大家的Java学习入门是非常有帮助的。

在最初执行java这个命令时,便会调用 ClassLoader 的 getSystemClassLoader 方法显式或者隐式加载 main 方法所在的类及其所引用的类。getSystemClassLoader 会返回 AppClassLoader,后者是 URLClassLoader 的一个子类。

因为 ClassLoader 的整套体系是打包在 jre/lib/rt.jar 中的。只有 rt.jar 先被加载进来,才能够加载别的类;但是 rt.jar 又是被谁加载的呢?自然就是大名鼎鼎的 BootstrapClassLoader。

所以严格来讲,BootStrapClassLoader 并不是整个体系中的一部分。

当 rt.jar 被加载进来后,ClassLoader 会调用 getSystemClassLoader,其中最重要的一步就是初始化 Launcher、ExtClassLoader 以及AppClassLoader,另外就是将 ContextClassLoader 设为 AppClassLoader。ExtClassLoader 与 AppClassLoader 都是 URLClassLoader 的子类,分别会加载 java.ext.dirs 和 java.class.path 路径下的 jar资源,前者一般指向 jre/lib/ext 下的所有jar,后者就是我们经常念叨的classpath。

区分这两个 ClassLoader 的主要目的是,让他们形成层级关系,ExtClassLoader 为 AppClassLoader 的父 ClassLoader,有了层级关系,便可随意使用双亲委托模型了。

ClassLoader extcl;

try {

extcl = ExtClassLoader.getExtClassLoader();

} catch (IOException e) {

throw new InternalError(

"Could not create extension class loader");

}

// Now create the class loader to use to launch the application

try {

loader = AppClassLoader.getAppClassLoader(extcl);

} catch (IOException e) {

throw new InternalError(

"Could not create application class loader");

}

// Also set the context class loader for the primordial thread.

Thread.currentThread().setContextClassLoader(loader);

ClassLoader具体会做什么?

ClassLoader是个非常重要的问题,通常我们只知道它加载了一个类进了jvm,但是具体做了什么呢?

Java设计者把classloader加载一个类的过程分为4步:

第一步,从某个地方得到我们想要的字节码二进制流;

第二步,读入字节码流并转化为Class;

第三步,链接;

第四步,初始化。

其中,第二步一般比较固定,因此ClassLoader提供了defineClass来完成这步;

protected final Class defineClass(String name, byte[] b, int off, int len,

ProtectionDomain protectionDomain)

throws ClassFormatError

{

protectionDomain = preDefineClass(name, protectionDomain);

Class c = null;

String source = defineClassSourceLocation(protectionDomain);

try {

c = defineClass1(name, b, off, len, protectionDomain, source);

} catch (ClassFormatError cfe) {

c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,

source);

}

postDefineClass(c, protectionDomain);

return c;

}

而 ClassLoader 提供了另一个方法 findClass 来完成第一步及第二步,即从某个地方读入类的二进制流,然后调用 defineClass 返回 Class。

protected Class findClass(final String name)

throws ClassNotFoundException

ClassLoader提供了resolveClass方法完成第三步链接的工作。

protected final void resolveClass(Class c)

除非特殊需要,否则尽量重载 findClass 而不是 loadClass。

loadClass 是 Java 1.0 就存在的类,为了增强可扩展性,将findClass和resolveClass封装到了loadClass中,一般我们只需要定义类的加载路径,因此仅需覆盖findClass。

通常我们显示加载类一般会用到ClassLoader.loadClass、Class.forName,他们的区别见这里。

resolveClass做了什么?

resolveClass最终调用了一个本地方法做link,这里的link主要做了这么几步事情:

验证Class以确保类装载器格式和行为正确;

准备后续步骤所需的数据结构;

解析所引用的其他类。

常见问题

ClassNotFoundException一般发生在显式类加载;NoClassDefFoundError一般发生在隐式加载;ClassCastException一般发生在jar包冲突,比如某个jar包已经被更上层的加载器加载了,但你却要求他强制转为下层加载器加载的同名类;

为什么会发明链接器?

最初程序员写程序都在一个文件里,随着程序规模的增加,逐渐发现越来越难以维护,扩展。于是,分多个文件和模块就成为必然。

但是文件间必然相互调用,这就带来了另一个问题:文件A引用了B的某个变量或方法,但是运行时A并不知道他们在内存中的位置。

于是人们发明了链接,这种方式会在编译阶段将需要引用的变量或者方法作个记号,这时形成A.o和B.o两个目标文件;通过ld链接器的链接,会将B中变量或方法的地址重新修改A的记号最终形成一个可执行文件,实际上就是把A和B合在一起工作了。

但是静态链接有个问题,比如A需要B模块的方法,C需要B模块的变量,D需要B模块的方法……如此一来,当我们编译为A, C, D几个可执行文件时,他们都会在内存中引用B,即B在内存中有多份拷贝。

首先浪费了内存;其次修改了B模块需要重新修改并发布A, C, D几个可执行文件,这是非常不方便的;于是动态链接的一个思想是在A, C, D调用时再确定记号的地址,而B则通过延迟加载的方式按需加载到内存。

顶一下
(0)
0%
踩一下
(0)
0%

IT培训0元试听 每期开班座位有限.0元试听抢座开始! IT培训0元试听

  • 姓名 : *
  • 电话 : *
  • QQ : *
  • 留言 :
  • 验证码 : 看不清?点击更换请输入正确的验证码

在线咨询在线咨询

温馨提示 : 请保持手机畅通,咨询老师为您
提供专属一对一报名服务。

------分隔线----------------------------
------分隔线----------------------------

推荐内容

相关热点