jvm系列 (五) ---类的加载机制
类的加载机制
目录
什么是类的加载机制
- 简单的说,就是虚拟机把类的数据从Class文件加载到内存,对数据进行检验,转换解析和初始化,最终形成可以被虚拟机直接使用的java类型
类加载的过程
- 类加载的过程:加载(注意和类加载区别开来),验证,准备,解析,初始化
加载
- 通过一个类的全限定名来获取其定义的二进制流
- 将这个字节流所代表的静态存储结构(类信息,静态常量、常量)转化成方法区的运行时数据结构
- 在内存中生成一个代表这个类的Class对象,作为方法区中这些数据的访问入口
验证
- 目的:确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的安全。Class文件可以从任何途径来,包括我们手动编辑一个Class文件,如果我们不对他进行验证,那么就可能会出现安全问题。
- 文件格式验证:是否符合Class文件的格式的规范,能够被当前版本的虚拟机处理。通过这个验证,可以保证字节流能够正确解析并存储于方法区,格式上符合一个Java类型信息的要求。只有通过这个验证后,字节流才会进入内存的方法区进行存储。
- 元数据验证,对字节码描述的信息进行语义分析,比如这个类是否有父类,是否继承了final修饰的类,保证描述的信息符合java语言规范。
- 字节码验证,通过数据流和控制流分析,确定程序语义是和合法的,符合逻辑的。这个阶段对类的方法体进行验证分析,保证在类的方法运行时不会危害虚拟机。比如说验证类型转换是否合理,比如说父类不能转为子类
- 符号引用验证,对常量池中的各种符号引用的信息进行匹配性校验,比如验证符号引用中通过字符串描述的全限定名是否能找到对应的类。确保解析动作能够正常执行。
准备
- 为类的静态变量分配内存并设置类常量的初始值
public static int value = 123
- 这段代码经过准备阶段后,value的值是0
public static final int value = 123
- 被static和final同时修饰的,经过准备阶段后,value是123
解析
- 虚拟机将常量池内的符号引用(类和接口的全限定名、字段的名称和描述符、方法的名称和描述符)替换为直接引用(可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄)的过程
初始化
- 准备阶段变量已经赋过系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源。说白了就是初始化静态变量指定的值和执行静态代码块
必须初始化的情况
- 用new创建对象(准确来说是new指令)的时候
- 读取设置一个类的静态字段,调用类的静态方法
- 使拥reflect包的方法对类进行反射调用的时候
- 初始一个类的时候,如果一个类的父类还没有初始化的时候,触发父类的初始化
- 主类(main方法的类)
public class SuperClass
{
public static int value = 123;
static
{
System.out.println("SuperClass init");
}
}
public class SubClass extends SuperClass
{
static
{
System.out.println("SubClass init");
}
}
public class Test
{
public static void main(String[] args)
{
System.out.println(SubClass.value);
}
}
//输出:SuperClass init
123
- 通过子类来获取父类的静态字段,只会触发父类的初始化不会触发子类的初始化
public static void main(String[] args)
{
SuperClass s[]=new SuperClass[10];
}
- 并没有输出SuperClass init,也就是没有初始化这个类,通过反编译发现用的指令是newarray,所以并没有触发初始化,如果不是创建数组而是创建对象的话,会输出SuperClass init,通过反编译发现指令是new而不是newarray
class ConstClass
{
public static final String HELLOWORLD = "Hello World";
static
{
System.out.println("ConstCLass init");
}
}
public class TestMain
{
public static void main(String[] args)
{
System.out.println(ConstClass.HELLOWORLD);
}
}
- 并没有输出ConstClass init,也就是说明并没有触发该类初始化,这是因为在编译阶段通过常量传播优化,这个字符串已经存储在NotInitialization类的常量池中,也就是实际上对这个常量的引用变为NotInitialization类对自身常量池的引用了
类加载器
- 通过一个类的全限定名来获取其定义的二进制字节流的代码模块称为类的加载器
一个类需要加载他的类加载器和这个类本身来确定在虚拟机的唯一性,也就是说两个类来源一个Class文件,如果加载他们的类加载器不同,那么这两个类不同
public static void main(String[]args) throws Exception{ ClassLoader myLoader=new ClassLoader(){ @Override public Class<?>loadClass(String name)throws ClassNotFoundException{ try{ String fileName=name.substring(name.lastIndexOf(".")+1)+".class"; InputStream is=getClass().getResourceAsStream(fileName); if(is==null){ return super.loadClass(name); } byte[]b=new byte[is.available()]; is.read(b); return defineClass(name,b,0,b.length); } catch(IOException e){ throw new ClassNotFoundException(name); } } } ; Object obj=myLoader.loadClass("Person").newInstance(); System.out.println(obj.getClass()); System.out.println(obj instanceof Person);//false }
在这里自己定义一个类加载器,而在这里实际上是存在两个Person类,一个是我们自己定义的类加载器加载的,一个是系统应用程序类加载器加载的
- 从虚拟机的角度,只有两种不同的类加载器,一种是启动类加载器,C++实现,是虚拟机的一部分。另一部分就是其他的类加载器,有java实现,继承自抽象类ClassLoader,属于虚拟机之外。
从java开发人员的角度看,分为启动类加载器,扩张类加载器,应用类加载器。
启动类加载器(Bootstrap ClassLoader)
- 加载的是JAVA_HOME/lib下的类库,启动类加载器无法被java程序直接引用
扩展类加载器(Extension ClassLoader)
- 这个类加载器由sun.misc.Launcher$ExtClassLoader实现,它负责用于加载JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量指定所指定的路径中所有类库,开发者可以直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader)
- 由sun.misc.Launcher$AppClassLoader实现,负责加载用户类路径(java.class.path)上指定的类库,开发者可以直接使用这个类加载器
双亲委派模型
- 图描述的就是所谓的双亲委托模型,除了顶层的启动类加载器外,其他加载器都有他的父加载器,但这不是继承关系,而是使用组合来复用父加载器的代码
- 工作过程:如果一个类收到类加载的请求,它首先不会自己去尝试去加载这个类,而是把这个请求委托给父类加载器去完成,所有的加载请求最后都会传授到顶层的启动类加载器,只有当父类加载器在他的搜索范围找不到所需的类的时候,也就是无法完成加载请求的时候,子加载器才会尝试去加载。
为什么要双亲委派模型
例如java.lang.Object,存放于rt.jar中,无论哪一个类加载器要去加载这个类,最终都是由Bootstrap ClassLoader去加载,因此Object类在程序的各种类加载器环境中都是一个类。相反,如果没有双亲委派模型,由各个类加载器自己去加载的话,如果用户自己编写了一个java.lang.Object,并放在CLASSPATH下,那么系统会出现多个不同的Objcet类
如果两个类加载器都要加载一个Class,如果没有双亲委托,各自去加载,那么会加载两份Class文件
双亲委托模型的实现
protected synchronized Class<?> loadClass(String name, boolean resolve)
307 throws ClassNotFoundException
308 {
309 // First, check if the class has already been loaded检查是否已经被加载
310 Class c = findLoadedClass(name);
//如果还没被加载
311 if (c == null) {
312 try {
//父加载器是否为null,因为启动类加载器是C++代码编写的,所以父加载器为null的时候,说明此时父加载器是启动类加载器。如果父加载器不为空,让父加载器去加载
313 if (parent != null) {
314 c = parent.loadClass(name, false);
315 } else {
316 c = findBootstrapClass0(name);
317 }
318 } catch (ClassNotFoundException e) {
319 // If still not found, then invoke findClass in order如果父加载器无法加载,调用本身的方法的来进行类加载器
320 // to find the class.
321 c = findClass(name);
322 }
323 }
324 if (resolve) {
325 resolveClass(c);
326 }
327 return c;
328 }
我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)
作者:jiajun 出处: http://www.cnblogs.com/-new/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。
jvm系列 (五) ---类的加载机制的更多相关文章
- JVM:Java 类的加载机制
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验,转换,解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制. 类的生命周期 类从被加载到虚拟机内 ...
- jvm系列(一):java类的加载机制
java类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装 ...
- JVM(1):Java 类的加载机制
原文出处: 纯洁的微笑 java类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang. ...
- Jvm类的加载机制
1.概述 虚拟机加载Class文件(二进制字节流)到内存,并对数据进行校验.转换解析和初始化,最终形成可被虚拟机直接使用的Java类型,这一系列过程就是类的加载机制. 2.类的加载时机 类从被虚拟机加 ...
- JVM-01:类的加载机制
本文从 纯洁的微笑的博客 转载 原地址:http://www.ityouknow.com/jvm.html 类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内 ...
- Java虚拟机JVM学习02 类的加载概述
Java虚拟机JVM学习02 类的加载概述 类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对 ...
- 深入java虚拟机学习 -- 类的加载机制
当看到"类的加载机制",肯定很多人都在想我平时也不接触啊,工作中无非就是写代码,不会了可以百度,至于类,jvm是怎么加载的我一点也不需要关心.在我刚开始工作的时候也觉得这些底层的内 ...
- 24.类的加载机制和反射.md
目录 1类的加载连接和初始化 1.1类的加载过程 1.2类的加载器 1.2.1类的加载机制 1类的加载连接和初始化 1.1类的加载过程 类的加载过程简单为分为三步:加载->连接->初始化 ...
- 深入java虚拟机学习 -- 类的加载机制(续)
昨晚写 深入java虚拟机学习 -- 类的加载机制 都到1点半了,由于第二天还要工作,没有将上篇文章中的demo讲解写出来,今天抽时间补上昨晚的例子讲解. 这里我先把昨天的两份代码贴过来,重新看下: ...
随机推荐
- spring持久类po或者javabean为什么常常实现序列化?
无论用hibernate或者mybatis结合spring做开发还是其他,系统里持久类往往要实现序列化, implements Serializable.我还是比较好奇,为什么要这样做呢?一直只知道个 ...
- ida和idr机制分析(盘符分配机制)
# ida和idr机制分析 ida和idr的机制在我个人看来,是内核管理整数资源的一种方法.在内核中,许多地方都用到了该结构(例如class的id,disk的id),更直观的说,硬盘的sda到sdz的 ...
- Mook第八周习题 单词长度(4分)(1)题
题目内容: 你的程序要读入一行文本,其中以空格分隔为若干个单词,以'.'结束.你要输出这行文本中每个单词的长度.这里的单词与语言无关,可以包括各种符号,比如"it's"算一个单词, ...
- 基于.NET CORE微服务框架 -Api网关服务管理
1.前言 经过10多天的努力,surging 网关已经有了大致的雏形,后面还会持续更新完善,请大家持续关注研发的动态 最近也更新了surging新的版本 更新内容: 1. 扩展Zookeeper封装2 ...
- 以太网帧、TCP与UDP段以及IP数据报格式总结
传输层及其以下的机制由内核提供,是操作系统的一部分,应⽤层由⽤户进程提供应⽤层数据通过协议栈发到⽹络上时,每层协议都要加上⼀个数据⾸部(header),称为封装.不同的协议层对数据包有不同的称谓,在传 ...
- python——变量
参考资料: Python程序设计与实现 变量名的命名规则 仅仅由大.小写英文字母,下划线(_),数字(不可作为变量名的开头)组合而成: 不能使用Python关键字和函数名作为变量名: 变量名不能包含空 ...
- Orleans稍微复杂的例子—互动
这是Orleans系列文章中的一篇.首篇文章在此 我费力费心的翻译过官方的教程,但是本人英语词汇量不高,可是架不住电子词典啊-只要肯花时间,我这些内容谁都可以做出来.所以这个事例告诉我们一个道理,那就 ...
- js版贪吃蛇
之前没有写博客的习惯,这是我的第一个博客,有些的不好的地方,希望大家多多提意见 js版的贪吃蛇相对比较简单,废话不多说直接上代码,有需要注意的地方我会标红,github源码地址https://gith ...
- js中callback.call()和callback()的区别
js中callback.call()和callback()的区别在js中callback.call()和callback() 有什么区别,举个例子:function a(){alert('hello! ...
- flex详解
布局的传统解决方案,基于盒状模型,依赖 display属性 + position属性 + float属性.它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现. 一.Flex布局是什么? Flex ...