朝花夕拾——初探Java虚拟机及其加载过程

这一周的颜如玉系列里,大部分都在研究Android的热补丁技术。这里不做深入,原理无非是在Android在加载Dex时,先记载新的class文件,以此替换旧的class文件。附上张鸿洋的的Blog链接,有兴趣的朋友可以了解。

为了彻底了解这一技术原理,因此打算从Java 虚拟机(Java Virtual Machine:以下简称 JVM)的加载开始学习。


本篇会以系统的方式为大家带来以下内容:

  1. JVM及其生命周期介绍
  2. JVM体系结构介绍
  3. JVM加载Class过程

JVM生命周期介绍

什么是JVM?
《深入JAVA虚拟机第二版》中是这样定义的:

  • 抽象规范
  • 一个具体的实现
  • 一个运行中的虚拟机实例

以上内容有点奇怪。一个虚拟机怎么有三种截然不同的定义?
因为在不同的环境下,这JVM有三种含义:

假如你是要实现某某平台下的JVM,那么一开始,JVM对你来说就是一个规范或者一个概念。然后,你可以采用软件,或软硬结合的方式来具体实现JVM的规范(即具体实现)。当你运行一个Java程序的时候,JVM就是一个运行中的虚拟机实例。

好吧,的确有点奇怪。不过对我们开发者来说,最后一个定义更符合我们的理解。

JVM的生命周期

对于一个JVM而言,它的生命周期与Java程序的运行情况息息相关,当你想开启一个Java程序时,会先生成一个JVM的运行实例,这个实例通过调用程序中的 main(String[] args)开启Java程序。我们的Java程序就在这个JVM实例中运行的。当你退出程序,当前的JVM实例也被销毁。
请注意:这是 1 <–>1 的关系,一个JVM只负责运行一个Java程序。


JVM体系结构介绍

从上面,我们知道一个Java程序是在一个JVM中运行的。那JVM里到底有什么东西呢?

下图是JVM的体系结构图,包含着我们的JVM里所有的东西。

这里写图片描述

  • 类装载子系统:涉及了Java虚拟机的其他几个部分,以及几个来自java.lang库的类(如 ClassLoader:为程序提供了访问类装载器机制的接口),详细内容将在下文JVM加载过程部分中讲诉

  • 运行时数据区(内存):分为5个模块,这个比较重要,稍后会有详细说明。

  • 本地方法接口:通过这个接口可使得JVM内的Java程序与本地方法进行交互,咦,怎么那么眼熟?其实就是我们的JNI

  • 执行引擎:JVM运行我们Java代码的核心实现。通过PC寄存器中的指令执行Java方法,也可以执行本地方法接口。需要注意的是一个线程对应一个执行引擎

作为一个Coder,OutOfMemryError,StackOverFlowError这些错误应该是经常遇到的。那为什么会产生这些错误呢,就与我们的运行时数据区(内存区)有关系。
下面,我们按是否线程私有来区分这五个模块:
(我说的细的掉渣,如果你只想粗浅的知道 点这

所有线程均可访问:方法区,堆

  • 方法区:简单的说,保存着类数据,即被装载的类,其基本信息,与一些额外信息都在方法区中被保存(当某类没有被引用时,不同的JVM下,不一定实现垃圾回收,无内存可分配时候,会出现OutOfMemoryError)

    有人问基本信息是什么?

  • 这个类型的全限定名(就是完整类名,如Java.lang.Object等)
  • 这个类型的父类的全限定名(除非这个类的是Object类,没有父类了)
  • 这个类型是类类型,还是接口类型
  • 这个类型的访问修饰符(如public,private等 )
  • 这个类型的所有接口的全限定名的有序列表

    额外信息又是什么?

  • 这个类型的运行时常量池:所有的常量,与该类中所使用的所有类在方法区内的符号引用 (也就是说,所有在该类中使用的类,都会保留它的一份在方法区内的符号引用,该类容易第一时间找到需要使用的类相关信息。)
  • 字段信息 (字段的名字,类型,修饰符)
  • 方法信息 (方法名,返回类型,入参的数量与类型,修饰符,方法中局部变量大小,方法的字节码,异常表)
  • 除了常量以外的所有 类(静态)变量(所有类实例共享,即时没有类实例也可以被访问,注意这类变量些变量只和类有关,而非类的实例。他们只作为一部分类型信息保存在方法区而已。只有在被使用的过程中才会被分配空间)
  • 一个到ClassLoader的引用 (如果是由启动类装载器装载,不记载。由用户自定义的类装载器装载的,保留引用)
  • 一个到Class类的引用 (使得可以通过Class类可以轻易的获取当前内的类在方法区中的信息,与引用。如:可以用Class.forNmae(*)来得到当前的Class的对象引用)

  • :简单的说,所有的实例化出的对象,数组都在这个区域,不过,所有线程都可以访问的时候,需要考虑线程安全问题。这个区域,不一定要连续的空间,支持动态的扩张和缩小。内存泄露一般就发生在这。(所有的JVM都在这实现了垃圾回收,内存无法支持创建对象时,会出现OutOfMemoryError

单一线程私有: Java栈,PC寄存器,本地方法栈

  • Java栈:以帧为单位保持着Java的运行状态,一个方法为一个栈帧,调用时,入栈,结束返回时出栈。

    • 栈帧:包括局部变量区,操作数栈,帧数据区;其中局部变量内保存着入参,局部变量;操作数栈:执行指令时,以栈的形式作为数据的临时储存与操作区(建议看Blog;帧数据区包含着一些数据来支持常量池数据的解析,正常返回,与异常委派机制。当栈帧内存大小固定时,超过了大小,会发生StackOverflowError 错误,当栈帧为动态增长时,申请的内存大小不足时,会发生OutOfMemoryError错误
  • PC寄存器:或者说是程序计数器更为恰当,只有一个字长大小。记录着下一条的将要被执行的指令地址,除本地方法除外。

  • 本地方法栈:一般为调用C/C++方法,实现和Java栈类似。不过当进入本地方法栈时,pc寄存器的值为“undefined”。

以上是按逻辑来区分,不过按物理上区分应该是以下这样的(这点上众说纷纭,不要问我….)
这里写图片描述


JVM加载Class过程

我们都知道编译好的class是一个二进制的文件,那我们的JVM是如何加载他们的呢?就是我们上面介绍的类装载子系统

加载一个class文件的在这里分为3步

1.装载:查找相应的class文件并装载二进制数据。
2.连接:执行验证,准备,以及解析(可选)
    验证:确保被导入类型的正确性
    准备:为类变量分配内存,并将其初始化为默认值
    解析:把类型中的符号引用转化为直接引用
3.初始化:把类变量初始化为正确初始值。

更加详细点这里

而在代码里完成这些的,是我们的ClassLoader类。
以下给出其他学习链接
ClassLoader双亲加载
ClassL:oader类 中文文档
我想我写的绝对不会比他们更好。所以恬不知耻的引用了他们的文章。

谢谢各位。本篇的很多理论来自《深入JAVA虚拟机第二版》,有兴趣的朋友可以去看看。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值