JVM运行时数据区

 

目录

JVM运行时数据区

线程私有

程序计数器(Program Counter Register)

Java虚拟机栈

本地方法栈(Native  Method  Stack)

线程间共享

Java堆

方法区(Method  Area)/永久带

运行时常量池(Running  Constant  Pool)

JDK1.8废弃永久带替换为元空间


Java虚拟机有自动内存管理的机制. Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域. 这些区域都有各自的用途.

JVM运行时数据区

 

线程私有

程序计数器(Program Counter Register)

  • 程序计数器是一块较小的内存空间, 可以看做是当前线程所执行的字节码的行号指示器.
  • 字节码指示器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令.
  • 由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的, 在任何一个时刻, 一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令.
  • 如果线程正在执行的是一个Java方法, 这个计数器记录的是正在执行的虚拟机字节码指令的地址;  如果正在执行的是Native方法,  这个计数器值为空.

程序计数器是唯一一个在Java虚拟机规范中没有规定任何OOM情况的区域.

Java虚拟机栈

Java虚拟机栈也是线程私有的, 它的生命周期与线程相同.

虚拟机栈描述的是Java方法执行的内存模型:  每个方法在执行的同时都会创建一个栈帧用于存储:

  • 局部变量表
  • 操作数栈
  • 动态链接
  • 方法出口等信息.

每一个方法从调用直至执行完成的过程, 就对应着一个栈帧在虚拟机栈中入栈和出栈的过程。

局部变量表存放着编译期可知的各种基本数据类型(boolean,  byte, char,  short,  int,  float,  long,  double),  对象引用等.  其中64位长度的long和double类型的数据会占用2个局部变量空间,  其余的数据类型只占用1个.

局部变量表所需的内存空间在编译期间完成分配, 当进入一个方法时, 这个方法需要在帧中分配多大的局部变量空间是完全确定的, 在方法运行期间不会改变局部变量表的大小.

Java虚拟机规范会虚拟机栈区域规定了两种异常状况:

如果线程请求的栈深度大于虚拟机所允许的栈深度, 将抛出StackOverflowError异常;  如果虚拟机栈可以动态扩展,  扩展时无法申请到足够的内存, 就会抛出OOM异常.

本地方法栈(Native  Method  Stack)

本地方法栈与虚拟机栈的区别在于: 虚拟机栈为虚拟机执行Java方法服务,  而本地方法栈则为虚拟机使用到的Native方法服务.

HotSpot虚拟机直接将本地方法栈和虚拟机栈合二为一. 

本地方法栈区域也会抛出StackOverflowError和OOM异常.

线程间共享

Java堆

Java堆是被所有线程共享的一块内存区域, 在虚拟机启动时创建. 此内存区域的唯一目的就是存放对象实例, 几乎所有的对象实例都在这里分配内存.Java堆是垃圾收集器管理的主要区域,  也被成为"GC堆".  从内存回收的角度来看, Java堆还可以细分为: 新生代和老年代.

根据Java虚拟机规范, Java堆可以处于物理不连续的内存空间中, 只要逻辑上是连续的即可。 如果在堆中没有内存完成实例分配的,并且堆也无法再扩展时, 将会抛出OOM异常。

方法区(Method  Area)/永久带

方法区也是各个线程共享的内存区域, 用于存储已被虚拟机加载的类信息, 常量, 静态变量, 编译器编译后的代码等数据.(也就是存储类的class文件)  虽然Java虚拟机规范将方法区描述为堆的一个逻辑部分,  但是它却被称为 Non-Heap(非堆).  用来与Java堆区分开.

当方法区无法满足内存分配需求时, 将抛出OOM异常. 

运行时常量池(Running  Constant  Pool)

运行时常量池是方法区的一部分。Class文件除了有类的版本,  字段,方法等描述信息外,  还有一项信息是常量池(Constant  Pool  Table),用来存放编译期生成的各种字面量符号引用, 这部分内容将在类加载后进入方法区的运行时常量池中存放。

字面量则是指:  文本字符串,  声明为final的常量值,  int  i=3,  这个3就是字面量.

运行时常量池相对于Class文件常量池的另外一个特征是具备动态性.并不要求常量一定只有在编译时期才能产生,  也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池. 运行期间也可能将新的常量放入池中.  String的intern()方法便是利用这一特性.

当常量池无法再申请到内存时会抛出OOM异常.

JDK1.8废弃永久带替换为元

  • 新生代:Eden+From Survivor+To Survivor
  • 老年代:OldGen
  • 永久代(方法区的实现) : PermGen----->替换为Metaspace(本地内存中)

把永久代从Java堆中移除了,并把类的元数据直接保存在本地内存区域(堆外内存),称之为元空间。并把类的元数据直接保存在本地内存区域(堆外内存)。元空间的本质和永久带类似, 都是对JVM规范中方法区的实现. 元空间与永久代最大的区别在于: 元空间并不在虚拟机中, 而是使用本地内存. 理论上取决于系统可虚拟的内存大小.

废弃永久带的原因:

  • 永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen。
  • 对永久代的调优过程非常困难,永久代的大小很难确定,其中涉及到太多因素,如类的总数、常量池大小和方法数量等,而且永久代的数据可能会随着每一次Full GC而发生移动。

这样做有什么好处:

类的元数据保存在本地内存中元空间的最大可分配空间就是系统可用内存空间可以避免永久代的内存溢出问题,不过需要监控内存的消耗情况,一旦发生内存泄漏,会占用大量的本地内存。

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值