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讲解写出来,今天抽时间补上昨晚的例子讲解. 这里我先把昨天的两份代码贴过来,重新看下: ...
随机推荐
- 【整理】01. localhost_access_log 记录post请求参数
环境:apache-tomcat-7.0.57 利用Filter过去request请求参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ...
- Handler案例-简易打地鼠游戏(延时处理消息)
1. 游戏思路 (1)用ImageView显示地鼠,初始状态将ImageView设置为不可见状态.当开始游戏后,通过sendMessageDelayed()方法延时发送消息,使ImageView显示出 ...
- recycleView 使用指南1
最近看了很多 recycleView 的使用文章,一直晕乎乎的,完全不知道套路是啥.很多人都是直接上代码,但是却没有详细说明代码的使用,于是打算自己写写,理理思路.顺便帮助那些正在学习 android ...
- 安徽省2016“京胜杯”程序设计大赛_I_恶魔A+B
恶魔A+B Time Limit: 1000 MS Memory Limit: 65536 KB Total Submissions: 73 Accepted: 17 Description 相信大家 ...
- .NET Core 2.0 正式发布信息汇总
万众瞩目的.NET Core 2.0终于发布了,原定于9.19的dotnetconf大会的发布时间大大提前了1个月,.NET Core 2.0/.NET Standard 2.0的正式发布是.NET ...
- 浅析MySQL中的Index Condition Pushdown (ICP 索引条件下推)和Multi-Range Read(MRR 索引多范围查找)查询优化
本文出处:http://www.cnblogs.com/wy123/p/7374078.html(保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错误 ...
- MySQL进程处于Waiting for table flush的分析
最近遇到一个案例,很多查询被阻塞没有返回结果,使用show processlist查看,发现不少MySQL线程处于Waiting for table flush状态,查询语句一直被阻塞,只能通过K ...
- Linux改变语言设置的命令
--Linux语言设置--------------2013/09/22Linux中语言的设置和本地化设置真是一个很繁琐的事情,时不时的会出现乱码的情况,在这篇文章中讨论的是shell中出现乱码的一些解 ...
- 学习笔记TF041:分布式并行
TensorFlow分布式并行基于gRPC通信框架,一个master负责创建Session,多个worker负责执行计算图任务. 先创建TensorFlow Cluster对象,包含一组task(每个 ...
- OpenStack(企业私有云)万里长征第六步——OpenStack网络及虚拟机存储位置
一.前言 昨天又装了一遍OpenStack.码农这项工作就如同人生,永远有你想不到的意外在等着你,时而是惊喜时而是悲伤.在装的过程中倒是很顺利,只是在安装完成之后碰到了两个之前没有碰到的问题,这里记录 ...