JVM垃圾回收详解一(重点)

news/2024/11/14 14:25:33/

堆空间的基本结构

Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 内存中对象的分配与回收。

Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)

从垃圾回收的角度来说,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆被划分为了几个不同的区域,这样我们就可以根据各个区域的特点选择合适的垃圾收集算法。

在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:

  1. 新生代内存(Young Generation)
  2. 老生代(Old Generation)
  3. 永久代(Permanent Generation)

下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间一层属于老年代,最下面一层属于永久代。

堆内存结构

JDK 8 版本之后 PermGen(永久) 已被 Metaspace(元空间) 取代,元空间使用的是直接内存

关于堆空间结构更详细的介绍,可以回过头看看 Java 内存区域详解 这篇文章。

内存分配和回收原则

对象优先在 Eden 区分配

大多数情况下,对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。下面我们来进行实际测试一下。

测试代码:

public class GCTest {public static void main(String[] args) {byte[] allocation1, allocation2;allocation1 = new byte[30900*1024];}
}

内存分配和回收原则

对象优先在 Eden 区分配

大多数情况下,对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。下面我们来进行实际测试一下。

测试代码:

public class GCTest {public static void main(String[] args) {byte[] allocation1, allocation2;allocation1 = new byte[30900*1024];}
}

通过以下方式运行:

添加的参数:-XX:+PrintGCDetails

 

运行结果 (红色字体描述有误,应该是对应于 JDK1.7 的永久代):

 

从上图我们可以看出 Eden 区内存几乎已经被分配完全(即使程序什么也不做,新生代也会使用 2000 多 k 内存)。

假如我们再为 allocation2 分配内存会出现什么情况呢?

allocation2 = new byte[900*1024];

 

allocation2 分配内存的时候 Eden 区内存几乎已经被分配完了

当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。GC 期间虚拟机又发现 allocation1 无法存入 Survivor 空间,所以只好通过 分配担保机制 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放 allocation1,所以不会出现 Full GC。执行 Minor GC 后,后面分配的对象如果能够存在 Eden 区的话,还是会在 Eden 区分配内存。可以执行如下代码验证:

public class GCTest {public static void main(String[] args) {byte[] allocation1, allocation2,allocation3,allocation4,allocation5;allocation1 = new byte[32000*1024];allocation2 = new byte[1000*1024];allocation3 = new byte[1000*1024];allocation4 = new byte[1000*1024];allocation5 = new byte[1000*1024];}
}

大对象直接进入老年代

大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。

大对象直接进入老年代的行为是由虚拟机动态决定的,它与具体使用的垃圾回收器和相关参数有关。大对象直接进入老年代是一种优化策略,旨在避免将大对象放入新生代,从而减少新生代的垃圾回收频率和成本。

  • G1 垃圾回收器会根据 -XX:G1HeapRegionSize 参数设置的堆区域大小和 -XX:G1MixedGCLiveThresholdPercent 参数设置的阈值,来决定哪些对象会直接进入老年代。
  • Parallel Scavenge 垃圾回收器中,默认情况下,并没有一个固定的阈值(XX:ThresholdTolerance是动态调整的)来决定何时直接在老年代分配大对象。而是由虚拟机根据当前的堆内存情况和历史数据动态决定。

长期存活的对象将进入老年代

既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。

大部分情况,对象都会首先在 Eden 区域分配。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间(s0 或者 s1)中,并将对象年龄设为 1(Eden 区->Survivor 区后对象的初始年龄变为 1)。

对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

修正(issue552):“Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的 50% 时(默认值是 50%,可以通过 -XX:TargetSurvivorRatio=percent 来设置,参见 issue1199 ),取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值”。

jdk8 官方文档引用:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html。

动态年龄计算的代码如下:

uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
//survivor_capacity是survivor空间的大小
size_t desired_survivor_size = (size_t)((((double)survivor_capacity)*TargetSurvivorRatio)/100);
size_t total = 0;
uint age = 1;
while (age < table_size) {
//sizes数组是每个年龄段对象大小
total += sizes[age];
if (total > desired_survivor_size) {
break;
}
age++;
}
uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
...
}

额外补充说明(issue672):关于默认的晋升年龄是 15,这个说法的来源大部分都是《深入理解 Java 虚拟机》这本书。
如果你去 Oracle 的官网阅读相关的虚拟机参数,你会发现-XX:MaxTenuringThreshold=threshold这里有个说明

Sets the maximum tenuring threshold for use in adaptive GC sizing. The largest value is 15. The default value is 15 for the parallel (throughput) collector, and 6 for the CMS collector.默认晋升年龄并不都是 15,这个是要区分垃圾收集器的,CMS 就是 6.

主要进行 gc 的区域

周志明先生在《深入理解 Java 虚拟机》第二版中 P92 如是写道:\

上面的说法已经在《深入理解 Java 虚拟机》第三版中被改正过来了。感谢 R 大的回答:

R 大的回答

 

总结:

针对 HotSpot VM 的实现,它里面的 GC 其实准确分类只有两大种:

部分收集 (Partial GC):

  • 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
  • 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
  • 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。

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

空间分配担保

空间分配担保是为了确保在 Minor GC 之前老年代本身还有容纳新生代所有对象的剩余空间。

《深入理解 Java 虚拟机》第三章对于空间分配担保的描述如下:

JDK 6 Update 24 之前,在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC 可以确保是安全的。如果不成立,则虚拟机会先查看 -XX:HandlePromotionFailure 参数的设置值是否允许担保失败(Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于,或者 -XX: HandlePromotionFailure 设置不允许冒险,那这时就要改为进行一次 Full GC。

JDK 6 Update 24 之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行 Minor GC,否则将进行 Full GC。


http://www.ppmy.cn/news/1546688.html

相关文章

设计模式之建造者模式(各项装修物料组合套餐选配场景)

前言&#xff1a; 乱码七糟&#xff0c;我时常怀疑这个成语是来形容程序猿的&#xff01; 无论承接什么样的需求&#xff0c;是不是身边总有那么几个人代码写的烂&#xff0c;但是却时常有测试小姐姐过来聊天(求改bug)、有产品小伙伴送吃的(求写需求)、有业务小妹妹陪着改代码(…

高效运维:构建全面监控与自动化管理体系

在当今数字化时代&#xff0c;企业IT系统的稳定运行直接关系到业务的连续性和竞争力。运维团队作为保障系统稳定运行的中坚力量&#xff0c;面临着前所未有的挑战。随着云计算、大数据、物联网等技术的快速发展&#xff0c;系统架构日益复杂&#xff0c;运维工作也从传统的被动…

Qt滑动条美化自定义

效果展示 主要代码 头文件 下面是hi控件的头文件&#xff0c;我们继承一个Qt原生的滑动条类QSlider&#xff0c;然后在基类的基础上进行自定义&#xff0c;我会对重要的变量进行解析&#xff1a; class XSlider : public QSlider {Q_OBJECT public:explicit XSlider(QWidget…

架构师备考-概念背诵(系统架构)

软件架构概念 一个程序和计算系统软件体系结构是指系统的一个或者多个结构。结构中包括软件的构件,构件的外部可见属性以及它们之间的相互关系。体系结构并非可运行软件。确切地说,它是一种表达,使软件工程师能够: (1)分析设计在满足所规定的需求方面的有效性:(2)在设计变…

Ubuntu 18 EDK2 环境编译

视频&#xff1a;在全新的Ubuntu上从零搭建UEFI的EDK2开发环境 开始&#xff1a;git clone https://github.com/tianocore/edk2.git 开始编译BaseTools前先更新一下子模块&#xff1a;git submodule update --init &#xff0c;然后&#xff1a;make -C BaseTools/ 问题1&a…

1.vue环境搭建

一、环境搭建 1&#xff09;搭建Node.js环境 2&#xff09;创建项目 npm create vuelatest接下来会有一些选项 √ 请输入项目名称&#xff1a; ... hello_vue3 //输入项目名称 √ 是否使用 TypeScript 语法&#xff1f; ... 否 / 是 …

Ubuntu20.04离线安装nginx

文章目录 一、gcc/g、make依赖包安装1.1 在有网的ubuntu机器上下载依赖包1.2 离线安装依赖包 二、nginx相关依赖包安装2.1 有网机器上下载安装包2.2 上传压缩包并解压2.3 安装pcre2.4 安装zlib2.5 安装openssl2.6 安装nginx 三、nginx启动验证 一、gcc/g、make依赖包安装 1.1 …

大模型开发整体流程 基于个人知识库的问答助手 项目流程架构解析

一、大模型开发整体流程 1. 何为大模型开发 定义核心点核心能力 2. 大模型开发的整体流程 1. 设计2. 架构搭建3. Prompt Engineering4. 验证迭代5. 前后端搭建 二、项目流程简析 步骤一&#xff1a;项目规划与需求分析 1.项目目标2.核心功能3.确定技术架构和工具 步骤二&#…