美文网首页
JVM内存区域介绍

JVM内存区域介绍

作者: JAVA编程手记 | 来源:发表于2019-04-10 18:46 被阅读0次

内存区域脑图

JVM内存区域.png image

JVM内存区域主要包括:

  • 方法区(永久代)
  • 虚拟机栈
  • java对象堆
  • pc计数器
  • 本地方法栈

Java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途,创建和销毁时间。

从区域共享主要分为:

  • 线程私有区域:程序计数器,虚拟机栈,本地方法栈
  • 线程共享区域:java堆,方法区
  • 直接内存

线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束而创建/销毁(在 HotspotVM 内, 每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的生/死对应)。

线程共享区域随虚拟机的启动/关闭而创建/销毁。
直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提供了基于 Channel 与 Buffer 的 IO 方式, 它可以使用 Native 函数库直接分配堆外内存, 然后使用DirectByteBuffer 对象作为这块内存的引用进行操作, 这样就避免了在 Java堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。

PC计数器

PC计数器本质上是一块很小的内存区域,标识着当前线程执行的字节码的行号。在java虚拟机的概念模型里,字节码解释器就是通过改变PC计数器的值来选择下一条需要执行的字节码指令,包括分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。

由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条程序中的指令,因此,为了线程之间计数器相互不影响,独立存储,我们称这类内存区域为线程私有的内存。

如果线程执行的是一个java方法,那么计数器记录的是正在执行的虚拟机字节码指令的地址,如果线程执行的方法是Native方法,则为空(Undefined)。这类内存区域也成为线程私有的内存,也是JVM中唯一一块没有规定任何OOM情况的区域。

Java虚拟机栈

虚拟机栈也是线程私有的,他的生命周期和线程相同。虚拟机栈描述的是java方法执行的内存区域:在一个线程中,每个方法的执行都会创建一个栈帧(Stack Frame),用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法的调用和执行完毕,对应着一个栈帧在虚拟机中入栈和出栈的过程。

栈帧(Stack Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法的结束而销毁,无论方法是正常执行结束还是抛出异常结束,都认为是方法结束。

StackFrame.png

本地方法区

本地方法区和虚拟机栈作用类似,不同的是虚拟机栈执行的是java方法,而本地方法则是执行Native方法,HotSpot VM将本地方法栈和虚拟机栈合二为一。

java堆

JAVA堆是jvm虚拟机管理的内存中最大的一块,同时java堆也是线程共享的,几乎所有新生的对象都是在java堆中分配内存的。正是因为几乎所有的对象都是在java堆中存放,所以java堆也是JVM GC的重要区域。

根据JVM的规范,JVM堆在物理机上可以处于不连续的内存空间,只要逻辑上是连续的即可。JVM堆的内存大小可以通过-Xmx(最大内存)和-Xms(初始化内存)两个参数控制,一般设置为相等最佳,防止发生内存抖动。

从GC的角度上java堆可细分为:新生代(Eden,From Survivor,To Survivor)和老年代。

新生代

新生代顾名思义就是用来存放新生对象的内存空间,一般占堆的1/3。由于新生代频繁的创建对象,所以会频繁的触发MinorGC进行垃圾回收。新生代又分为Eden区,SurvivorFrom,SurvivorTo三个区域。

Eden区

新生的java对象优先在Eden区分配内存空间,如果创建的对象占用内存很大,则直接分配到老年代。当Eden区内存不足的时候就会触发MinorGC,对新生代区进行一次垃圾回收。

ServivorFrom ServivorTo

ServivorFrom ServivorTo又被称为from和to区,也有叫做S1和S2区。两个Servivor区相对的成为From和To区域,当一个Servivor为From区时,另一个Servivor为To区,反之也是这样,当年轻代由于内存不足发生MinorGC时,Eden和ServivorFrom区域中存活的对象复制到ServivorTo中,如果一些对象的年龄达到了老年代的标准,则将这些对象放到老年代区域中。

年老代

主要存放应用程序中生命周期长的内存对象。

老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行 了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足 够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。
MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没 有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减 少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的 时候,就会抛出 OOM(Out of Memory)异常。

方法区

即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、静 态变量、即时编译器编译后的代码等数据. HotSpot VM 把 GC 分代收集扩展至方法区, 即使用 Java 堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型 的卸载, 因此收益一般很小)。
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版 本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 Java 虚拟机对 Class 文件的每一部分(自然也包括常量 池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会 被虚拟机认可、装载和执行。

相关文章

网友评论

      本文标题:JVM内存区域介绍

      本文链接:https://www.haomeiwen.com/subject/xviwiqtx.html