GC基础知识
内存溢出和内存泄漏
- 内存溢出(Out Of Memory)
就是申请内存时,JVM没有足够的内存空间。通俗说法就是去蹲坑发现坑位满了。 - 内存泄露 (Memory Leak)
就是申请了内存,但是没有释放,导致内存空间浪费。通俗说法就是有人占着茅坑不拉屎。
垃圾定义
没有任何引用指向的一个对象或者多个对象(循环引用)。
如何定位垃圾
-
引用计数(ReferenceCount)
在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数加一,如果删除对该对象的引用,那么它的引用计数就减一,当该对象的引用计数为0时,那么该对象就会被回收。 -
根可达算法(RootSearching)
通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。- 在Java语言中,可以作为GCRoots的对象包括下面几种:
- 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(Native方法)引用的对象。
- 在Java语言中,可以作为GCRoots的对象包括下面几种:
常见的垃圾回收算法
- 标记清除(mark sweep)
- 标记阶段:
collector从mutator根对象开始进行遍历,对从mutator根对象可以访问到的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象。 - 清除阶段:
collector对堆内存(heap memory)从头到尾进行线性的遍历,如果发现某个对象没有标记为可达对象-通过读取对象的header信息,则就将其回收。
- 标记阶段:
collector: 垃圾收集器;
mutator: 指除了垃圾收集器之外的部分,比如说我们应用程序本身。
- 拷贝算法 (copying)
将内存平均分成A区、B区两块,进行复制+清除垃圾的操作.
算法过程:- 新生对象被分配到A块中未使用的内存当中。当A块的内存用完了, 把A块的存活对象复制到B块。
- 清理A块所有对象。
- 新生对象被分配到B块中未使用的内存当中。当B块的内存用完了, 把B块的存活对象复制到A块。
- 清理B块所有对象。
- 循环1。
- 标记压缩(mark compact)
分为两个阶段,一个是标记(mark),一个是压缩(compact). 其中标记阶段跟标记-清除算法中的标记阶段是一样的。- 压缩阶段
它的工作就是移动所有的可达对象到堆内存的同一个区域中,使他们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起,通过这样的方式来达到减少内存碎片的目的。 - 移动对象时的顺序,一般分为下面三种:
- 任意顺序 - 即不考虑原先对象的排列顺序,也不考虑对象间的引用关系,随意的移动可达对象,这样可能会有内存访问的局部性问题。
- 线性顺序 - 在重新排列对象时,会考虑对象间的引用关系,比如A对象引用了B对象,那么就会尽可能的将A,B对象排列在一起。
- 滑动顺序 - 顾名思义,就是在重新排列对象时,将对象按照原先堆内存中的排列顺序滑动到堆的一端。
- 压缩阶段
如何使对象在内存中安全分配内存空间
- CAS+失败重试机制
略 - TLAB
新对象都是在Eden区分配空间,这块空间是在多线程间共享的。那么考虑一下,多线程是可能同时创建新对象的,这时候必然需要一种同步机制。使用队列?或者通过互斥?这些方式确实都可以。不过,我们还有一种更好的方式,TLAB,它全称是thread local allocation buffer,这是eden区里的一块空间。每个线程都有它自己的tlab,因此,只要对象分配是发生在tlab里面,这时候就不需要进行同步控制。
其实分配的时候,是让每个线程拥有了私有的分配指针,实际的对象存储的地方,依然是公共可访问的。
参考 https://blog.csdn.net/shuipinglp/article/details/90240377
常见的垃圾回收器
- 新生代收集器
Serial、ParNew、Parallel Scavenge。 - 老年代收集器
Serial Old、CMS、Parallel Old - 堆内存垃圾收集器(即物理内存上不分新生代和老年代)
G1
新生代垃圾收集器
Serial 收集器
Serial 是一款用于新生代的单线程收集器,采用复制算法进行垃圾收集。Serial 进行垃圾收集时,不仅只用一条线程执行垃圾收集工作,它在收集的同时,所有的用户线程必须暂停(Stop The World)。
如下是 Serial 收集器和 Serial Old 收集器结合进行垃圾收集的示意图,当用户线程都执行到安全点时,所有线程暂停执行,Serial 收集器以单线程,采用复制算法进行垃圾收集工作,收集完之后,用户线程继续开始执行。
- 图例
ParNew 收集器
ParNew 就是一个 Serial 的多线程版本,其它与Serial并无区别。ParNew 在单核 CPU 环境并不会比 Serial 收集器达到更好的效果,它默认开启的收集线程数和 CPU 数量一致,可以通过 -XX:ParallelGCThreads 来设置垃圾收集的线程数。
如下是 ParNew 收集器和 Serial Old 收集器结合进行垃圾收集的示意图,当用户线程都执行到安全点时,所有线程暂停执行,ParNew 收集器以多线程,采用复制算法进行垃圾收集工作,收集完之后,用户线程继续开始执行。
- 图例
Parallel Scavenge 收集器
Parallel Scavenge 也是一款用于新生代的多线程收集器,与 ParNew 的不同之处是ParNew 的目标是尽可能缩短垃圾收集时用户线程的停顿时间,Parallel Scavenge 的目标是达到一个可控制的吞吐量。
吞吐量就是 CPU 执行用户线程的的时间与 CPU 执行总时间的比值【吞吐量 = 运行用户代代码时间/(运行用户代码时间+垃圾收集时间)】,比如虚拟机一共运行了 100 分钟,其中垃圾收集花费了 1 分钟,那吞吐量就是99%。
如下是 Parallel Scavenge 收集器和 Parallel Old 收集器结合进行垃圾收集的示意图(这也是 JDK1.8 默认的方式):
老年代垃圾收集器
Serial Old 收集器
Serial Old 收集器是 Serial 的老年代版本,同样是一个单线程收集器,采用标记-整理算法。
如下图是 Serial 收集器和 Serial Old 收集器结合进行垃圾收集的示意图:
CMS(Concurrent Mark Sweep) 收集器
CMS收集器是一种以最短回收停顿时间为目标的收集器,以"最短用户线程停顿时间"著称。整个垃圾收集过程分为4个步骤:
① 初始标记:标记一下 GC Roots 能直接关联到的对象,速度较快。
② 并发标记:进行 GC Roots Tracing,标记出全部的垃圾对象,耗时较长。
③ 重新标记:修正并发标记阶段引用户程序继续运行而导致变化的对象的标记记录,耗时较短。
④ 并发清除:用标记-清除算法清除垃圾对象,耗时较长。
整个过程耗时最长的并发标记和并发清除都是和用户线程一起工作,所以从总体上来说,CMS 收集器垃圾收集可以看做是和用户线程并发执行的。
-
图例
-
CMS收集器存在的一些缺点
对CPU资源敏感:默认分配的垃圾收集线程数为(CPU 数+3)/4,随着CPU数量下降,占用CPU资源越多,吞吐量越小;无法处理浮动垃圾(在并发清理阶段,用户线程还在运行时产生的垃圾) -
适用场景
重视服务器响应速度,要求系统停顿时间最短。可以使用 -XX:+UserConMarkSweepGC 来选择 CMS 作为老年代收集器。
Parallel Old 收集器
Parallel Old收集器是Parallel Scavenge的老年代版本,是一个多线程收集器,采用标记-整理算法。可以与Parallel Scavenge收集器搭配,可以充分利用多核 CPU 的计算能力。
- 图例
- 适用场景
与Parallel Scavenge 收集器搭配使用;注重吞吐量。jdk7、jdk8 默认使用该收集器作为老年代收集器
G1
https://www.cnblogs.com/xiaofengshan/p/12817608.html
对象在内存分代中如何流转
年轻代
大部分对象刚创建的时候都会分配在年轻代的Eden区,如果内存不够,部分对象进入老年代,部分对象继续被分配到Survivor区,当年轻代空间不够就会触发MinorGC(YGC,只回收年轻代内存)。
- MinorGC步骤
- YGC回收之后,大多数的对象会被回收,活着的进入s0(To Survivor)
- From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor,同时清空Eden 和 From Survivor 分区。
- 进入老年代的时机
- 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年 龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。
- 年轻代无法装进大对象,直接进入老年代。
老年代
老年代的对象都是从年轻代根据一定的规则流转过来的,当OldGen区内存不够,则会进行FullGC。
- 老年代的对象流转规则
- 超过指定年龄(参数-XX:MaxTenuringThreshold 配置,默认15)的没有被垃圾回收,流转到老年代。
- 大对象直接进入,超过参数指定字节数(-XX:PretenureSizeThreshold)设置的字节数的大对象会直接进入老年代,这是因为对象越大,复制开销就越大。
- 动态年龄判断规则进入,意思是不一定要到指定年龄再流转到15,如果某一年龄以上的对象到达一定大小,也会提前进入老年代。当躲过一轮GC的对象加起来超过surrvivor区50%,如年龄1+年龄2+年龄n一直累加,直到年龄n的时候发现加起来超过了surrvivor空间的50%,则年龄n以上的对象直接进入老年代。
- minorGC发生时,suprivor区放不下,超过的部分转移到老年代。
GC常用参数
- -Xmn -Xms -Xmx -Xss
年轻代 最小堆 最大堆 栈空间 - -XX:+UseTLAB
使用TLAB,默认打开 - -XX:+PrintTLAB
打印TLAB的使用情况 - -XX:TLABSize
设置TLAB大小 - -XX:+DisableExplictGC
System.gc()不管用 ,FGC - -XX:+PrintGC
打印GC简要信息 - -XX:+PrintGCDetails
打印GC详细信息 - -XX:+PrintHeapAtGC
打印GC前后堆的概况 - -XX:+PrintGCTimeStamps
打印CG发生的时间戳 - -XX:+PrintGCApplicationConcurrentTime (低)
打印应用程序时间 - -XX:+PrintGCApplicationStoppedTime (低)
打印暂停时长 - -XX:+PrintReferenceGC (重要性低)
记录回收了多少种不同引用类型的引用 - -verbose:class
类加载详细过程 - -XX:+PrintVMOptions
- -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial
必须会用 - -Xloggc:opt/log/gc.log
- -XX:MaxTenuringThreshold
升代年龄,最大值15 - 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 ...
这些不建议设置
Parallel常用参数
- -XX:SurvivorRatio
- -XX:PreTenureSizeThreshold
大对象到底多大 - -XX:MaxTenuringThreshold
- -XX:+ParallelGCThreads
并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同 - -XX:+UseAdaptiveSizePolicy
自动选择各区大小比例
CMS常用参数
- -XX:+UseConcMarkSweepGC
- -XX:ParallelCMSThreads
CMS线程数量 - -XX:CMSInitiatingOccupancyFraction
使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收) - -XX:+UseCMSCompactAtFullCollection
在FGC时进行压缩 - -XX:CMSFullGCsBeforeCompaction
多少次FGC之后进行压缩 - -XX:+CMSClassUnloadingEnabled
- -XX:CMSInitiatingPermOccupancyFraction
达到什么比例时进行Perm回收 - GCTimeRatio
设置GC时间占用程序运行时间的百分比 - -XX:MaxGCPauseMillis
停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代
常用GC回收器组合参数
-XX:+UseSerialGC:
Serial New (DefNew) + Serial Old,小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器。
-XX:+UseConc(urrent)MarkSweepGC:
ParNew + CMS + Serial Old,一般不使用。
-XX:+UseParallelGC:
Parallel Scavenge + Parallel Old(1.8默认)。
-XX:+UseG1GC:
G1。
参考
https://blog.csdn.net/chuobenggu7592/article/details/100978957
https://blog.csdn.net/weixin_38106322/article/details/109122814
https://blog.csdn.net/weixin_43228814/article/details/88934939
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~