浅谈 —— JVM运行时数据区

一、JVM运行时数据区模型图

 

二、详述

1. 程序计数器(线程私有)

  1. 程序计数器的是为了记录当前执行的字节码指令,工作原理是通过改变这个计数器,来获取下一条需要执行的字节码指令,例如分支、循环、跳转、异常处理等,都需要依赖程序计数器来执行。

  2. 在多线程环境下,程序计数器记录了当前线程所执行到的位置,当从其他线程切换回来时,能够准确的继续执行。

             注意:程序计数器不会出现OutOfMemoryError(OOM),它会随着线程的创建而创建,随着线程的结束而结束。

 

2. 虚拟机栈(线程私有)

  1. Java虚拟机栈描述的是方法执行的内存模型,每个虚拟机栈由一个个栈帧组成,当一个线程调用方法时,都会有一个栈帧被压进虚拟机栈中,随着调用结束,栈帧会从虚拟机栈中弹出。
  2. 虚拟机栈也是线程私有的,随着线程的创建而创建,随着线程的结束而死亡。

2.1 栈帧

2.1.1 操作数栈

每一个方法的执行,都会伴随着操作数栈的压栈和弹栈操作。当一个方法开始执行前,操作数是空的,当方法开始执行时,会把局部变量表或者对应实例字段压入操作数栈中,随着计算的进行,会从栈中把元素弹栈给局部变量表或者方法的调用者。

2.2.2 局部变量表

局部变量表存放的是编译器可知的各种数据类型和对象引用,以及返回地址信息。

局部变量表中的信息是以Slot(变量槽)的形式进行存储的,在32位虚拟机中,一个Slot可以存储32位的数据类型(byte,short,int,boolean,float,char,reference,returnAddress),对于64为数据类型(long,double),会使用两个Slot来进行存储。

2.2.3 动态链接

当调用其他方法时,需要把符号引用转换为直接引用,持有该引用就是为了方法调用的动态链接。

注意:1. 虚拟机栈会出现OutOfMemoryError(OOM),当Java虚拟机堆中的内存不足时,在进行了垃圾回收之后然后不足,就会出现OOM。

           2. 虚拟机栈也会出现StackOverFlowError,Java虚拟机栈无法进行动态扩展,当线程请求的栈深度超过了虚拟机栈所能接收的最大深度时,就会报StackOverFlowError错误。

 

3. 本地方法栈(线程私有)

本地方法栈也是线程私有的,与虚拟机栈类型,当一个本地方法被调用时,就会有栈帧压入本地方法中,该栈帧中的信息也和虚拟机栈类似,有局部变量表、操作数栈、动态链接、方法返回信息、附加信息。

同Java虚拟机栈相同,本地方法栈也会出现OOM和StackOverFlowError。

 

4. 堆(线程共享)

堆内存是线程共享的,此内存区域存放的是对象实例,但在JDK1.7+之后,并不是所有的对象实例都存放在堆空间中,因为在JDK1.7开始,默认开启了逃逸分析,如果方法中的对象仅在该方法中使用,即并未发生逃逸,则该对象会在栈中进行存储。

Java的堆内存模型如下图所示:

堆内存可以分为新生代和老年代,默认新生代:老年代 = 1 : 2,其中,新生代又可分为Eden区、Survivor0区、Survivor1区,默认Eden区:Survivor0区:Survivor1区 = 8 : 1 : 1,Survivor0区和Survivor1区又被称为From区或To区,每次只会使用其中一块内存。

通常情况下,当一个对象被创建时,他会在Eden区中进行存储,当Eden区中不足以存储对象时,会触发Minor GC(Young GC),对新生代(Eden区和From区)进行垃圾回收,并把存活的对象转储到To区中,并将这些存活的对象的年龄+1,此时,原来的From区会变为To区,原来的To区会变为From区,默认情况下,如果一个对象的年龄达到了15,或进入到老年代中。

如果Minor GC之后,Eden区仍然不足以存放该对象时,该对象会直接进入到老年代中。

分配担保机制

在进行Minor GC之前,老年代会先判断其可用空间是否大于新生代所有对象的空间,如果大于,那么此次Minor GC是安全的,如果小于,那么虚拟机会查看是否开启了分配担保机制,如果HandlePromotionFailure=true,即开启了分配担保机制,那么老年代会判断历次进入老年代的对象的平均大小,如果大于,则会进行Minor GC,但此次Minor GC仍然是危险的,如果小于或者HandlePromotionFailure=false,则会触发一次Full GC。

为什么要进行分配担保

因为新生代采用的是复制收集算法,如果在Minor GC之后,仍然有大量的对象存活(极端情况下所有对象都存活),而Survivor区只能存放少量的对象,此时这些对象就需要进入到老年代中,而在进入老年代之前,需要判断这些当前老年代中的空闲内存是否足以容纳这些对象,但是无法预测此次进入到老年代的对象有多大,只能通过历次进入到老年代对象的平均大小进行估算,依次来判断是否需要对老年代进行Full GC。

注意:堆会出现OutOfMemoryError(OOM),常见的有:

OutOfMemoryError:GC Overhead Limit Exceeded:当JVM花费大量时间进行垃圾回收,但是每次只能回收少量内存时,会报此错误。

OutOfMemoryError:Java heap space:在创建对象时,堆内存不足以存放新创建的对象时,会报此错误,此错误和本机物理内存无关,和配置的内存大小有关。

 

5. 元空间(线程共享)

JDK1.8及其之后,元空间彻底取代了方法区,元空间使用的是直接内存,用于存储类的元数据

注意:元空间会出现OutOfMemoryError(OOM),OutOfMemoryError: MetaSpace。

 

6. 运行时常量池(线程共享)

运行时常量池用于存放编译时生成的字面量和符号引用,JDK1.7之前,运行时常量池是逻辑包含字符串常量池的,其位于方法区中,JDK1.7时,字符串常量池被单独拿出来存放于堆空间中,运行时常量池的其他部分仍然位于方法区中,JDK1.8及其之后,元空间代替了方法区,字符串常量池依然位于堆空间中,运行时常量池的其他部分就被转移到了元空间中。

注意:运行时常量池会出现OutOfMemoryError(OOM)。

 

7. 直接内存(线程共享)

直接内存不是运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这一块内存也被频繁的使用。

在JDK1.4时,新引入了NIO类 ,这是一种基于通道和缓冲区的I/O方式,可以使用Native函数库分配堆外内存,然后通过位于Java堆中的DirectByteBuffer对象来作为这块内存的引用来进行操作,可以在某些场景中显著的提高性能,避免了在Java堆和Native堆之间来回的复制。

注意:直接内存会出现OutOfMemoryError(OOM)。

 

参考资料

《JavaGuide 面试突击版》

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天向上Charles

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值