学习笔记系列(4)——JVM运行时内存区域

251 阅读5分钟

前言

2021.04.30,很开心入职新公司已经一个半月了。我准备整理几篇关于JVM以及GC方面的知识。
现在难免文笔稚嫩粗糙,而且笔记也都是以自己能够理解的语言文字方式表达出来用于自己总结梳理。如果有幸被大佬萌看到,非常希望各位大佬可以指出我的不足缺漏,并且可以指导我该往什么方向更深入的学习思考。
掘金——帮助开发者成长的社区,也希望自己能够从小菜鸡一点点进步成长。

程序计数器

当前线程执行的字节码的行号指示器。能通过改变计数器的值去得到下一条需要执行的字节码指令。各条线程之间互不影响,独立存储。该内存区域为线程私有。
线程执行的是非native方法,程序计数器中存储的是当前指令的地址,如果执行的是native方法,程序计数器中的值就是undefined。程序计数器中存储的数据所占用的空间大小不会随着执行的程序而改变,不会有outOfMemoryError。

本地方法栈

为虚拟机使用到的native方法服务。会抛出StackOverFlowError和OutOfMemoryError。

java虚拟机栈

java栈是运行java方法的内存模型,里面存放的是栈帧,每调用一个方法就会创建一个栈帧,栈帧:局部变量表、操作数栈、指向当前运行方法的所属类的运行时常量池的引用、方法返回地址和一些额外的附加信息。这也是线程所私有的。
会抛出StackOverFlowError和OutOfMemoryError。

  • 局部变量表,用于存储局部变量。
  • 操作数栈,程序的计算过程都是由它完成。
  • 指向运行时常量池的引用,因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。用于存放编译期间生成的各种字面量和符号引用。具备动态性
  • 方法返回地址,当一个方法执行完后,就要返回之前调用他的地方。
    栈帧是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接、 方法返回值和异常分派。

当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。在这个区域规定了两种异常状况:

如果线程请求的栈深入大于虚拟机所允许的深度,将抛出StackOverFlowError异常!
如果虚拟机栈可以动态扩展,当扩展到无法申请内存到足够的内存,就会抛出OutOfMemoryError异常!

java堆

jvm内存管理最大的一片区域,堆的目的是存放对象的实例。
也是垃圾收集器的主要管理区域。线程共享的区域。
会抛出OutOfMemoryError。当没有内存分配实例,堆也无法再扩展的时候。
垃圾回收,堆主要分为:
年轻代:Eden、To Survivor、From Survivor 8:1:1
老年代
永久代(jdk8已经移除)

方法区(永久代)

堆的逻辑部分。
存放内容:

  1. 类信息:类名/继承关系/类本身的属性
  2. 方法信息:随类的加载而加载,方法名/方法属性/相关异常信息等
  3. 常量池
  4. 类变量:即非final类变量
  5. java.lang.class的实例引用:每个加载的类都出啊构建一个java.lang.class的实例,这个实例的引用存放在方法区内
  6. 方法表:方便快速激活方法
  7. 对classLoader的引用
    可以看到方法区中绝大部分的数据都是随类的加载而加载的,基本是类的元数据,往往加载完成后不再有改动。
    方法区中存在gc吗?---答案是肯定的,但可以看到除了类变量可能存在变动需要gc外,其他的类数据在该类无实例的情况下也会gc
    方法区的大小是固定的吗? ---否,JDK8之前的方法区空间是占用JVM内存空间的,方法区默认初始大小为20.75M,用户可以手动修改方法区大小:
-XX:PermSize #设置永久代初始分配空间
-XX:MaxPermSize #设定永久代最大可分配空间

无法满足内存分配时,会抛出OutOfMemoryError异常。

在Java8中,方法区存在于元空间(Metaspace)。元空间不再与堆连续,而且是存在于本地内存中。

默认情况下元空间是可以无限使用本地内存的,但JVM同样提供了一些参数来限制元空间的使用:
-XX:MetaspaceSize #元空间初始内存,单位bytes
-XX:MaxMetaspaceSize #元空间最大内存,默认无限制
-XX:MinMetaspaceFreeRatio #GC后元空间的最小剩余百分比
-XX:MaxMetaspaceFreeRatio #GC后元空间的最大剩余百分比

运行时常量池

是方法区的一部分
用于存放编译期间生成的各种字面量和符号引用。具备动态性

直接内存

被频繁使用,也会导致OutOfMemoryError
JDK1.4引入了一种NIO机制。
基于 Channel 与 Buffer 的 IO 方式,以使用 Native 函数库直接分配堆外内存, 然后使用DirectByteBuffer 对象作为这块内存的引用进行操作

  • 为什么废除永久代
  1. 为了融合HOTPOTJVM和JRokit VM
  2. 字符串存在永久代中,容易出现性能问题和内存溢出。
  3. 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
  4. 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。