• JVM 内存管理 你知道多少


    JVM 内存管理 你知道多少


    1、概述

    ​  “Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有的区域则是依赖用户线程的启动和结束而建立和销毁。”

    Java 虚拟机运行时的数据区:
    在这里插入图片描述


    补充:

    ​  线程隔离的数据区:

    • 虚拟机栈(VM Stack)
    • 本地方法栈(Native Method Stack)
    • 程序计数器(Program Counter Register)

    ​  线程共享的数据区:

    • 方法区(Method Area)
    • 堆(Heap)



    2、Java 内存区域

    • 程序计数器(Program Counter Register)

      ​  程序计数器 是 用于存放下一条(Java 虚拟机)字节码指令所在单元的地址 的地方,该区域所在的内存 属于 “线程私有”内存。

      ​  如果 线程 正在执行的是一个 Java 方法,这个计数器的值为正在执行的 (Java 虚拟机)字节码指令的地址;如果 线程 正在执行的是本地(Native)方法,则这个计数器的值为空。

    • 虚拟机栈(VM Stack)

      ​  虚拟机栈 是 用于存放 栈帧(Stack Frame) 的地方,每当一个(Java)方法被执行时,Java 虚拟机 就会创建一个 栈帧,栈帧 存储着 局部变量表、操作数栈、动态连接、方法出口等信息,一个(Java)方法 从 调用 到 执行完毕 的过程 对应着 栈帧 入栈 到 出栈 的过程,该区域所在的内存 属于 “线程私有” 内存。

    • 本地方法栈(Native Method Stack)

      ​  本地方法栈 的功能与 虚拟机栈 相似,唯一不同的是 虚拟机栈 服务于 虚拟机执行 Java 方法,本地方法栈 服务于 虚拟机执行本地方法,该区域所在的内存 属于 “线程私有” 内存。

    • 堆(Heap)

      ​  堆 是 用于存放(Java)对象实例 的地方,该区域所在的内存 属于 “线程共享” 内存。

      ​  堆 是 垃圾收集器 管理的内存区域,因此也被称为 “GC堆”。

      ​  堆的大小 可以通过 (Java 虚拟机)参数 -Xmx 和 -Xms 进行设定。

    • 方法区(Method Area)

      ​  方法区 是用于存放 被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据 的地方,该区域所在内存 属于 “线程共享” 内存。


    扩展:

    • 运行时常量池(Runtime Constant Pool)

      ​  运行时常量池 是 方法区 的一部分,用于存放 编译期生成的各种 字面量 与 符号引用。

    • 直接内存(Direct Memory)

      ​  直接内存 并不属于 Java 内存区域,但这部分内存也被频繁地使用到,例如:通过 Native 函数库直接分配 堆外内存,减少数据传输损耗,提高性能。

      ​  直接内存 只受本机内存限制。




    3、Java 对象

    3.1、对象的创建

    ​  在 Java 语言中,创建一个对象仅仅需要一个关键字 new ,但在 Java 虚拟机 中,创建一个对象则需要经过以下几个步骤。

    ​  1、当 Java 虚拟机 遇到一条字节码 new 指令时,首先会去检查 这个指令的参数 是否能在 常量池 中定位到 一个类的符号引用,并且检查这个 符号引用代表的类 是否 已被加载、解析和初始化过,如果没有就先执行 该类的类加载过程。

    ​  2、在类加载检查通过后,虚拟机 通过 “指针碰撞”(或 “空闲列表”)分配方式 在 堆 中为 该对象分配内存空间。

    ​  3、内存空间分配完成之后,虚拟机 必须将 分配到的内存空间(但不包括对象头)都初始化为 零值。

    ​  4、接下来,虚拟机 还将对 对象 进行必要的设置,如:该对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码(延后至 Object:hashCode() 的调用 才进行计算)、对象的 GC 分代年龄等,这些信息均存放于 对象的对象头中。

    ​  5、至此,从虚拟机的角度来看,一个新的对象已经诞生,但从 Java 程序的角度来看,对象的创建才刚刚开始(构造函数还没有执行),接下来即是 执行构造函数(在虚拟机中体现为 () 方法)完成对 对象的初始化,正式完成对 对象的构造。


    3.2、对象的内存布局

    ​  对象 在 堆 中的内存布局可以划分为三个部分:

    • 对象头(Header)
      • 对象自身的运行数据:哈希码、GC 分代年龄、锁状态标志等
      • 类型指针:指向 对象的类型元数据(即 是哪个类的实例)
    • 实例数据(Instance Data)
      • 字段内容
    • 对其补充(Padding)
      • 占位符

    3.3、对象的访问定位

    ​  Java 程序在使用对象时,会通过 栈 上的 reference 数据来操作 堆 中的具体对象,而 reference(引用) 访问定位 堆 中的对象可分两种方式:

    • 句柄访问

      ​  堆 中划分出一块内存作为句柄池(句柄包含 对象实例数据地址 与 类型数据地址),reference 中存储的就是 对象的句柄地址。

      ​  优势:reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针。

    在这里插入图片描述

    • 直接指针访问

      ​  reference 中直接存储 对象地址。
      在这里插入图片描述

       优势:相较于句柄方式 节省了一次指针定位的时间开销,提高了访问速度。


    3.4、对象的引用

    • 强引用(Strongly Reference)

      ​  指 在程序代码中的引用赋值( Object obj = new Object() ),只要 强引用关系 还存在,垃圾收集器 就永远不会回收掉 被引用的对象。

    • 软引用(Soft Reference)

      ​  指 一些还有用、但非必须的对象,在系统将要发生 内存溢出异常 前,垃圾收集器 会将这些对象进行回收。

      ​  JDK1.2 后,提供 SoftReference 类 实现 软引用。

    • 弱引用(Weak Reference)

      ​  指 一些非必须的对象,当 垃圾收集器 开始工作时,无论当前内存是否充足,都会将这些对象进行回收。

      ​  JDK1.2 后,提供 WeakReference 类 实现 弱引用。

    • 虚引用(Phantom Reference,又称 “幽灵引用” 或 “幻影引用”)

      ​  一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,它存在的唯一目的只是:为了能在这个对象被 垃圾收集器 回收时收到一个系统通知。

      ​  JDK1.2 后,提供 PhantomReference 类 实现 虚引用。


    3.5、对象的存亡

    ​  当一个对象 “死亡” 时,GC(Garbage Collection,垃圾收集)会将该对象清除 并 回收分配给该对象的内存空间。那么,GC 怎么判断一个对象是否 “死亡” 了呢?

    ​  GC 判断一个对象是否 “死亡” 通常有以下两种算法:

    • 引用计数算法

      ​  在 对象 中添加一个 引用计数器,每当有一个地方引用它时,计数器值加一;每当有一个地方的引用失效时,计数器值减一;任何时刻计数器值为 零 的对象就是 “已死亡” 的对象(即 该对象不可能再被使用)。

      • 优点:原理简单,判断效率高
      • 缺点:占用额外的内存空间、无法解决对象间的循环引用问题
    • 可达性分析算法

      ​  以 “GC Roots”(根对象)作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径被称为 “引用链(Reference Chain)”,如果 某个对象 到 “GC Roots” 间没有任何 引用链 相连(或者 用图论的话来说就是 从 “GC Roots” 到 这个对象不可达)时,该对象 “已死亡”(即 该对象不可能再被使用)。

      • 优点:解决了对象间的循环引用问题
      • 缺点:相较于 引用计数算法 判断效率 没那么高

      扩展:Java 中的 “GC Roots”

      • 虚拟机栈(栈帧中的本地变量表)中 引用的对象,如:当前正在运行的方法 所使用到的 参数、局部变量、临时变量等
      • 方法区中 类静态属性 引用的对象,如:Java类 的引用类型静态变量
      • 方法区中 常量 引用的对象,如:字符串常量池里的引用
      • 本地方法栈中 JNI(Native 方法)引用的对象
      • Java 虚拟机 内部的引用,如:基本数据类型对象的 Class 对象、一些常驻的异常对象(NullPointException、OutOfMemoryError)、系统类加载器
      • 所有被同步锁(synchronized 关键字)持有的对象
      • 反映 Java 虚拟机 内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等
      • 不同垃圾收集器 和 不同回收的内存区域 中的 “临时性” 对象

    在这里插入图片描述


    补充:

    • 对象的 “缓刑”:

    ​  当一个对象在 可达性分析算法 中判定为 不可达对象时,该对象会进入 “缓刑” 阶段:检测该对象是否有必要执行 finalize() 方法。

    ​  如果对象没有覆盖 finalize() 方法(或 finalize() 方法已经被虚拟机调用过),则 虚拟机 认为该对象 没必要执行 finalize() 方法,真正宣布其 “死亡”;

    ​  否则,虚拟机 认为该对象 有必要执行 finalize() 方法,并将该对象放入 F-Queue 队列中,然后由虚拟机 自动建立的、低调度优先级 的 Finalizer 线程 去执行它的 finalize() 方法(这里的 “执行” 是指虚拟机会触发这个方法开始运行,但并不承诺一定会等待它运行结束)。

    • 对象的 “重生”:

    ​  当对象在 finalize() 方法中 重新与引用链上的任何一个对象建立关联,如:把自己(this 关键字)赋值给某个类变量或者对象的成员变量,即 该对象获得 “重生”(移出 “即将回收” 的集合)。

    ​  相反,如果该对象在 finalize() 方法中 没能重新与引用链上的任何一个对象建立关联,该对象也将面临 “死亡”。




    4、垃圾收集算法

    4.1、分代收集理论

    ​  分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说和一条经验法则之上。

    • 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的

    • 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡

    • 跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数

    ​  基于 分代收集理论,垃圾收集器 一致的设计原则为:收集器应该将 Java 堆划分出不同的区域,然后将回收对象依据其年龄(即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。


    补充:

    Java 堆的分代

    • 新生代(Young Generation)
    • 老年代(Old Generation)

    分代收集

    • 部分收集(Partial GC):
      • 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集
      • 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集
      • 混合收集(Mixed GC):既对 整个新生代进行垃圾收集,也对 部分老年代进行垃圾收集
    • 整堆收集(Full GC):对整个 Java 堆 和 方法区 进行垃圾收集


    4.2、标记 - 清除算法

    ​  算法分为 “标记” 和 “清除” 两个阶段:首先标记出所有需要回收的对象,在标记完成之后,统一回收掉所有被标记的对象(或统一回收掉所有未被标记的对象)。

    ​  缺点:产生碎片空间。

    在这里插入图片描述


    4.3、标记 - 复制算法

    ​  将内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另一块上,然后再把已使用过的内存空间一次性清理掉。

    ​  缺点:可用内存减半。

    在这里插入图片描述


    4.4、标记 - 整理算法

    ​  算法的标记过程 与 “标记 - 清除算法” 一致,整理过程则是:将所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。

    ​  缺点:移动 大对象 可能造成较久的停顿。

    在这里插入图片描述



    以上全部内容均来自于对 周志明老师的 《深入理解 Java 虚拟机》的部分内容的总结。
    我的总结于这本书的内容相比,无亦于水滴同大川,真心推荐大家去细读这本书。
    以上内容仅用于个人学习。

  • 相关阅读:
    STM32 FREERTOS osDelayUntil()异常
    【Flutter 面试题】怎么理解Flutter的Isolate?并发编程
    【C#】Redis在net core下使用教程
    2022牛客暑期多校训练营1 个人题解
    Anaconda使用教程(新手友好)
    计算机网络期末复习(谢希仁第八版 & 超详细整理)对零基础非常友好!!!
    重磅!!!监控分布式NVIDIA-GPU状态
    Android修改默认system/bin/下可执行程序拥有者和权限,使用实例,只有root和系统app权限才能执行某个命令。
    两个BI开发,3000多张报表?如何做的到?
    对比 elasticsearch 和 mysql
  • 原文地址:https://blog.csdn.net/weixin_51123079/article/details/126011158