java垃圾回收
发布日期:2021-05-10 04:59:18 浏览次数:23 分类:精选文章

本文共 2442 字,大约阅读时间需要 8 分钟。

为什么需要垃圾回收

在任何计算机系统中,内存是非常宝贵的资源。如果我们不进行垃圾回收,内存将逐渐被耗尽,因为无论我们如何分配内存空间,都需要回收它进行重新利用。内存无限大的假设在现实中并不成立,因此垃圾回收是必须的。


垃圾回收的定义

垃圾回收的核心在于确定哪些内存空间已经不再被使用,需要安全地释放回系统。要实现这一点,我们需要明确垃圾的定义。

在垃圾回收过程中,最常用的是引用计数算法可达性分析法。引用计数算法通过记录每个对象的引用次数来判断是否需要回收。但这种算法在处理循环引用时效率较低,比如:

  • 对于一个圆环结构的三个对象A → B → C → A,A、B、C都会引用对方,没有任何一个引用计数为0,这时候引用计数算法会将它们都标记为不需要回收。

而**可达性分析法(Reachability Counting)**则通过从GC根源(GC Roots)开始,逐步遍历所有引用的链路。没有任何GC根源可以到达的对象就会被标记为垃圾。例如,虚拟机栈中的局部变量、方法区的类静态属性、常量以及本地方法的引用都是GC根源。


引用类型与垃圾回收策略

在垃圾回收过程中,内存回收的强度与引用的类型密切相关。JDK1.2之前,引用的定义较为简单,但在JDK1.2以后,引用的概念被扩展为四种:强引用软引用弱引用虚引用

强引用(Strong Reference)

强引用是最常见的引用类型。当一个对象被强引用时,垃圾回收器永远不会回收它的内存。例如:

Object o = new Object();

只要o强存在,垃圾回收器都不会回收o对象。

软引用(Soft Reference)

软引用用于描述那些在短期内可能用不上,但并非绝对必要的对象。在内存不足极为严重时,软引用对象才会被回收。例如:

SoftReference
softRef = new SoftReference
(new SomeObject());
// 内存不足时:
softRef.clear();

需要注意的是,软引用对象在内存还未严重不足时不会被回收。

弱引用(Weak Reference)

弱引用用于描述那些非必须对象。弱引用关联的对象在垃圾回收过程中的存活时间是由垃圾回收器决定的。例如:

WeakReference weakRef = new WeakReference<>(new Object());
// 垃圾回收后,weakRef.get()可能返回 null

虚引用(Phantom Reference)

虚引用存在的唯一目的是为了跟踪对象的回收状态。它不会阻止对象的回收,但可以在回收时收到一个回收事件通知。例如:

PhantomReference phantomRef = new PhantomReference<>(new Object());

方法区的垃圾回收

除了堆内存的垃圾回收,方法区的垃圾回收也有其独特之处。方法区的垃圾回收主要回收两类对象:

  • 废弃常量:例如字面量、符号引用等。例如,在常量池中存在一个字符串“abc”,但如果所有引用对象已经被回收,那么“abc”也可能被回收。
  • 无用类:满足以下三个条件的类会被回收:
    • 所有实例已被回收。
    • 加载该类的ClassLoader已被回收。
    • 类对应的Class对象没有被任何地方引用,无法通过反射访问。

  • 垃圾收集算法

    垃圾收集算法主要分为四种类型:

    1. 标记-清除(Mark-Sweep)

    这是垃圾回收的基础算法。它分为两步:

    • 标记:标记所有需要回收的对象。
    • 清除:清除所有被标记的对象。

    标记-清除算法的优点是简单直接,但缺点是效率低下。它会产生大量内存碎片。

    2. 复制(Copying)

    为了解决标记-清除的效率问题,复制算法将可用内存划分为两块,每次只用一块。当一块使用完毕时,将存活对象复制到另一块,清理已使用的内存。

    • 优点:效率高,内存碎片少。
    • 缺点:每次回收后需要额外的空间来存活对象。

    3. 标记-整理(Mark-Compact)

    这是专门为老年代设计的算法。它与标记-清除不同的地方在于,存活对象会被整理到一边,然后清理掉另一边的内存。这种算法效率比标记-清除稍高,但仍会产生内存碎片。

    4. 分代收集(Generational Collection)

    这是当前商业虚拟机中最常用的垃圾收集算法。它将堆内存划分为新生代和老年代:

    • 新生代:采用复制算法。
    • 老年代:采用标记-清除或标记-整理算法。

    新生代和老年代的划分比例(如8:1)可以根据具体要求进行调整。

    • 8:1:新生代占非永久内存的90%,旧生代占10%。

    GC日志是什么?

    GC日志是阅读VM性能问题的基础工具。它记录垃圾回收过程中发生了什么事件。日志中通常会包含以下信息:

  • GC触发时间:垃圾回收发生的具体时间。
  • GC类型:如普通GC、 Full GC。
  • 内存变化:垃圾回收前后的内存占用变化。
  • 时间消耗:垃圾回收耗时与调用的具体线程。
  • 例如,以下是一段典型的GC日志示例:

    [GC 283.736:[ParNew:261599K->261599K(261952K),0.0000288 secs]
    [Full GC 3324K->152K(3712K),0.0025925 secs]

    常见问题

  • 虚拟机中的内存是由谁分配的?

    • 内存分配由CPU执行malloc等指令完成。
  • 什么是内存碎片?

    • 内存碎片是指未被使用的内存空间。大量碎片会影响内存分配效率。
  • Finalize方法如何被调用?

    • Finalize方法由垃圾回收器自动调用。但虚拟机只会在特定条件下(如触发GC)才会定期执行Finalizer线程。
  • 如何让对象被回收?

    • 如果对象没有强引用,并且无法从GC根源路径上到达它,就会被回收。
  • 如何选择垃圾收集算法?

    • 新生代采用复制算法,老年代采用标记-整理算法,老年代的垃圾收集依赖于对象的存活率。
  • 上一篇:java线程--一网打尽
    下一篇:java内存模型

    发表评论

    最新留言

    哈哈,博客排版真的漂亮呢~
    [***.90.31.176]2025年04月21日 04时54分46秒