garbage-collection垃圾收集入门


备注

垃圾收集(GC)是一种自动回收由程序不再需要的对象占用的内存的方法。这与手动内存管理形成对比,其中程序员明确指定应该释放哪些对象并将其返回到内存。好的GC策略比手动内存管理更有效,但它可能取决于软件的类型。

垃圾收集的主要优点是:

  • 它使程序员不必进行手动内存管理。
  • 它避免了手动内存管理可能产生的某些难以发现的错误(例如悬空指针,双重释放,某些类型的内存泄漏)。
  • 使用垃圾收集的语言通常不那么复杂。

主要缺点是:

  • 与手动内存管理相比,垃圾收集有一些开销。
  • 它可能会影响性能,尤其是在不需要的时刻触发垃圾收集时。
  • 它是不确定的,程序员不知道何时完成垃圾收集以及是否释放了对象。

大多数“较新的”编程语言都内置了垃圾收集,例如Java,C#,.NET,Ruby和JavaScript。像C和C ++这样的旧语言没有垃圾收集,尽管有垃圾收集可用的实现。还有一些语言允许您使用垃圾收集和手动内存管理的组合,例如Modula-3和Ada。

垃圾收集策略不同,但许多人使用标记和扫描方法的(变体)。在标记阶段,找到并标记所有可访问的对象。在扫描阶段,扫描堆以查找无法访问和未标记的对象,然后清除这些对象。现代垃圾收集器还使用分代方法,其中保留两个或更多个对象分配区域(代)。最年轻的一代包含最新分配的对象,并且经常清理。在某个时间段内“存活”的对象被提升为老一代。

许多使用GC的语言允许程序员对其进行微调(例如,参见Java 8虚拟机垃圾收集调整指南.Net垃圾收集文档

在Java中启用详细的gc日志记录

通常,jvm的垃圾收集(gc)对用户(开发人员/工程师)是透明的。

除非用户面临内存泄漏或需要大量内存的应用程序,否则通常不需要GC调整 - 这两者最终都会导致内存不足异常,从而迫使用户查看问题。

第一步通常是增加内存(堆或perm-gen / meta-space,具体取决于它是由于运行时加载还是应用程序的libary基础很大,或者类加载或线程中是否存在泄漏 - 处理机制)。但是,只要不可行,下一步就是试着了解出了什么问题。

如果只想在特定时刻获取快照,那么作为jdk一部分的jstat 实用程序就足够了。

但是,为了更详细地理解,在每个gc事件之前和之后都有一个包含堆快照的日志会很有帮助。为此,用户必须使用-verbose:gc 作为jvm启动参数的一部分来启用详细的gc日志记录,并包括-XX:+PrintGCDetails-XX:+PrintGCTimeStamp 标志。

对于那些想要主动分析其应用程序的人,还有jvisualvm 这样的工具,它们也是jdk的一部分,通过它们可以深入了解应用程序行为。

下面是一个示例程序,gc配置和verbose-gc日志输出:

package com.example.so.docs.gc.logging;

import java.util.Arrays;
import java.util.Random;

public class HelloWorld {

    public static void main(String[] args) {
        sortTest();
    }
    
    private static void sortTest() {
        System.out.println("HelloWorld");
        
        int count = 3;
        while(count-- > 0) {
            int size = 1024*1024;
            int[] numbers = new int[size];
            Random random = new Random();
            for(int i=0;i<size;i++) {
                numbers[i] = random.nextInt(size);
            }
            
            Arrays.sort(numbers);
        }
        System.out.println("Done");
        
    }
    

}
 

GC选项:

-server -verbose:gc  -XX:+PrintGCDetails -XX:+PrintGCTimeStamps  -Xmx10m  -XX:-PrintTenuringDistribution  -XX:MaxGCPauseMillis=250 -Xloggc:/path/to/logs/verbose_gc.log
 

输出:

Java HotSpot(TM) 64-Bit Server VM (25.72-b15) for windows-amd64 JRE (1.8.0_72-b15), built on Dec 22 2015 19:16:16 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 6084464k(2584100k free), swap 8130628k(3993460k free)
CommandLine flags: -XX:InitialHeapSize=10485760 -XX:MaxGCPauseMillis=250 -XX:MaxHeapSize=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:-PrintTenuringDistribution -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
0.398: [GC (Allocation Failure) [PSYoungGen: 483K->432K(2560K)] 4579K->4536K(9728K), 0.0012569 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.400: [GC (Allocation Failure) [PSYoungGen: 432K->336K(2560K)] 4536K->4440K(9728K), 0.0008121 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.401: [Full GC (Allocation Failure) [PSYoungGen: 336K->0K(2560K)] [ParOldGen: 4104K->294K(5632K)] 4440K->294K(8192K), [Metaspace: 2616K->2616K(1056768K)], 0.0056202 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
0.555: [GC (Allocation Failure) [PSYoungGen: 41K->0K(2560K)] 4431K->4390K(9728K), 0.0004678 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.555: [GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 4390K->4390K(9728K), 0.0003490 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.556: [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 4390K->293K(5632K)] 4390K->293K(8192K), [Metaspace: 2619K->2619K(1056768K)], 0.0060187 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 2560K, used 82K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd14938,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 5632K, used 4389K [0x00000000ff600000, 0x00000000ffb80000, 0x00000000ffd00000)
  object space 5632K, 77% used [0x00000000ff600000,0x00000000ffa49670,0x00000000ffb80000)
 Metaspace       used 2625K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 282K, capacity 386K, committed 512K, reserved 1048576K
 

以下是关于GC的一些有用链接:

  1. 一个解释gc概念的存档页面(jdk7)
  2. G1收集器教程
  3. 有用的VM选项
  4. JDK 5 - GC人体工程学(概念仍然相关)
  5. JDK 6调优(概念仍然相关)

介绍

如果对象不再可由程序中的主入口点访问,则它们有资格进行垃圾收集(GC)。 GC通常不是由用户显式执行的,但是为了让GC知道对象不再需要,开发人员可以:

取消引用/指定null

someFunction {
     var a = 1;
     var b = 2;
     a = null; // GC can now free the memory used for variable a
     ...
} // local variable b not dereferenced but will be subject to GC when function ends
 

使用弱引用

大多数使用GC的语言允许您创建对象的弱引用,这些引用不计入GC的引用。如果只有对象的弱引用而没有强(正常)引用,则该对象符合GC的条件。

WeakReference wr = new WeakReference(createSomeObject());
 

请注意,在此代码之后,使用弱引用的目标而不检查对象是否仍然存在是危险的。初学者程序员有时会犯这样的代码错误:

if wr.target is not null {
    doSomeAction(wr.target);
}
 

这可能会导致问题,因为在null检查之后和执行doSomeAction之前可能已调用GC。最好先创建对象的(临时)强引用,如下所示:

Object strongRef = wr.target;
if strongRef is not null {
    doSomeAction(strongRef);
}
strongRef = null;