【JVM】运行时内存详解:堆,方法区,本地方法栈,虚拟机栈,程序计数器

99 阅读4分钟

Java运行时数据区及对象的分配_数据区的地址和程序的地址共用-CSDN博客

19.栈的理解_哔哩哔哩_bilibili

堆是所有线程共享的区域,所有创建的对象都在堆中。堆中存在OOM和GC。

根据分代收集理论又可以划分成新生代和老年代新生代又细分为Eden伊甸园区from(survivor0)、to(survivor1) 三个区域。

默认大小比例为,eden:from:to = 8:1:1 。yonung generation:old generation = 1:2

对象分配与GC

1.创建一个新对象时,会在新生区的Eden进行分配;如果新生区的空间不足以分配,会进行一次Minor GC(Young GC) 后检查是否够分配;如果还是不够,就放入老年区;如果老年区还是放不下,就进行一次Full GC; 进行完Full GC后old区还是放不下,抛出OOM。

2.新生代收集(Minor GC\Young GC)的过程:进行Minor GC时,对Eden区可以回收的对象进行回收不能回收的放入survivor的to区;同时对Survivor的两个区From和To,检查From区可以回收的对象,如果对象不能回收就分代年龄加一并也移到to区,如果分代年龄到达阈值了(一般是15),就晋升老年代

3.老年代收集(Major GC/Old GC):对老年代进行收集。目前只有CMS垃圾回收器会对Old区单独回收。

4.整堆收集(Full GC):收集整个Java堆和方法区。

5.每次GC都会导致Stop The World现象,即停止其它线程的运行。

栈/虚拟机栈

栈管运行,堆管存储。

栈中不存在GC,但可能OOM(stackoverflow,比如一直递归调用某个方法)。

每个线程都拥有自己栈空间,在栈空间中,每调用一个方法压入一个栈帧,所以说方法和栈帧是对应的。

栈帧

每调用一个方法就会压一个栈帧,栈帧的内部有局部变量的引用。运行时的操作都是在栈帧中操作的。

栈帧内部有操作数栈局部变量表

  • 操作数栈:方法运行时,各种字节码指令执行的时候会往操作数栈写入和提取内容
  • 局部变量表:局部变量表主要存放该方法的参数方法内声明的变量

下图是栈帧内部图

方法区

方法区其实逻辑上也是堆的一部分,它是有OOM和GC的,很少很少GC。

方法区存什么

  1. 类的元信息。(类名,父类名,访问修饰符)
  2. 运行时常量池
    1. 字面量( 整数、浮点数、字符串等 )
    2. 符号引用(类、接口、方法、字段的符号引用)
  1. 类的方法的信息(通过反射获取)。
  2. 类的字段/域信息(通过反射获取)。

整个类的相关信息,字段、方法、运行时常量池,都在方法区中


方法区是一个抽象的概念,具体实现有永久代和元空间。在hotspot中,1.7及之前叫永久代,1.8及之后叫元空间

对下面的三张图总结

1.6 ====》1.7

将 ****静态变量、字符串常量池 移动到 中。

1.7 ====》1.8

永久代改为元空间, 并将才虚拟机内存 移到 直接内存中。

  • JDK1.6及1.6以前,方法区采用永久代实现,类变量(静态变量)字符串常量池还在方法区中,方法区实际存在于虚拟机内存

  • JDK1.7,方法区采用永久代实现,类变量(静态变量)、字符串常量池被移出方法区放入堆中,方法区实际存在于虚拟机内存

  • JDK1.8,方法区采用元空间实现,类变量(静态变量)、字符串常量池被移出方法区放入堆中,方法区实际存在于直接内存

程序计数器

PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。执行引擎的字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令

程序计数器是线程私有的。操作系统切换线程的时候 通过线程中程序计数器的值 知道接下来该执行该线程中哪条指令。

当执行native方法时,程序计数器的值为undefined

本地方法栈

也是线程私有的

虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一

本地native方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。