后端---jvm中的垃圾回收机制

本文深入探讨了Java垃圾回收机制,解析了JVM内存空间结构,包括新生代和旧生代的作用与管理方式,以及垃圾回收算法如标记-清除、复制、标记-整理和分代回收的具体应用。同时,文章介绍了几种常见的垃圾回收器,如Serial、ParNew、Parallel、CMS和G1收集器的特点及适用场景,并提出了GC优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java垃圾回收机制

1.JVM内存空间

 

  JVM堆(Heap)= 新生代(Young) + 旧生代(Tenured)

 分区作用: 

新创建的对象通常先将其分配在新生代中,在新生代中经过若干次GC之后仍未释放的对象,再将它移动到旧生代。为了让内存回收更高效(GC会暂停JVM中的应用),Sun JDK在1.2开始对堆采用了分代管理的方式。在分配对象遇到内存不足时,先对新生代进行GC(Young GC);当新生代GC之后仍无法满足内存空间分配需求时, 才会对整个堆空间以及方法区进行GC (Full GC)

 相关参数:

-Xms                       -- 设置堆内存初始大小

 -Xmx                       -- 设置堆内存最大值

-XX:MaxTenuringThreshold  -- 设置对象在新生代中存活的次数

-XX:PretenureSizeThreshold  -- 设置超过指定大小的大对象直接分配在旧生代中

 注意点:当新生代设置得太小时,也可能引发大对象直接分配到旧生代中。

 

  新生代(Young)= Eden区 + Survivor区

1、常见的标记可用对象的算法有哪些?
(1)引用计数法:每个对象都创建一个私有的引用计数器,当该对象被其他对象引用时(出现在等号右边),引用计数器加1;当不再引用时,引用计数器减一;当引用计数器为0时,对象即可被回收。这种方式存在着当两个对象互相引用时,二者引用计数器值都不为0无法被回收的问题;
(2)根搜索算法:JVM一般使用的标记算法,把对象的引用看作图结构,由根节点集合出发,不可达的节点即可回收,其中根节点集合包含的如下5种元素:
1、Java栈中的对象引用;
2、本地方法栈中的对象引用;
3、运行时常量池的对象引用;
4、方法区中静态属性的对象引用;
5、所有Class对象;

2、常见的垃圾回收算法有哪些?JVM使用哪种?
(1)标记-清除算法:分两个阶段执行,第一个阶段标记可用对象,第二个阶段清除垃圾对象;这个方法很基础简单,但效率低下,而且会产生内存碎片(不连续的内存空间),无法再次分配给较大对象。
(2)复制算法:被广泛用于新生代对象的回收。将内存分为两个区域,新对象都分配在一个区域中,回收时将可用对象连续复制到另一个区域,回收完成后,新对象分配在有对象的区域,循环往复。这种算法不会产生内存碎片,且效率较高,但因为同时只有一个区域有效,会导致内存利用率不高。
(3)标记-整理算法:被应用于老年代对象的回收。这种算法与标记清除算法类似,第一个阶段标记可用对象,第二个阶段将可用对象移动到一段连续的内存上,解决了标记-清除算法会产生内存碎片的缺点。
(4)分代回收算法:在HotSpot虚拟机中,基于分代的特点(堆内存可进一步分为年轻代、老年代,老年代存放存活时间较长的对象),JVM GC使用分代回收算法。
年轻代使用复制算法:分为一个较大的Eden区与两个较小的、等大小的Survivor区(From Space与To Space),比例一般是8:1:1。新对象都分配在Eden区,当GC发生时(新生代的GC一般叫做Minor GC),将Eden区与From区中的可用对象复制到To区中,From Space与To Space互换名称,循环方法。直到发生如下两种情况,对象进入老年代:
1' From区内的对象已经达到存活代数阀值(经过GC的次数达到设定值),GC时不会进入To区中,直接移动至老年代;
2' 在回收Eden区与From区后,超出To区可容纳范围,则直接将存活对象移动至老年代。

老年代使用标记-整理算法:当老年代满的时候,会触发Full GC(新生代与老年代一起进行GC)。

3、常见的垃圾回收器有哪些?有什么特点?适合应用与什么场景?
(1)Serial收集器
年轻代采用复制算法、串行回收、与“Stop the world”机制(GC时停止其他一切工作),适用于单核CPU环境,绝对不推荐应用于服务器端。
Serial提供了老年代的回收器Serial Old,采用标记-整理算法,其他特性与新生代一致。
Serial+Serial Old适合客户端场景。
(2)ParNew收集器
相当于Serial的多线程版本,并行回收,年轻代同样采用复制算法与“Stop the world”机制,适用于多核CPU、低延迟环境,推荐应用于服务器场景。
(3)Parallel收集器
与ParNew类似,复制算法、并行回收、“Stop the world”机制,但是与ParNew不同,Parallel可以控制程序吞吐量大小,也被称为吞吐量优先的垃圾收集器。
与Serial类似,Parallel也有老年代版本,Parallel Old,同样采用标记整理-算法。
Parallel+Parallel Old非常适用于服务器场景。
(4)CMS收集器
与Parallel的高吞吐对应,CMS就是为高并发、低延时而生的。采用标记-清除算法、并行回收、“Stop the world”。因为采用了标记-清除算法,会产生大量内存碎片,要慎重使用。
(5)G1收集器
是一款基于并行、并发、低延时、暂停时间可控的区域化分代式垃圾回收器。
具有革命意义的设计,放弃了堆区年轻代、老年代的划分方案,而是将堆区或分成约2048个大小相同的独立Region块。
详情可参看ImportNew博文http://www.importnew.com/15311.html

4、GC的优化方案?
基本的原则就是尽可能地减少垃圾和减少GC过程中的开销。其中需要注意,JVM进行次GC的频率很高,但因为Minor GC占用时间极短,所以对系统产生的影响不大。更值得关注的是Full GC的触发条,具体措施包括以下几个方面:
(1)不要显式调用System.gc()
调用System.gc()也仅仅是一个请求(建议)。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。但即便这样,很多情况下它会触发Full GC,也即增加了间歇性停顿的次数。
(2)尽量减少临时对象的使用
临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,也就减少了Full GC的概率。
(3)对象不用时最好显式置为Null
一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。
(4)尽量使用StringBuffer,而不用String来累加字符串
由于String是常量,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
(5)能用基本类型如Int,Long,就不用Integer,Long对象
基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。
(6)尽量少用静态对象变量
静态变量属于全局变量,不会被GC回收,它们会一直占用内存。
(7)分散对象创建或删除的时间
集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行Full GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值