Java 垃圾收集器与内存分配策略

本文深入探讨了Java垃圾回收机制,介绍了垃圾收集器如何通过根搜索算法确定对象的存活状态,解析了不同类型的引用(强引用、软引用、弱引用、虚引用)以及它们在内存管理中的作用。同时,文章对比了标记-清除、复制和标记-整理等垃圾收集算法的优缺点,阐述了分代收集算法在现代虚拟机中的应用。

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

“Java和C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人想出来。”

垃圾收集(Garbage Collection,GC)

当需要排查各种内存溢出,内存泄漏问题时,当垃圾收集称为系统达到更高并发量的瓶颈时,就需要对这些自动化的技术实施必要的监控和调节。

java堆中的内存的分配个回收都是动态的,垃圾收集器所关注的是这部分内容。堆中几乎存放着所有对象实例,垃圾收集器对堆进行回收之前第一件事情就是确定有哪些对象还活着,哪些已经死去(不可能再被任何途径使用的对象)。

引用计数算法

判断对象是否存活:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;引用时效时,计数器就减1;任何时刻计数器都为0的对象就是不可能再被使用的。

Java语言中没有使用引用计数法来管理内存,其中主要的原因是它很难解决对象之间的相互循环引用的问题。

根搜索算法

Java和C#还有Lisp都是使用根搜索算法判断对象是否存活的,这个算法的基本思路就是通过一系列的名为GC Roots的对象作为起始点,从这些节点向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(不可达),证明此对象是不可用的。
在Java中,可作为GC Roots的对象包括:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNL(一般说的Native方法)引用的对象

JDK1.2之前对于引用的定义:
如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。
1.2之后,Java对引用的概念进行了补充,将引用分为强引用,软引用,弱引用,虚引用四种,引用强度逐渐减弱。

  1. 强引用就是在程序中普遍存在的,只要还存在垃圾回收期就永远不会回收被引用的对象。
  2. 软引用被用来描述还有用,但是非必需的对象。**在内存将要发挥上能溢出之前将这些软引用关联的对象列入回收范围之内,进行第二次回收。**如果还是内存不够,就会抛出异常。
  3. 弱引用描述非必需对象,只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
  4. 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收时收到一个系统通知。

对象可以在被GC时自我拯救,但是这种自救的机会只有一次,因为一个对象的finalize()的方法最多只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行。finalize()能做的所有工作,使用try-finally或其他方式都能做得更好,更及时,不推荐使用finalize()方法。

回收方法区

可以不要求虚拟机在方法区实现垃圾收集,而且在方法区进行垃圾收集的性价比一般比较低,在堆中,尤其是新生代中,常规应用进行一次垃圾收集一般可以回收70%-95%的空间,而永久代的垃圾收集效率远低于此。

永久代,垃圾收集主要回收:废弃常量+无用的类

判断一个常量是否是“废弃常量”:在常量池中的"abc",在系统中没有一个String对象引用它,在回收时就会把它清理出常量池。
判断“无用的类”:

  1. 该类所有的实例都被回收,堆中不存在任何该类的实例。
  2. 加载该类的ClassLoader已经被回收。
  3. 该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾收集算法
标记-清除算法

算法分为标记和清除两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。标记清除之后会产生大量不连续的垃圾内存碎片,最后导致需要分配大的内存的时候不得不提前触发另一次垃圾收集动作。

复制算法

解决效率问题,这种算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次清理掉。不用考虑内存碎片,实现简单,运行高效。代价是将内存缩小为原来的一半,过高。

标记-整理算法

使用复制收集算法在对象存活率较高时就要执行较多的复制动作,效率将会变低。这种算法和标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。

分代收集算法

当代商业虚拟机的垃圾收集都采用“分代收集”的算法,这种算法没有什么新的思想,只是根据对象的存活周期的不同将内存划分为几块。以按时把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法。在新生代,每次垃圾收集都有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量粗活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外的空间对他进行分配担保,就必须使用标记-清理或者标记-整理算法进行回收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Antrn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值