『深入学习Java』(一) JVM 内存区域

前言

JVM 虚拟机,是每个 Java 程序员都绕不过去的坎儿。

本篇一起来学习下,JVM 内存区域的划分以及各部分的作用。

本篇中,大部分资料来源于 周志明 《深入理解 Java 虚拟机 第三版》,以及网络资料。

JVM 内存区域

JVM 内存区域划分大致如下:

image-20220227203314338

从线程的角度看,可分为两大区域,线程共享区域和线程私有区域。

  • 线程共享区域

    JVM内所有线程,都可访问到的区域。

    • 方法区
  • 线程私有区域

    只有本线程,才可访问到的区域。

    • 虚拟机栈
    • 本地方法栈
    • 程序计数器

程序计数器

用于记录代码当前的执行位置。

当前执行 Java 方法时,记录当前正在执行的虚拟机字节码指令地址。

当前执行 Native方法时,不记录值。

由于JVM多线程是通过线程轮流切换、分配CPU执行时间的方式实现的。

所以每个线程都需要一个程序计数器,用于记录代码的执行位置,以待下次切换回来时,能够恢复到正确的执行位置。

虚拟机栈

虚拟机栈的大致结构如下:

image-20220227214935364

方法被调用时,JVM 便创建一个栈帧(Stack Frame),用于保存局部变量表,操作数栈,动态链接,方法出口等信息。

方法调用时,入栈;方法完毕时,出栈。

本地方法栈

与虚拟机栈功能一致。保存 Native 方法。

HotSpot 虚拟机中,虚拟机栈与本地方法栈合二为一。

堆的唯一目的就是存放对象实例。

Java 中“几乎”所有对象,都存放在堆中。之所以用“几乎”,是因为随着栈上分配、标量替换等优化手段的出现。

逃逸分析利用标量替换实现堆上的对象分解为栈上的标量。

堆是垃圾回收的重点区域。

堆的分区

大致如下:

image-20220301220937326

  • 新生代

    新生代占据堆的三分之一。但是因为新生代中分区较多,所以图例有些失衡,需要注意。

    • Eden 区
    • Survivor 区
  • 老年代

对象的分配流程

这里使用一张图,来简单描述一下 Java 对象的配置过程。

image-20220306104039943

图中,我们可以看到分配对象一共有四个大阶段:

  1. 逃逸分析。

    满足逃逸条件的,可直接在栈上分配。

  2. 是否为大对象。

    大对象直接进入老年代。 如果老年代内存不足,进行一次 Full GC,一次 Full GC之后,还内存不足,直接 OOM

  3. 是否满足 TLAB 分配。

    例如一些对象不涉及多线程竞争的,就可优先在堆的线程私有区域进行分配。

  4. 年轻代 - Eden 区划分。

    上述条件都不满足时,最终会将对象分配至Eden区。 Eden区内存不足时,发起一次Young GC,之后内存还不足时,抛出OOM

这里我们简单的了解一下,对象分配的过程。其中还有许多知识点,不是一次就能搞清楚的,例如 逃逸分析后的优化手段、分配担保机制等等。

我们后续再继续学习这些比较深入的知识点。

常用命令

  • -Xms: 初始堆空间内存 (默认为物理内存的1/64)
  • -Xmx: 最大堆空间内存(默认为物理内存的1/4)
  • -XX:NewRatio: 配置新生代与老年代在堆结构的占比(默认2)
  • -XX:SurvivorRatio :设置新生代中Eden区与Survivor区的比例。(默认值8)
  • -XX:UseTLAB:设置是否开启TLAB空间。(默认打开)
  • -XX:TLABWasteTargetPercent:设置TLAB空间所占用Eden空间的百分比大小。
  • -XX:MaxTenuringThreshold=N次 年龄晋升老年代阈值(包含)

方法区

用于存储已被 JVM 加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。这是堆的一个逻辑部分。

《Java虛拟机规范》中明确说明:“尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。”但对于 HotSpot JVM 而言,方法区还有一个别名叫做Non-Heap (非堆),目的就是要和堆分开。所以,方法区看作是一块独立于Java堆的内存空间。

也就是说,在不同的 JVM 虚拟机中,方法区的实现方式可能是不一样的。

实际上,在 Hot Spot JVM 中,不同版本之间方法区的实现方式也是不一样的。

在 JDK 8 以前,Hot Spot 用永久代来实现方法区;而在 JDK 8 及之后,已经改为在本地内存中的实现的元空间 ( Meta-space) 来代替。

版本区别

版本 变化
JDK 1.6及之前 有永久代(permanent generation)。永久代行使方法区的功能。
JDK 1.7 有永久代,但已经逐步“去永久代”。字符串常量池、静态变量移至堆中。
JDK 1.8及之后 无永久代,类型信息等移至元空间中,字符串常量池、静态变量仍在堆。

命令

  • -XX:MetaspaceSize

    设置元空间初始化空间

  • -XX:MaxMetaspaceSize

    设置元空间内存最大空间

猜你喜欢

转载自juejin.im/post/7075519928534564878