走进JVM的内存模型

1、概述:我们在用Java语言进行编程时,并没有像C/C++程序这样为每一个 new 操作去写对应的 delete/free操作。这得益于Java 程序把内存控制权利交给 JVM虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。

2、JVM内存模型:

JVM虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。

JDK 1.8 和之前的版本略有不同,JDK 1.7:

JDK 1.8之前分为:线程共享(Heap堆区、Method Area方法区)、线程私有(虚拟机栈、本地方法栈、程序计数器)

JDK 1.8:

JDK 1.8以后分为:线程共享(Heap堆区、MetaSpace 元空间)、线程私有(虚拟机栈、本地方法栈、程序计数器)

下面我们将划分的每个区域进行更进一步了解:

3、程序计数器

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为"线程私有"的内存。

作用:

1、字节码解释器通过改变程序计数器读取指令,从而实现代码的流程控制

2、多线程在线程上下文切换时,用于记录当前线程执行的位置

4、Java虚拟机栈

与程序计数器一样,Java 虚拟机栈(后文简称栈)也是线程私有的,它的生命周期和线程相同,随着线程的创建而创建,随着线程的死亡而死亡。

方法调用的数据需要通过栈进行传递,每一次方法调用都会有一个对应的栈帧被压入栈中,每一个方法调用结束后,都会有一个栈帧被弹出 。在活动线程中, 只有位于栈顶的栈帧 才是有效的, 称为当前活动栈帧 ,代表正在执行的当前方法。在JVM执行引擎运行时, 所有指令都只能针对当前活动栈帧进行操作。

栈由一个个栈帧组成 ,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法返回地址。和数据结构上的栈类似,两者都是先进后出的数据结构,只支持出栈和入栈两种操作。

局部变量表:存放了编译期可知的各种基本数据类型、对象引用。

操作数栈 主要作为方法调用的中转站 使用,用于存放方法执行过程中产生的中间计算结果。另外,计算过程中产生的临时变量也会放在操作数栈中。

动态链接 主要服务一个方法需要调用其他方法的场景。动态链接的作用就是为了将符号引用转换为调用方法的直接引用,这个过程也被称为 动态连接 。

Java 方法有两种返回方式,一种是 return 语句正常返回,一种是抛出异常。不管哪种返回方式,都会导致栈帧被弹出。也就是说, 栈帧随着方法调用而创建,随着方法结束而销毁。无论方法正常完成还是异常完成都算作方法结束。

简单总结一下程序运行中栈可能会出现两种错误:

  • StackOverFlowError: 若栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过 当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError错误。
  • OutOfMemorryError: 如果栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间 ,则抛出OutOfMemorryError异常。

5、本地方法栈

与Java虚拟机栈的功能类似,它负责native关键字修饰的本地方法被执行的时候,在本地方法栈中创建一个栈帧,用于存放该native本地方法的局部变量表、操作数栈、动态链接、方法出口信息。方法执行完毕后,相应的栈帧也会出栈并释放内存空间。也会出现StackOverFlowError和 OutOfMemoryError两种错误。

以上是线程私有的区域,下面两个区域则是线程共享区:元空间与堆区

6、堆区

Heap堆是JVM 所管理的内存中最大的一块区域,被所有线程共享的一块内存区域。堆区中存放对象实例,"几乎"所有的对象实例以及数组都在这里分配内存。

由于"逃逸分析"的存在,若某些方法中的对象引用没有被返回或者未被外面使用,就会在栈分配。

堆区是JVM进行垃圾回收最主要的一片区域,也常称GC堆,为了更好的管理与垃圾回收,进一步划分为新生代(Eden,S0、S1)与老年代。

新生代:老年代=1:2;Eden:Eden:S0+S1=8:2

对象的创建过程:

步骤一:类加载检查

步骤二:分配内存

步骤三:初始化零值

步骤四:设置对象头

步骤五:执行init构造方法

上面说大部分对象的创建都会在堆区进行分配空间,那么对象究竟是如何分配的?

大部分情况下,对象会在 Eden 区生成,当 Eden 区装填满的时候,会触发 Young Garbage Collection,即 YGC垃圾回收的时候,在 Eden 区实现清除策略,没有被引用的对象则直接回收。依然存活的对象会被移送到 Survivor 区。Survivor 区分为 s0 和 s1 两块内存区域。每次 YGC的时候,它们将存活的对象复制到未使用的Survivor 空间(s0 或 s1),然后将当前正在使用的空间完全清除,交换两块空间的使用状态。每次交换时,对象的年龄会加+1。

如果 YGC 要移送的对象大于 Survivor 区容量的上限,则直接移交给老年代。一个对象也不可能永远呆在新生代,在 JVM 中 一个对象从新生代晋升到老年代的阈值默认值是 15,可以在 Survivor区交换 14 次之后,晋升至老年代。

7、元空间

在jdk1.8后使用元空间替换了永久代,看到这有点迷糊了,方法区、永久代、元空间三者到底什么关系?

方法区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是方法区,也就是说永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式。并且,永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现变成了元空间。

相关推荐
懂点Tech11 小时前
ctfshow misc28:GIF 高度修改解题与脚本实现
开发语言·windows·python
云和数据.ChenGuang12 小时前
Python 重写(Override)深度解析:从 MRO 到 super() 的完整机制
java·开发语言·人工智能·python·langchain·1024程序员节
摇滚侠12 小时前
《On Java 中文版 基础卷》阅读笔记 实现隐藏 12
java·数据库·笔记
美狐美颜SDK开放平台12 小时前
视频美颜SDK试用DEMO阶段应该测试什么?直播APP接入实操指南
开发语言·人工智能·深度学习·音视频·美颜sdk·直播美颜sdk·第三方美颜sdk
dtq042413 小时前
C++小案例 - 日志实战
开发语言·c++
星夜离尘5613 小时前
MATLAB ADC 码流触顶诊断:命中数、缺口分段与削顶不可辨识性
开发语言·matlab
码匠许师傅13 小时前
【C++三方组件】Qt 概览:桌面 C++ 的事实标准与许可证三轨
开发语言·c++·qt
码匠许师傅14 小时前
【C++三方组件】POCO:跨平台系统编程全家桶
开发语言·c++
silvamux_14 小时前
10款模型写种草笔记:可发布性、返工来源与成本数据缺口复盘
java·数据库·笔记
bkspiderx15 小时前
基于Socket的广播通信及C语言实现详解
c语言·开发语言·socket·广播·socket的广播通信