JVM运行时数据区域

java虚拟机在运行时会把所管理的内存划分为若干不同的数据区域。这些区域都有各自的用途和创建以及销毁时间,有的随着虚拟机的启动而存在有的则依赖用户的线程的启动和结束而创建和销毁。根据SE7版的规定,虚拟机中会包括一下几个运行时的数据区域。
在这里插入图片描述

  • 程序计数器
    程序计数器是一块很小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器,在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。
    java虚拟机的多线程是通过线程的轮流切换并分配处理器的执行时间的方式来实现的,在任何一个确定的时间点,一个处理器(对多核处理器来说是指的一个核)都只会执行一条线程中的指令。因此,为了切换线程后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,每个线程的程序计数器互不影响,独立存储,我们称这类内存区域为线程私有的内存。
  • java虚拟机栈
    与程序计数器一样,java虚拟机栈也是线程私有的,它的生命周期与线程相同。java虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从调用到执行完成都对应着一个栈帧在虚拟机中入栈到出栈的过程。
    java虚拟机栈包含了局部变量表,局部变量表存放了编译期间可知的各种基本数据类型(int byte short等),对象的引用(reference类型,不等同于对象的本身,它可能是一个指向对象起始地址的引用指针,也可能是指向代表对象的句柄或其他与此对象相关的位置 )和returnAddress类型(指向一条字节码指令的地址)。
    局部变量表所需要的内存空间在编译期间完成分配,当进入一个方法时这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小
  • 本地方法栈
    本地方法栈和虚拟机栈的作用类似,它们之间的区别是虚拟机栈是为虚拟机执行java方法(字节码)提供服务,而本地方法栈则是为虚拟机中使用到的Native方法提供服务。在HotSpot虚拟机中直接把本地方法栈和虚拟机栈合二为一
  • java堆
    对于大多数java程序来说,java堆是虚拟机中管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时候创建,这块区域的唯一目的就是存放对象实例。几乎所有对象的实例都在这里分配,java堆是要垃圾回收器管理的主要区域有时也别叫做GC堆。从内存回收的角度来看,基本上都采用分代收集算法所以java堆还可以细分为新生代和老年代,在细致一点的有Eden空间,From Survivor空间,To Survivor空间等。从内存分配角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer)
  • 方法区
    方法区与java堆一样是各个线程共享的一块内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。java虚拟机把方法区描述为堆的一个逻辑部分,但是它却有一个别名Non-Heap(非堆),目的是与java堆区分开来。
    对于大多数在HotSpot开发者来说很多人把方法区称之为永久代,但是两者并不等价,仅仅是因为HotSpot虚拟机的设计团队把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾回收器可以像管理java堆一样来管理这部分内存。但是这样做的后果是更容易遇到内存溢出的问题(永久代有-XX:MaxPermSize的上限,J9和JRockit只要没有触碰到进程可用内存的上限,例如32位系统中的4GB,就不会出现问题),而且有极少数方法(例如String.intern())会因这个原因
    导致不同虚拟机下有不同的表现。JDK 1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出。
    Java虚拟机规范对方法区的限制非常宽松,除了可以使用不连续的内存地址除外和可以选择固定大小或是可以扩展外还可以选择不实现垃圾收集。相对而言垃圾回收在这个区域也是很少出现的,但是并非数据进入了方法区就如永久代名字一样永远的存在了,这个区域的内存回收的目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域内存很少被回收,尤其类型的卸载,但是这部分的内存回收确实是必要的。在Sun公司的BUG列表中若干个BUG就是因为HotSpot虚拟机对这个区域未完全回收而导致的BUG。
    在1.7之前在(JDK1.2 ~ JDK6)的实现中,HotSpot 使用永久代实现方法区,HotSpot 使用 GC分代来实现方法区内存回收。
    在1.8版中HotSpot取消了永久代,取而代之的是元空间。
    元空间是一块与堆不相连的本地内存。原本存在永久代的数据,一部分移到了java堆里面,一部分移到了本地内存里面(即元空间)(文档中原句:Move part of the contents of the permanent generation in Hotspot to the Java heap and the remainder to native memory.) 。永久代中原来存储的字符串常量(池)、符号引用(这两个在jdk7普遍就已经将其放在堆上了)和类的静态变量现在存储在java堆中,其余的数据作为元数据存储在元空间中。
    那么是不是也就没有方法区了呢?当然不是,方法区是一种规范,永久代和元空间是一种实现方式。元空间和永久代的存储位置不同,永久代是堆的一部分,和新生代和老年代地址是连续的,而元空间属于本地内存。它们的存储内容也不一样,元空间存储的是类的元信息。静态变量和常量池等并入堆中。相当于永久带的数据被分到了堆和元空间中。总结下来1.8做出这样更新的原因有以下几种
    • 字符串存在永久代中,容易出现性能问题和内存溢出。
    • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
    • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
    • Oracle 可能会将HotSpot 与 JRockit 合二为一。
  • 运行时常量池
    运行时常量池是方法区的一部分,Class文件中除了有类的版本,方法,字段,接口等信息描述外,还有一项就是常量池用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池存放。
    运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。
    运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值