【JVM】谈谈JVM内存区域的划分

admin 发表于:2021-03-08 02:16:51 阅读数:615

对 Java 程序员来说我们不用自己手动管理对象内存的申请与释放,全部交由 Java 虚拟机(JVM)来管理内存的分配与回收。

因此,日常开发中我们不用关心内存分配与回收,减少了很多繁琐的工作,大大提高了开发效率。 也正是因为如此,一旦出内存泄漏和溢出方面的问题,如果不了解 JVM 内部的内存结构、工作机制,那么排查问题将变得异常艰难。

接下来,我们一起学习 JVM 内存区域的划分、作用以及可能产生的问题。

根据 Java 虚拟机规范,Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为几个不同的数据区域,如下所示:

JVM内存区域的划分

其中,有些区域会在虚拟机进程启动的时候创建,由所有线程共享。还有些区域则在用户线程的启动的时候创建,线程结束的时候销毁,这部分区域则是线程私有的。

JVM 内存区域主要分为线程共享区域(Java堆、方法区)、线程私有区域(程序计数器、虚拟机栈、本地方法栈)。

1、程序计数器(Program Counter Register)

程序计数器也叫PC寄存器。是一块较小的内存空间。 在 JVM 规范中,每个线程都有自己的程序计数器,独立存储,互不影响,也就是说程序计数器是线程私有的。 如果当前线程执行的是一个 Java 方法,程序计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是本地(Native)方法,则是空(Undefined)。 此内存区域是唯一一个在 JVM 规范中没有规定任何 OutOfMemoryError 情况的区域。

2、Java 虚拟机栈(VM Stack)

Java 虚拟机栈也是线程私有的,每个线程在创建时都会创建一个虚拟机栈,生命周期与线程相同。 虚拟机栈描述的是 Java 方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)压入虚拟机栈,方法执行完毕栈帧出栈。 栈帧中存储着局部变量表、操作数栈、动态链接、方法出口等信息。

在 JVM 规范中对虚拟机栈规定了两类异常:

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常; 如果 Java 虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出 OutOfMemoryError 异常。 可以通过参数-Xss 设定Java虚拟机栈空间大小。

3、本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈类似,也是每个线程都会创建一个。区别是,虚拟机栈为虚拟机执行 Java 方法服务,本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

JVM 规范中并未对本地方法栈的实现做强制规定,具体虚拟机可以根据需要自由实现它。甚至在 Oracle Hotspot JVM 中将本地方法栈和虚拟机栈合二为一。 与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出 StackOverflowError 和 OutOfMemoryError 异常。

4、堆(Heap)

Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。它是Java内存管理的核心区域,用来存放 Java 对象实例,几乎所有的 Java 对象实例都被直接分配在堆上。 但是随着即时编译技术的进步和逃逸分析技术的逐渐成熟,栈上分配、标量替换优化手段将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。

从 JDK1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。

Java堆是垃圾收集器管理的主要区域,因此也被称为GC堆。从垃圾回收的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的, 所以 Java堆还可以细分为:新生代(Eden区、From Survivor区 和 To Survivor区)和老年代。 无论如何划分,Java 中存储的都是对象的实例,这一点上是不变的,而将 Java堆细分的目的只是为了更好的回收内存,或者更快的分配内存。

如果在Java堆中没有足够的内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将抛出 OutOfMemoryError 异常。 通过参数-Xms 和 -Xmx 设定初始堆大小和最大堆大小。

5、方法区(Method Area)

方法区也是所有线程共享的一块内存区域,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。 由于早期 HotSpot JVM 使用永久代实现方法区,很多人习惯将方法区称为永久代(Permanent Generation)。 方法区是Java虚拟机规范中的定义,是一种规范,而永久代则是一种是实现,一个是标准一个是实现, 其他的虚拟机(比如 BEA JRockit、IBM J9等)实现并没有永久代这一说法。

方法区的发展

由于永久代的大小是有限的,并且 JVM 对永久代垃圾回收(如,常量池回收、类型的卸载)的效果比较难以令人满意, 我们通常使用 -XX:PermSize 和 -XX:MaxPermSize 设置永久代的大小, 32位机器默认的永久代大小为64M,64位的机器则为85M。 一旦类的元数据超过了设定的大小,程序就会耗尽内存,并出现内存溢出错误(OOM)。

在 JDK6 的时候 HotSpot 团队就有放弃永久代逐步改为本地内存(Native Memory)来实现方法区的计划了, 到了 JDK7 已经把原本放在永久代的字符串常量池、静态变量等移到堆上分配, 在 JDK8 中彻底移除了永久代,将 JDK7 中永久代剩余的内容(主要是类的元数据)移到元空间(Metaspace)中。 移除永久代是为融合 HotSpot JVM 与 JRockit VM 而做出的努力,因为 JRockit 没有永久代,不需要配置永久代。

元空间(Metaspace)

元空间与永久代最大的区别在于:元空间并不在 Java虚拟机中,而是使用本地内存(Native Memory)。因此,默认情况下,元空间大小仅受本地内存限制。

类的元数据放入本地内存,字符串常量池和类的静态变量放入 Java堆中,这样加载多少类的元数据就不再由 MaxPermSize 控制,而由系统的实际可用空间来控制。通过参数 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 设定元空间初始值和最大值。

相关文章

  • http://openjdk.java.net/jeps/122
  • https://www.infoq.cn/article/Java-PERMGEN-Removed
  • https://xie.infoq.cn/article/e5e566b566d4aeb1d04a2c0de
  • https://www.jianshu.com/p/3ecece7ece20

更多精彩内容请关注公众号 geekymv,喜欢请分享给更多的朋友哦」

Barbara Middleton
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis porta eros lacus, nec ultricies elit blandit non. Suspendisse pellentesque mauris sit amet dolor blandit rutrum. Nunc in tempus turpis.
Like · Reply · 3 hrs

Sean Brown
Donec sollicitudin urna eget eros malesuada sagittis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam blandit nisl a nulla sagittis, a lobortis leo feugiat.
Like · Reply · 2 hrs

Vivamus quis semper metus, non tincidunt dolor. Vivamus in mi eu lorem cursus ullamcorper sit amet nec massa.
Morbi vitae diam et purus tincidunt porttitor vel vitae augue. Praesent malesuada metus sed pharetra euismod. Cras tellus odio, tincidunt iaculis diam non, porta aliquet tortor.

Kayli Eunice
Sed convallis scelerisque mauris, non pulvinar nunc mattis vel. Maecenas varius felis sit amet magna vestibulum euismod malesuada cursus libero. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus lacinia non nisl id feugiat.
Like · Reply · 2 hrs