JVM内存分布

《深入理解Java虚拟机》第2章笔记


JVM

图为Java虚拟机内存划分情况。

程序计数器

当前线程所执行的字节码的行号指示器,通俗来说就是用于指示线程执行到程序的哪一步。在Java中,由于多个线程是轮流占用处理器,为了线程切换后能够恢复到正确的执行位置,每个线程都有一个独立的程序计数器用于保存各线程程序执行位置。所以该数据区是线程私有的。值得注意的是,该区域是唯一一个不会抛出OOM异常的区域。

Java虚拟机栈

Java虚拟机栈,就是我们平时所说的栈区,也是线程私有的,其生命周期与线程相同。虚拟机栈主要描述的是Java方法执行的内存模型:每个方法执行时都会创建一个栈帧,用于存储方法的局部变量表、操作数栈、动态链接、方法出口等信息,而每个方法从调用到执行完成,就对应着一个栈帧在虚拟机栈中的压入和弹出过程。其实我们平时使用IDE进行调试时,就可以非常直白地看到虚拟机栈栈帧的压入与弹出,下图展示的是IDEA中程序的调试画面:
stack
我在inc2方法打个断点,执行程序,首先创建一个“main”线程,然后依次压入ByteCode()、inc1()、inc2()对应的栈帧。

Java虚拟机规范对这个区域规定了两种异常:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,只有在扩展时无法申请到足够的内存,才会抛出OutOfMemoryError异常。这里有个问题:什么是虚拟机所允许的栈深度呢?其实最大深度是由线程分配到的栈空间决定的,举个栗子:假设某个线程分配到的栈空间大小为100KB,而每个栈帧占用的空间为2KB,理论上栈的深度可达到50,只要超过50,就会抛出StackOverflowError。那什么时候才会抛出OutOfMemoryError异常?刚刚也提到,如果虚拟机栈是允许栈动态扩展的,譬如现在栈的深度已经达到50了,当栈深度继续增加时,虚拟机允许再为该线程分配栈空间,假设扩展到200KB,而如果200KB已经是虚拟机可以给线程分配的最大栈空间,一旦栈深度超过100,就会抛出OutOfMemoryError异常。注意:Java虚拟机(HotSpot虚拟机)并不支持栈空间的动态扩展。

本地方法栈

这块区域与虚拟机栈的作用与结构基本一致,唯一不同的就是本地方法栈是为native方法服务的。同样地,这块区域也会抛出StackOverflowError异常和OutOfMemoryError异常。

Java堆

Java堆唯一的目的就是存放对象实例,譬如new String()就会在堆区中产生String类的一个对象实例,它是线程共享的,所有线程产生的实例对象都存放在Java堆中。而正是由于该区域是用于存放实例对象,也就意味着这块区域是GC(垃圾回收)的“主战场”,故也可以称Java堆为GC堆。从内存回收的角度来看,由于现在的收集器基本上都是采用分代收集算法,所以堆区还可以细分为新生代和老年代,而新生代还可以分为“Eden(伊甸)区”、“From Survivor区”和“To Survivor区”,这样分区都是为了使GC执行更加快速。另外,JVM是允许Java堆动态扩展的,如果堆区没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OOM异常。

方法区

与Java堆一样,是线程共享区域,主要用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Java虚拟机规范在逻辑上将方法区归属于堆区,但是方法区也称为“Non-Heap(非堆)”,目的就是为了将方法区与堆区分开。

在HotSpot虚拟机中,方法区也称为“永久代”,注意“永久代”这个概念只是在HotSpot虚拟机存在,可以理解是HotSpot虚拟机对Java虚拟机规范中方法区的实现,而在其他虚拟机中,是有不同的实现方法。对于“永久代”而言,可以通过相关参数来设置内存上限大小和是否支持动态扩展,但这也会更加容易遇到内存溢出的问题,例如:在某个程序中,设定永久代大小为128MB,且不允许动态扩展,如果该Java程序使用到了类似cglib动态生成大量的代理子类,将会抛出OOM异常。所以在JDK 1.8及之后的版本中,已经将“永久代”移除,取而代之的是元空间,直接使用本机内存来存放方法区中的信息,只要不超过本机内存上限,就不会抛出OOM异常。

对于方法区的GC回收,主要是针对常量池的回收和对类型的回收,但是往往回收的效果都不太理想,原因为对这块区域的回收条件相对苛刻。

运行时常量池

运行时常量池也是方法区的一部分,主要用于存放经由编译器编译生成的Class文件中的符号引用和字面量,程序运行时,将由类加载器将这些信息加载到虚拟机的运行时常量池。除此之外,还会存放符号引用翻译后的直接引用,和Java中的某些方法(例如String类的intern()方法)在运行期间将新的常量加入到运行常量池中。既然归属于方法区,自然受到方法区内存的限制,当常量池无法申请到内存时,会抛出OOM异常。

直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也会被虚拟机调用,而且可以会导致OOM异常抛出。

简单来说,这部分内存主要作为Java堆和Native堆之间的缓冲区,当Java堆需要获取Native堆中的数据时,可由存储在Java堆中的DirectByteBuffer对象作为这块内存的引用而进行操作,避免了在Java堆和Native堆之间来回复制数据导致效率低下。

直接内存之所以会导致Java程序抛出OOM异常,主要原因是开发者没有考虑到这块内存的存在,举个栗子:本机总内存为2G,假设开发者将某个Java程序初始内存占用配置为1.5G,而直接内存占用0.5G,由于开发者没有考虑到直接内存,当该Java程序内存动态扩展时,导致OOM异常抛出。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值