JVM 运行时数据区简介

106 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第18天,点击查看活动详情

首先,我们需要明确,JVM虚拟机规范中规定了可以有方法区、堆、JVM栈、程序计数器和本地方法栈这几块区域,他们具体的实现是由虚拟机自己决定的。

运行时数据区主要分为五部分,分别是线程共享的堆、方法区和线程独享的JVM栈、本地方法区和程序计数器

其中JVM栈是用来执行方法的地方,一个方法执行,就会在里面产生一个栈帧,而一个栈帧里面又由操作数栈、局部变量表、动态链接和返回地址组成。通过字节码我们可以发现,执行一个方法,其实就是根据配合局部变量表在操作数栈中进行入栈出栈然后赋值等一系列操作。这个地方会出现两种异常,栈的深度超过最大容许深度的栈溢出,一个是OOM,可能是因为栈帧过多(不恰当的递归)或者是栈帧过大。

程序计数器就是一小块内存区域,他用来存储字节码执行的行号,因为Java是多线程的,线程进行上下文切换切出去之后又重新获得执行权的时候,线程执行到哪里,就需要程序计数器的指示。这是唯一一个没有内存溢出的区域,因为一旦溢出,多线程的体系就彻底崩盘了。

本地方法栈和JVM栈类似,只不过他是用来执行本地方法的,这个区域也会出现栈溢出和OOM,但是它无法进行管理和调优。在HotSpot虚拟机中把本地方法栈和JVM栈合二为一了。

一个类被创建完之后分成两部分,一部分是类的数据,一部分就是对象,我们很顺利成章地能想到,在堆中切一块出来专门用来放类的数据,这个地方就是方法区,这种实现方式就是通过永久代来实现的,但是我们后面发现这种实现方式会导致频繁的内存溢出JDK7的时候将字符串常量池和静态变量放到堆中,JDK8彻底放弃方法区,改用元空间来实现,元空间存储在本地内存中。

堆内存分为两部分,一部分是老年代,一部分是新生代,他们的比例是1:2.内存分代模型和垃圾回收算法是相辅相成的。

我们从最开始的堆内存开始说起,她是用来存放对象的,我们在实际垃圾回收的过程中发现,有些对象存活时间很长,有些对象存活时间很短,我们就在考虑能否根据不同对象存活时间不同将堆内存分为两块,利用分治思想制定不同的垃圾回收算法来提高效率。所以就有了老年代和新生代,针对老年代,我们发明了标记清除法来实现,针对新生代,我们使用标记复制算法来实现,因为复制算法的本意,我们需要给存放新生代的地方划分区域,所以划分出Eden和survivor区域,为了高效地利用空间,我们希望不是1:1,我们希望把其中一块S区变小,但是这又存在一个问题,一旦她变小了,每次用到S区,她享对Eden区域满得快,导致YGC次数增加了,需要改进,最后我们想到了在S区域再切开,对半开,比例是8:1:1,这样子每次内存的占用比例四90%,并且频繁YGC的问题也被解决了