深入理解JVM(六)——类加载器原理
我们知道我们编写的java代码,会经过编译器编译成字节码文件(class文件),再把字节码文件装载到JVM中,映射到各个内存区域中,我们的程序就可以在内存中运行了。那么字节码文件是怎样装载到JVM中的呢?中间经过了哪些步骤?常说的双亲委派模式又是怎么回事?本文主要搞清楚这些问题。
类装载流程
1、加载
加载是类装载的第一步,首先通过class文件的路径读取到二进制流,并解析二进制流将里面的元数据(类型、常量等)载入到方法区,在java堆中生成对应的java.lang.Class对象。
2、连接
连接过程又分为3步,验证、准备、解析
2.1、验证
验证的主要目的就是判断class文件的合法性,比如class文件一定是以0xCAFEBABE开头的,另外对版本号也会做验证,例如如果使用java1.8编译后的class文件要再java1.6虚拟机上运行,因为版本问题就会验证不通过。除此之外还会对元数据、字节码进行验证,具体的验证过程就复杂的多了,可以专门查看相关资料去了解。
2.2、准备
准备过程就是分配内存,给类的一些字段设置初始值,例如:
public static int v=1;
这段代码在准备阶段v的值就会被初始化为0,只有到后面类初始化阶段时才会被设置为1。
但是对于static final(常量),在准备阶段就会被设置成指定的值,例如:
public static final int v=1;
这段代码在准备阶段v的值就是1。
2.3、解析
解析过程就是将符号引用替换为直接引用,例如某个类继承java.lang.object,原来的符号引用记录的是“java.lang.object”这个符号,凭借这个符号并不能找到java.lang.object这个对象在哪里?而直接引用就是要找到java.lang.object所在的内存地址,建立直接引用关系,这样就方便查询到具体对象。
3、初始化
初始化过程,主要包括执行类构造方法、static变量赋值语句,staic{}语句块,需要注意的是如果一个子类进行初始化,那么它会事先初始化其父类,保证父类在子类之前被初始化。所以其实在java中初始化一个类,那么必然是先初始化java.lang.Object,因为所有的java类都继承自java.lang.Object。
说完了类加载过程,我们来介绍一下这个过程当中的主角:类加载器。
类加载器
类加载器ClassLoader,它是一个抽象类,ClassLoader的具体实例负责把java字节码读取到JVM当中,ClassLoader还可以定制以满足不同字节码流的加载方式,比如从网络加载、从文件加载。ClassLoader的负责整个类装载流程中的“加载”阶段。
ClassLoader的重要方法:
1: public Class<?> loadClass(String name) throws ClassNotFoundException
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
载入并返回一个类。
1: protected final Class<?> defineClass(byte[] b, int off, int len)
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
定义一个类,该方法不公开被调用。
1: protected Class<?> findClass(String name) throws ClassNotFoundException
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
查找类,loadClass的回调方法
1: protected final Class<?> findLoadedClass(String name)
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
查找已经加载的类。
系统中的ClassLoader
BootStrap Classloader (启动ClassLoader)
Extension ClassLoader (扩展ClassLoader)
App ClassLoader(应用 ClassLoader)
Custom ClassLoader(自定义ClassLoader)
每个ClassLoader都有另外一个ClassLoader作为父ClassLoader,BootStrap Classloader除外,它没有父Classloader。
ClassLoader加载机制如下:
自下向上检查类是否被加载,一般情况下,首先从App ClassLoader中调用findLoadedClass方法查看是否已经加载,如果没有加载,则会交给父类,Extension ClassLoader去查看是否加载,还没加载,则再调用其父类,BootstrapClassLoader查看是否已经加载,如果仍然没有,自顶向下尝试加载类,那么从 Bootstrap ClassLoader到 App ClassLoader依次尝试加载。
值得注意的是即使两个类来源于相同的class文件,如果使用不同的类加载器加载,加载后的对象是完全不同的,这个不同反应在对象的 equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用 instanceof 关键字对对象所属关系的判定结果。
从代码上可以看出,首先查看这个类是否被加载,如果没有则调用父类的loadClass方法,直到BootstrapClassLoader(没有父类),我们把这个过程叫做双亲模式,
双亲模式的问题
顶层ClassLoader,无法加载底层ClassLoader的类
Java框架(rt.jar)如何加载应用的类?
比如:javax.xml.parsers包中定义了xml解析的类接口
Service Provider Interface SPI 位于rt.jar
即接口在启动ClassLoader中。
而SPI的实现类,在AppLoader。
这样就无法用BootstrapClassLoader去加载SPI的实现类。
解决
JDK中提供了一个方法:
1: Thread. setContextClassLoader()
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题;
基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例。
双亲模式的破坏
双亲模式是默认的模式,但不是必须这么做;
Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent;
OSGi的ClassLoader形成网状结构,根据需要自由加载Class。
小结
本文介绍了类加载的流程,以及ClassLoader工作机制,最后分析双亲模式的缺陷,以及如何弥补该缺陷,介绍了tomcat、OSGI如何自定义类加载流程。
参考资料:
《实战Java虚拟机》 葛一鸣
《深入理解Java虚拟机(第2版)》 周志明
深入理解JVM(六)——类加载器原理的更多相关文章
- 深入理解JVM一类加载器原理
我们知道我们编写的java代码,会经过编译器编译成字节码文件(class文件),再把字节码文件装载到JVM中,映射到各个内存区域中,我们的程序就可以在内存中运行了.那么字节码文件是怎样装载到JVM中的 ...
- 【深入理解JVM】类加载器与双亲委派模型 (转)
出处: [深入理解JVM]类加载器与双亲委派模型 加载类的开放性 类加载器(ClassLoader)是Java语言的一项创新,也是Java流行的一个重要原因.在类加载的第一阶段“加载”过程中,需要通过 ...
- JVM学习--(六)类加载器原理
我们知道我们编写的java代码,会经过编译器编译成字节码文件(class文件),再把字节码文件装载到JVM中,映射到各个内存区域中,我们的程序就可以在内存中运行了.那么字节码文件是怎样装载到JVM中的 ...
- 【深入理解JVM】类加载器与双亲委派模型
原文链接:http://blog.csdn.net/u011080472/article/details/51332866,http://www.cnblogs.com/lanxuezaipiao/p ...
- 深入理解JVM,类加载器
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流(即字节码)”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这个动作的代码模块称 ...
- JVM自定义类加载器加载指定classPath下的所有class及jar
一.JVM中的类加载器类型 从Java虚拟机的角度讲,只有两种不同的类加载器:启动类加载器和其他类加载器. 1.启动类加载器(Boostrap ClassLoader):这个是由c++实现的,主要负责 ...
- 深入理解:java类加载器
概念理解:Java类加载器总结 1.深入理解Java类加载器(1):Java类加载原理解析 2.深入理解Java类加载器(2):线程上下文类加载器
- 深入理解JVM之类加载
---title: [学习]深入理解JVM之类加载.mddate: 2019-10-20 22:20:06tags: JVM 类加载--- Java类的加载,连接,初始化都是在程序运行期间执行的 ## ...
- 1.1 jvm核心类加载器--jdk源码剖析
目录 前提: 运行环境 1. 类加载的过程 1.1 类加载器初始化的过程 1.2 类加载的过程 1.3 类的懒加载 2. jvm核心类加载器 3. 双亲委派机制 4. 自定义类加载器 5. tomca ...
随机推荐
- c++中虚多态的实现机制
c++中虚多态的实现机制 參考博客:http://blog.csdn.net/neiloid/article/details/6934135 序言 证明vptr指针存在 无继承 单继承无覆盖 单继承有 ...
- 注册Azure AD 2.0 应用程序
作者:陈希章 发表于 2017年3月22日 上一篇 介绍了Microsoft Graph应用程序的一些概念,以及目前还比较普遍的Azure AD 1.0应用程序的注册方式.但正如我多次提到的那样,虽然 ...
- vue路由
vue-router 现在的应用都流行SPA应用(single page application) 传统的项目大多使用多页面结构,需要切换内容的时候我们往往会进行单个html文件的跳转,这个时候受网络 ...
- IDEA下Spring Boot的快速搭建
下边使用的是IDEA快速搭建一个Spring Boot项目 (1)File--New-New Project (2)点击Next填写相应的信息 (3)点击Next,选择Dependencies,这里创 ...
- 在java项目中使用webservice
今天学习webservice,主要参考了网络上的一些文章. 1.关于原理的介绍:个人认为这篇文章写得不错了,戳这里. 2.关于demo的编写:个人认为这篇文章很简洁,也能运行成功,戳这里. 按照上面那 ...
- 自学Zabbix2.6-zabbix升级
如果你从zabbix 1.6升级到1.8或者zabbix 1.8升级到zbbix 2.0,那么你需要额外执行sql文件(path.sql): 但是在2.0之后这些步骤都不需要手动做,如果你的版本很低, ...
- springboot整合redis
springboot-整合redis springboot学习笔记-4 整合Druid数据源和使用@Cache简化redis配置 一.整合Druid数据源 Druid是一个关系型数据库连接池,是阿 ...
- IDEA配置jdk
提前安装jdk,配置环境变量 一.配置jdk 1.依次点开File -->Project Structure,点击左侧标签页,点击SDKs 2.点击+号,选SDK 3.按照这个顺序,可以配置多个 ...
- JaveScript运算符(JS知识点归纳三)
JaveScript中有许多的运算符,在这里就只说明一些需要注意的. 01 一元运算符 一元:指的是参与运算的操作数只有一个 最经常使用的是++ -- 计算规则: ++/-- 前置于操作数的时候 ...
- Java中数据类型及其之间的转换(转)
Java中数据类型及其之间的转换 基本的数据类型 基本类型有以下四种:1)int长度数据类型有:byte(8bits).short(16bits).int(32bits).long(64bits).2 ...