JVM 学习笔记 - Java 虚拟机结构

2017-06-24 UFreedom 更多博文 » 博客 » GitHub »

JVM

原文链接 http://ufreedom.me/articles/2017/06/java_structure.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


Java 虚拟机结构

根据 Java 虚拟机规范,Java 的基本结构可归纳为如图所示:

类加载子系统

类加载子系统负责从文件系统或者网络中加载 Class 信息,加载的类信息会被存放在方法区,当然方法区不仅仅只存放类信息。

Java 运行时区域

运行时区域为 Java 虚拟在执行 Java 程序过程中所管理的内存区域,然后又根据不同的用途将这片区域再进一步划分为 线程共享区域线程私有区域,其中 线程共享区域 包括 程序计数器虚拟机栈本地方法栈 三个部分,而 线程私有区域 包括 方法区 两个部分。

程序计数器

很小的一片内存区域,也是唯一个 不会有 OutOfMemeryError 情况的区域。在线程切换时,为了保证切换后的线程能恢复到正确的执行位置,Java 虚拟机会为每个线程创建程序计数器,被用于记录当前线程所执行字节码的行号。每个线程的程序计数器都是互相独立的,互不影响。

虚拟机栈

虚拟机栈就是所谓的 Java 栈,它是 线程私有 的一块内存区域,虚拟机栈和线程执行密切相关,它的生命周期和线程相同。线程执行过程中的基本行为就是方法调用,在每个方法执行时,JVM 都会创建一个栈帧用于存储局部变量表,操作数栈,帧数据区等信息,每个方法从调用到执行完成,对应一个栈帧在虚拟机栈中入栈和出栈。

因为在函数调用过程中,都会有相应栈帧的创建与销毁,如果函数调用层次过深,导致栈空间不足以再创建栈帧,系统就会抛出 StackOverfloowError 栈溢出错误。如果虚拟机栈可以动态进行扩展,但是在扩展时,无法申请到足够的内存,则会抛出 OutOfMemeoryError 异常。

局部变量表

局部变量表用于保存函数的参数以及局部变量,局部变量表的生命周期开始于栈帧的创建,结束于栈帧的销毁。它存放了编译期可知的各种基本数据类型,对象引用类型,和 returnAdress 类型

操作数栈

操作数栈主要用于保存计算过程的中间结果。

帧数据区

帧数据区内有用来支持方法返回的数据,异常处理表。另外帧数据区内还有访问常量池的指针,以便程序访问常量池

本地方法栈

对比虚拟机栈,虚拟机栈服务于 Java 方法,而本地方法栈主要用来服务 Native 方法。

了解完线程私有区域,下面就来了解线程共享区域:方法区和堆。

Java 堆是 JVM 结构中最大的一块,它可以是物理上不连续的内存空间,只要逻辑上连续就行。

Java 堆是 所有线程共享的区域,几乎所有的对象实例都会存放在堆中。在 Java 虚拟机规范中:所有的对象实例以及数据都会在堆上分配。但是随着现在一些技术的发展,比如说可以利用逃逸分析技术,将线程私有的对象打散分配在虚拟机栈上。

堆中存放了大量的实例对象,所以它也是垃圾回收管理的主要区域。垃圾回收技术主流的算法有:标记-清除算法,复制算法,标记-整理算法。HotSpot 采用的是分代收集算法,它将 Java 堆划分为:新生代老年代,其中新生代存储 新生对象 或者 年龄较小 的对象,而老年代存放 大对象年龄较大 的对象。新生代又被分为 eden 区From Survivor空间,To Survivor 空间,From Survivor空间,To Survivor 空间又被称为:s0 区,s1区,或者 from 区和 to 区。 Java 虚拟机对堆进行如此细致的划分,主要还是为了更好的分配和回收内存,以便于管理如此众多的对象实例。

方法区

方法区用于存储已被类加载子系统加载的类信息,常量,静态变量等数据。

方法区只是 JVM 规范中定义的一个概念,不同的虚拟机可能有不同的实现方式。HotSpot 则采用 永久代(Permanenet Generation) 实现方法区,使用 GC 分代算法对方法区进行管理。永久代只是 HotSpot 特有的一种概念,在别的虚拟机是不存在这个概念的,在 JDK 1.7 中,HotSpot 已将永久代移除,用元数据区取代。

运行时常量池 是方法区的一部分,它用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的常量池中存放。 GC 回收作用于方法区时,主要目标是对常量池回收和对类型的卸载,JVM 虚拟机规范规定,当方法区无法再进行内存分配时,将抛出 OutOfMemoryError 异常。

直接内存

直接内存区域是一个堆外内存,它独立于运行时数据区。主要被 NIO 类用于通道(Channel)和缓冲区的 IO 操作,它通过 Native 函数库进行分配,然后在 Java 堆中使用 DirectByteBuffer 对象进行引用操作。

垃圾回收系统

垃圾回收系统主要关注的是 Java 堆方法区,因为线程私有区域内存随着线程而生,随着线程而.Java 虚拟机栈的栈帧随着方法的执行和退出进行入栈和出栈操作,这几个区域的内存分配和回收都具备确定性,线程和方法的结束,内存就会被自然的回收掉。但是 Java 堆和方法区则不同,这部分区域只有在程序运行期间才知道哪些对象会被创建,这部分内存的分配和回收都需要根据运行时情况进行灵活处理。

在进行垃圾回收时,首先先要判断对象是否已死,目前虚拟机采用的是可达性分析算法,这个算法的主要思想是通过一系列的 GC Roots 对象作为起始点,然后开始向下搜索,搜索过程中所走的路径成为 引用链,当一个对象对 GC Roots 没有任何引用链相连时,则标记改对象是不可达,被列为可回收对象。 标记完对象后,下一步就是使用某种算法将对象回收,目前主流的回收算法有:标记-清除算法复制算法标记-整理算法。HotSpot 中,对堆内存先进行分代处理,然后新生代采用复制算法,老年代采用标记-整理算法。