Java虚拟机-堆、栈、运行时数据区

Java虚拟机-堆、栈、运行时数据区

前言:

本篇文章主要讲解java(JVM)在运行期间,其运行时数据区的作用、职责与划分。包括堆内存、栈内存–虚拟机栈、本地方法栈、方法区、常量池、程序计数器等概念。

文章主要内容摘自《深入理解Java虚拟机》第三版-周志明

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。

这些区域如图所示:
在这里插入图片描述

1. 程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,所以程序计数器是线程私有的。

JVM中的程序计数器也是在Java虚拟机规范中唯一一个没有规定任何OutOfMemoryError情况的区域。在任意时刻一条JVM线程只能执行一个方法的代码,方法可以是Java方法,或者是native方法。

  1. Java虚拟机中的程序计数器仅仅是虚拟机中的,存在于内存之上的“虚拟”计数器,而不是电脑中的实体程序计数器。
  2. JVM线程中执行的方法有2种类型:普通Java方法和由其他语言实现的native方法。如果当前执行的是普通Java方法,则程序计数器记录的是虚拟机字节码指令的地址。如果当前执行的是native方法,则计数器的值为空(Undefined)。

2. Java虚拟机栈

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表存放了编译器可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)和returnAddress类型(指向了一条字节码指令的地址)。

这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的long和double类型的数据会占用两个变量槽,其余的数据类型只会占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多个的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。这里的“大小”是指变量槽的数量。

Java虚拟机栈内存的大小既可以被实现成固定大小,也可以根据计算动态拓展或收缩,当前大部分的JVM实现是支持动态拓展的。

Java虚拟机栈可能发生的异常:

  1. 线程请求分配的栈容量 > Java虚拟机最大栈容量,则JVM会抛出StackOverFlowError异常。
  2. 如果Java虚拟机可动态拓展,则如果在拓展的过程中无法申请到足够的内存,就会抛出OurOfMemoryError异常。

3. 本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是分查相似的,其区别只是虚拟机栈为执行Java方法服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。

需要说明:Sun HotSpot虚拟机就直接将本地方法栈和Java虚拟机栈合二为一了。

4. Java堆

对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。

Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆”。从回收内存的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以Java堆中经常会出现“新生代”、“老年代”、“永久代”、“Eden空间”、“From Survivor空间”、“To Survivor空间”等名词。

  1. 根据《Java虚拟机规范》的规定,Java堆可以处于物理上不连续的内存空间,但在逻辑上它应该被视为连续的,单对于大对象,对数虚拟机实现出于实现简单、存储效率高的考虑,跟可能要去连续的内存空间。
  2. Java堆既可以被实现成固定大小的,也可以是扩展的,当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定)。
  3. 如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

5. 方法区

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、及时编译器编译后的代码缓存等数据。虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆(Non-Heap)”,目的是与Java堆区分开来。

《Java虚拟机规范》对方法区的约束是非常宽松的,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,甚至还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域的确是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收效果比较难令人满意,尤其是类型卸载,条件相当苛刻,但是这部分区域的回收又是又确实是必须的。

首先,要明确一个概念——方法区,是一个概念,是Java虚拟机规范中定义的概念,一个非堆的运行时数据区域,用于存放被JVM加载的类信息、常量、静态变量、及时编译器编译后的代码等数据,运行是常量池也是存放于方法区中。逻辑上的非堆表示和Java堆独立,那物理上呢?Java虚拟机规范中定义了方法区这个概念,但是并没有规定此区域是否需要垃圾收集。

在Java7以前,HotSpot虚拟机中,方法区也被称为”永久代“,因为在物理上,方法区使用的是有JVM开辟的堆内存,由于和Java堆共享内存并且内存空间由垃圾收集器统一分配和管理,自然的垃圾收集也拓展到了方法区上。此时,Java堆中分区为青年代(Yong Generation)和老年代(Old Generation),而方法区自然地被称为永久代(Permanent Generation)。

JVM虚拟机有不同的实现,比较主流的是sun公司的HotSpot虚拟机,在此才有”永久代的概念“,其他虚拟机不存在”永久代“这个概念

在Java8中,HotSpot虚拟机改变了原有方法区的物理实现,将原本由JVM管理内存的方法区的移到了虚拟机以外的计算机本地内存,并将其称为元空间(Metaspace)。这样一来,现在的方法区实际存储于元空间,再也不用和Java堆共享内存了,”永久代“也就永久地被撤销了。

尽管永久代撤销了,方法区这个逻辑上的空间一直是存在的,所以在Java8以后,方法区的垃圾回收在物理上就是对元空间的垃圾回收。由于元空间用的是计算机本地内存,所以理论上来说只要内存足够大,方法区就能有多大,实际上Metaspace的大小是可以通过参数来设定的,如果Metaspace的空间占用达到了设定的最大值,那么就会触发GC来收集死亡对象和类的加载器。常用的G1和CMS垃圾收集器都能很好的回收Metaspace区。

5.1 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table)。运行时常量池是Class文件中每一个类或接口的常量池表的运行时表示形式,每一个运行时常量池都在Java虚拟机的方法区中分配,在加载类或接口到虚拟机后,就创建对应的运行时常量池。常量池的作用是:

存放编译器生成的各种字面量和符号引用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建或运行时解析、翻译到具体的内存地址之中。

字面量(Literal):通俗理解就是Java中的常量,如字符串、声明为final的常量值等。

符号引用(Symbolic References):属于编译原理中的概念,包括下面三类常量:

  1. 类和接口的全限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值