Java垃圾收集器 - 何时进行垃圾回收?

67

什么决定了垃圾收集器何时实际进行垃圾收集?它是在特定的时间之后还是在使用了一定量的内存之后发生?或者有其他因素吗?

6个回答

41
它在确定需要运行时运行。在分代垃圾收集器中,一种常见的策略是在第0代内存分配失败时运行垃圾收集器。也就是说,每次分配少量内存块(大块内存通常直接放入“较老”的代中)时,系统会检查第0代堆中是否有足够的可用空间,如果没有,它将运行GC以释放空间以使分配成功。 然后将旧数据移动到第1代堆中,当那里的空间耗尽时,GC会在那里运行一个收集操作,升级最长时间存在的数据到第2代堆中,依此类推。所以GC不仅仅是“运行”。它可能仅在第0代堆上运行(大多数收集将只是这样),或者如果确实需要释放大量内存,则可能检查每个代。
但这远非唯一的策略。并发GC在后台运行,程序运行时进行清理。一些GC可能作为每个内存分配的一部分运行。增量收集器可能会这样做,每次内存分配扫描几个对象。
垃圾收集器的整个目的是它应该在不需要任何用户输入的情况下执行其任务。因此,通常情况下,您不能以及不应该预测它何时运行。
其他JVM当然可以自由选择任何策略。
编辑:关于Java和分代GC的上述部分是不正确的。请参阅下面的详细信息:

Java 1.0和1.1虚拟机使用标记-清除收集器,这可能会在垃圾回收后使堆成为碎片。从Java 1.2开始,虚拟机切换到分代收集器,其具有更好的碎片整理行为(参见Java理论与实践:垃圾收集和性能)。

因此,Java实际上已经有了分代GC很久了。Java 6中新的是Garbage-First(G1)垃圾收集器,可在Java 6u14中使用。根据声称发布于1.6.0_14的文章它不是默认启用的。并行收集器仍然是默认GC,对于普通家庭用途来说,它是最有效的GC。 G1旨在成为并发收集器的替代品。它被设计为更可预测,并允许使用内存区域进行快速分配。


1
@Johannes Weiß 不,自 JDK 1.2以来就是这样。1.0和1.1虚拟机使用标记-清除收集器,在垃圾回收后可能会使堆片段化。从Java 1.2开始,虚拟机切换到分代收集器,其具有更好的碎片整理行为。请参见http://www.ibm.com/developerworks/library/j-jtp01274.html。 - Pascal Thivent
1
@Mr.Goose 不,当垃圾被识别出来时,它会被释放。代际垃圾回收意味着垃圾回收仅检查某些对象是否为垃圾。它经常检查任何新创建的对象(第0代)是否可以被释放。如果一个对象存活足够长时间以达到一定的阈值,那么它就被放入第1代,这意味着GC在正常快速的垃圾回收期间不会检查它。偶尔,GC会查看第1代对象,看看它们中是否有任何可以释放的垃圾,而更少见的是,它会查看第2代对象(直到GC使用的所有代数)。 - jalf
这只是启发式的实现,即“如果一个对象刚刚被创建,那么它很快就会变成垃圾,所以每当我们需要释放内存时,这些应该是我们检查的第一个对象。但是,如果它已经存在很长时间,它可能会继续存在很长时间。因此,不断检查它将是低效的。” - jalf
1
@S.R Lobato:这个(相当古老的)问题典型地误认为“垃圾必须被清除”。分代GC将所有幸存对象从一个内存空间移动到另一个内存空间,这意味着剩下的一切都是未使用的内存或垃圾,不需要任何额外的操作,因为整个内存空间现在已经被释放了,根据定义。 - Holger
1
@jalf:旧一代对象不仅被遍历的频率较低,JVM也会切换策略。虽然年轻一代对象需要遍历所有年轻引用,但JVM会跟踪旧一代对象的变化,因为旧对象变化较少,所以GC也可以跳过自上次主要GC以来未发生变化的旧对象区域,因此仍然引用与之前相同的对象。 - Holger
显示剩余4条评论

9

当JVM没有足够的内存空间运行时,垃圾收集器会运行并删除不必要的对象以释放内存。

不必要的对象是指没有其他引用(地址)指向它们的对象。

一个对象有主要4种方式可以被标记为可回收。

  1. Null Referencing

    The garbage collector can delete an object when the reference variable of the object is assigned null as its value.

        A a = new A();
        a = null;
    
  2. Re-assigning

    When another object is assigned to the reference variable of an object, the older referenced object can be deleted by the garbage collector.

      A a = new A(100);
      a =new A(200);
    
  3. Local Scope

    If an object is created inside a block, then that object will be eligible for garbage collection outside that block.

      if(condition){
    
         A a = new A();
    
      }
    
  4. Isolation

    An object can contain a reference to another object, but there must be at least one reference (address) variable for those objects in the stack, otherwise all those objects are eligible for garbage collection.

          class A{
                A r;
                A(int i){
                 //something   
               }
          } 
    
          A a1 = new A(100);
          a1.r = new A(101);
          a1.r.r = new A(102);
          a1.r.r.r = a1;
    
          a1  = null //all ojects are eligible to garbage collector  
    

9
  • 这取决于程序JIT编译的方式。
  • 从外部我们无法确定它何时运行。
  • 它遵循某种算法,这取决于特定的GC。
  • 在客户机上运行Java虚拟机,默认情况下,Windows的虚拟内存为4GB。这也取决于那个特定时间的空闲虚拟内存。

您可以尝试此小程序来检查GC的行为

public class GCTest {

   final int NELEMS = 50000;

   void eatMemory() {

      int[] intArray = new int[NELEMS];

      for (int i=0; i<NELEMS; i++) {
        intArray[i] = i;
      }

   }

   public static void main (String[] args) {

      GCTest gct = new GCTest();

      // Step 1: get a Runtime object
      Runtime r = Runtime.getRuntime();

      // Step 2: determine the current amount of free memory
      long freeMem = r.freeMemory();
      System.out.println("free memory before creating array: " + freeMem);

      // Step 3: consume some memory
      gct.eatMemory();

      // Step 4: determine amount of memory left after consumption
      freeMem = r.freeMemory();
      System.out.println("free memory after creating array:  " + freeMem);

      // Step 5: run the garbage collector, then check freeMemory
      r.gc();
      freeMem = r.freeMemory();
      System.out.println("free memory after running gc():    " + freeMem);
   }
}

可能的输出 -- 在您的情况下可能会有所不同

free memory before creating array: 4054912
free memory after creating array:  3852496
free memory after running gc():    4064184

请查看这个链接 http://www.devdaily.com/java/edu/pj/pj010008/


创建数组前的可用内存:263777600 创建数组后的可用内存:263577584 运行垃圾回收(GC)后的可用内存:14174392。为什么运行GC后显示更少的内存? - Yugraaj Sandhu

5

这很大程度上取决于您实际使用的垃圾收集器,它的调整方式以及许多输入参数。

有关HotSpot Garbage Collector(Java常用的垃圾收集器)及其调整方式的详细信息,请查看此链接


2
这完全取决于实际的JVM以及它选择做什么,作为程序员,这基本上不在你的控制范围之内。灰发老顽固专家可能想告诉JVM他们更懂,但对于普通人来说,这应该被视为黑魔法,最好不要碰。
你需要关心的是它是否能跟上你的程序创建和丢弃对象的速度。如果不能,整个程序会停止,进行全局清理。这会导致非常糟糕的响应时间,但在现代计算机上的现代JVM中很少发生。
如果你想了解程序中发生的事情以及何时发生,请调查Java 6 JDK最近版本中的“jvisualvm”工具。它非常适合用来窥视内部。

1

垃圾回收器在需要资源时运行,并且定期运行,您可以通过告知何时是收集CPU的好时机来影响它,使用System.gc()

您可以通过显式地将引用设置为null来帮助垃圾回收器,例如,通过为您的对象提供分配资源的init()方法和显式清理这些资源并将其引用设置为null的cleanup()方法。通过自己将引用设置为null,您可以防止垃圾回收器不得不查找具有更多路径到根的对象群集。


1
对于现代垃圾回收器而言,将变量设置为null或不设置都没有区别,而调用System.gc()几乎总是愚蠢的。 - Fredrik
3
请不要使用"愚蠢"之类的词语,将变量设置为null是一种良好的实践,它有助于垃圾回收以小步骤执行而不是较大的块。 (注意,我谈论的是类属性而不是堆栈上的临时方法变量。)至于调用System.gc(),普通应用程序无需费心,但大型应用程序可能需要它以平滑收集时间,始终保持应用程序响应性。 - rsp
@rsp:GC的工作方式几乎总是愚蠢或至少会对gc性能造成损害,因此调用System.gc()几乎总是没有帮助的。这也是为什么a) gc甚至不需要理会它 b) 可以关闭它,以免受到某些认为自己比gc算法更聪明的人编写的代码的影响。之所以仍然可能存在这种情况,是因为正如我所说,“几乎总是”愚蠢,这意味着有一些地方你确实可能想要这样做。 将变量设置为null只会增加代码量,根本不会影响gc。 - Fredrik
@rsp:为了更好地理解为什么在大多数情况下将其设置为null并不重要,看一下以下博客文章会有帮助。它们并不完美,但足以向非专业人士解释清楚:http://chaoticjava.com/posts/how-does-garbage-collection-work/ 和 http://chaoticjava.com/posts/parallel-and-concurrent-garbage-collectors/。 唯一能帮到的情况是,如果保留引用的变量将继续存在,而所引用的数据不再需要,那就可以将其置空,但这种情况很明显,并不像“自己将每个引用置空”那样。 - Fredrik
我知道垃圾收集器是如何工作的,只是有时在高流量情况下,它们倾向于推迟收集,直到需要一次性处理太多的垃圾,从而妨碍业务。有趣的是你提到了IBM。我记得有一个特别的例子涉及他们应用服务器、JVM和操作系统上的代码,如果不提示垃圾收集器,就会出现故障。 - rsp
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接