深入理解java虚拟机【Java虚拟机类生命周期】
C/C++等纯编译语言从源码到最终执行一般要经历:编译、连接和运行三个阶段,连接是在编译期间完成,而java在编译期间仅仅是将源码编译为Java虚拟机可以识别的字节码Class类文件,Java虚拟机对中Class类文件的加载、连接都在运行时执行,虽然类加载和连接会占用程序的执行时间增加性能开销,但是却可以为java语言带来高度灵活性和扩展性,java的针对接口编程和类加载器机制实现的OSGi以及热部署等就是利用了运行时类加载和连接的特性,java的Class类在虚拟机中的生命周期如下:
上图中加载、验证、准备、初始化和卸载这个五个阶段的顺序是确定的,而解析阶段则不一定,在某些情况下为了支持java语言的运行时动态绑定,也可以在初始化阶段之后再开始。
(1).加载:
Java虚拟机把Class类文件加载到内存中,并对Class文件中的数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程。
在加载阶段,java虚拟机需要完成以下3件事:
a.通过一个类的全限定名来获取定义此类的二进制字节流。
b.将定义类的二进制字节流所代表的静态存储结构转换为方法区的运行时数据结构。
c.在java堆中生成一个代表该类的java.lang.Class对象,作为方法区数据的访问入口。
加载阶段与连接阶段是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,这些夹在加载阶段之中进行的动作仍然属于连接阶段,加载和连接阶段仍然保持着固定的先后顺序。
(2).验证:
验证是连接阶段的第一步,其目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的安全,如果验证失败,会抛出java.lang.VerifyError异常。
验证阶段的主要工作有:
a.文件格式验证:验证Class文件魔数、主次版本、常量池、类文件本身等等。
b.元数据验证:主要是对字节码描述的信息进行语义分析,包括是否有父类、是否是抽象类、是否是接口、是否继承了不允许被继承的类(final类)、是否实现了父类或者接口的方法等等。
c.字节码验证:是整个验证过程中最复杂的,主要进行数据流和控制流分析,如保证跳转指令不会跳转到方法体之外的字节码指令、数据类型转换安全有效等。
d.符号引用验证:发生在虚拟机将符号引用转化为直接引用的时候(连接第三阶段-解析阶段进行符号引用转换为直接引用),符号引用验证的目的是确保解析动作能正常执行,如果无法通过符号引用验证,则会抛出java.lang.IncompatibleClassChangeError异常的子类异常,如java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。
验证阶段对于虚拟机来说非常重要,但是不是一个必需的阶段,如果所运行的代码已经反复被使用和验证过了,可以通过-Xverify:none参数关闭大部分的验证措施,以提高虚拟机时间时间。
(3).准备:
准备阶段是正式为类变量(静态变量,注意不是实例变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
对于普通非final的类变量,如public static int value = 123;在准备阶段过后的初始值是0(数据类型的零值),而不是123,而把123赋值给value是在初始化阶段才进行的动作。
对于final的类变量,即常量,如public staticfinal int value =123;在准备阶段过程的初始值直接就是123了,不需要准备为零值。
(4).解析:
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用(SymbolicReference):以一组符号来描述所引用的目标,与虚拟机内存布局无关,引用的目标不一定已经被加载到虚拟机内存中。
直接引用(DirectReference):可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用和虚拟机实现的内存布局相关,同一个符号引用在不同虚拟机上翻译处理的直接引用不一定相同,如果有了直接引用,则引用的目标对象必须已经被加载到虚拟机内存中。
解析的动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行解析。
(5).初始化:
初始化是类使用前的最后一个阶段,在初始化阶段java虚拟机真正开始执行类中定义的java程序代码。
Java虚拟机规范严格规定了有且只有以下四种情况必须立即对类进行初始化:
a.遇到new、获取静态变量(final常量除外)、为静态变量赋值以及调用静态方法时,如果类没有进行过初始化,则需要先触发其初始化。
b.使用java.lang.reflect包的方法对类进行反射调用的时候(Class.forName(…)),如果类还没有初始化,需要先触发对其的初始化。
c.当初始化一个类的时候,如果发现其父类还没有初始化,则需要先触发对其父类的初始化。
d.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
上述四种情况称为对一个类的主动引用,除此之外的引用方式都不会触发初始化,称为被动引用。
初始化的过程其实就是一个执行类构造器<clint>方法的过程,类构造器执行的特点和注意事项:
1).类构造器<clint>方法是由编译器自动收集类中所有类变量(静态非final变量)赋值动作和静态初始化块(static{……})中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定。静态初始化块中只能访问到定义在它之前的类变量,定义在它之后的类变量,在前面的静态初始化中可以赋值,但是不能访问。
2).类构造器<clint>方法与实例构造器<init>方法不同,它不需要显式地调用父类构造器方法,虚拟机会保证在调用子类构造器方法之前,父类的构造器<clinit>方法已经执行完毕。
3).由于父类构造器<clint>方法先与子类构造器执行,因此父类中定义的静态初始化块要先于子类的类变量赋值操作。
4). 类构造器<clint>方法对于类和接口并不是必须的,如果一个类中没有静态初始化块,也没有类变量赋值操作,则编译器可以不为该类生成类构造器<clint>方法。
5).接口中不能使用静态初始化块,但可以有类变量赋值操作,因此接口与类一样都可以生成类构造器<clint>方法。
接口与类不同的是:
首先,执行接口的类构造器<clint>方法时不需要先执行父接口的类构造器<clint>方法,只有当父接口中定义的静态变量被使用时,父接口才会被初始化。
其次,接口的实现类在初始化时同样不会执行接口的类构造器<clint>方法。
6).java虚拟机会保证一个类的<clint>方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,只会有一个线程去执行这个类的<clint>方法,其他线程都需要阻塞等待,直到活动线程执行<clint>方法完毕。
初始化阶段,当执行完类构造器<clint>方法之后,才会执行实例构造器的<init>方法,实例构造方法同样是按照先父类,后子类,先成员变量,后实例构造方法的顺序执行。
(6).使用:
当初始化完成之后,java虚拟机就可以执行Class的业务逻辑指令,通过堆中java.lang.Class对象的入口地址,调用方法区的方法逻辑,最后将方法的运算结果通过方法返回地址存放到方法区或堆中。
(7).卸载:
当对象不再被使用时,java虚拟机的垃圾收集器将会回收堆中的对象,方法区中不再被使用的Class也要被卸载,否则方法区(Sun HotSpot永久代)会内存溢出。
Java虚拟机规定只有当加载该类型的类加载器实例为unreachable状态时,当前被加载的类型才被卸载.启动类加载器实例永远为reachable状态,由启动类加载器加载的类型可能永远不会被卸载,类型卸载仅仅是作为一种减少内存使用的性能优化措施存在的,具体和虚拟机实现有关,对开发者来说是透明的.
卸载自定义来加载器加载的类的可靠做法为:
a.每次创建特定类加载器的新实例来加载指定类型的不同版本,这种使用场景下,一般就要牺牲缓存特定类型的类加载器实例以带来性能优化的策略了.
b.对于指定类型已经被加载的版本, 会在适当时机达到unreachable状态,被unload并垃圾回收.每次使用完类加载器特定实例后(确定不需要再使用时), 将其显示赋为null, 这样可能会比较快的达到jvm 规范中所说的类加载器实例unreachable状态, 增大已经不再使用的类型版本被尽快卸载的机会.
深入理解java虚拟机【Java虚拟机类生命周期】的更多相关文章
- java类生命周期,类的“加载,连接,初始化,使用,卸载过程”详解
“ 如果说核心类库的 API 比做数学公式的话,那么 Java 虚拟机的知识就好比公式的推导过程” 每本Java入门书籍在介绍Java这门语言的时候都会提到Java跨平台,“一次解释,到处运行的特点“ ...
- Java多线程之线程的生命周期
Java多线程之线程的生命周期 一.前言 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(R ...
- 第24章 java线程(3)-线程的生命周期
java线程(3)-线程的生命周期 1.两种生命周期流转图 ** 生命周期:**一个事物冲从出生的那一刻开始到最终死亡中间的过程 在事物的漫长的生命周期过程中,总会经历不同的状态(婴儿状态/青少年状态 ...
- (转)JVM类生命周期概述:加载时机与加载过程
原文地址: http://blog.csdn.net/justloveyou_/article/details/72466105 JVM类加载机制主要包括两个问题:类加载的时机与步骤 和 类加载的方式 ...
- JVM类生命周期概述:加载时机与加载过程
一个.java文件在编译后会形成相应的一个或多个Class文件,这些Class文件中描述了类的各种信息,并且它们最终都需要被加载到虚拟机中才能被运行和使用.事实上,虚拟机把描述类的数据从Class文件 ...
- Mybatis核心类生命周期和管理
Mybatis核心类生命周期和管理 原文链接:https://blog.csdn.net/qq1134550437/article/details/51960480 1.SqlSessionFacto ...
- (转)《深入理解java虚拟机》学习笔记7——Java虚拟机类生命周期
C/C++等纯编译语言从源码到最终执行一般要经历:编译.连接和运行三个阶段,连接是在编译期间完成,而java在编译期间仅仅是将源码编译为Java虚拟机可以识别的字节码Class类文件,Java虚拟机对 ...
- java类生命周期详细解析
(一)详解java类的生命周期 引言 最近有位细心的朋友在阅读笔者的文章时,对java类的生命周期问题有一些疑惑,笔者打开百度搜了一下相关的问题,看到网上的资料很少有把这个问题讲明白的,主要是因为目前 ...
- java 静态变量生命周期(类生命周期)
Static: 加载:java虚拟机在加载类的过程中为静态变量分配内存. 类变量:static变量在内存中只有一个,存放在方法区,属于类变量,被所有实例所共享 销毁:类被卸载时,静态变量被销毁,并释放 ...
随机推荐
- Linux防火墙
9.1 认识防火墙 只要能够分析与过滤进出我们管理之网域的封包数据, 就可以称为防火墙. 硬件防火墙 由厂商设计好的主机硬件, 这部硬件防火墙内的操作系统主要以提供封包数据的过滤机制为主,并将其他 ...
- Karma +Jasmine+ require JS进行单元测试并生成测试报告、代码覆盖率报告
1. 关于Karma Karma是一个基于Node.js的JavaScript测试执行过程管理工具(Test Runner). 该工具可用于测试所有主流Web浏览器,也可集成到CI(Continuou ...
- git服务器的搭建
http://blog.jobbole.com/25944/ 1,概念 git服务器:就是一个仓储,一个大家都可以访问的公共仓储,大家可以从这个仓储中拉取和推送数据. 协议: 与gist服务通讯的仓储 ...
- ORACLE 分析函数整理汇总
1. 聚合分析函数 SUM MIN MAX AVG COUNT 这类聚合类分析函数可以在窗口中分组
- JQuery Object vs. DOM element
JQuery Object 和 DOM的区别 HTML DOM 定义了访问和操作HTML文档的标准方法.其中 document 是DOM 树的根对象 ,在浏览器宿主环境中,可以通过JS操作HTML D ...
- linux shell 去重 uniq和sort的区别
sort -u 和 uniq都能起到删除重复信息的功能,那么他们的区别究竟在哪呢?$ cat test jasonjasonjasonfffffjason 下面分别执行三个命 ...
- 用java操作XML文件(DOM解析方式)
XML 可扩展标记语言(Extensible Markup Language),是独立于软件和硬件的传输工具. XML的作用: (1)用作配置文件 (2)简化数据共享 (3)简化数据传输 XML DO ...
- Scrum会议10.19
Scrum会议 组名称:好好学习 项目名称:记账本 参会成员:林莉(Master)胡丽娜 宫丽君 汪东涵 时间:2016.10.19 已完成内容: 1.完成新项目的查找,查找学姐的代码和项目. 2.理 ...
- 02-JAVA中的基本语法
第一个动手动脑: 首先用枚举类型定义两个变量,然后判断两个变量是否引用同一对象,在判断枚举类型是否是原始数据类型,再从字符串中转换,最后列出所有的对象. 很明显,这两个变量都是枚举类型,但是这两个变量 ...
- 打开office2010里面的access,总是提示要配置Office single image
刚安装了,office2010,打开里面的access时,总是提示要配置Office single image,但打开word和excel没问题,很是不舒服 在网上找到N种方法,试下来还是不行. 后来 ...