GC - The Garbage Collector

 The Garbage Collector


Note that the discussions of garbage collectors, the various GC algorithms, and modeling in the following sections focus on Sun's implementations of the Java Virtual Machine.


The Java runtime system's garbage collector runs as a separate thread. As the application allocates more and more objects, it depletes the amount of heap memory available. When this drops below a threshold percentage, the garbage collection process begins. The garbage collector stops all other runtime threads. It marks each object as live or dead, and reclaims the space taken up by the dead objects.


There are many different types of garbage collectors. Each is based on a different algorithm and exhibits different behavior. They range from simple reference-counting collectors to very advanced generational collectors. Both the algorithm chosen and the implementation can affect the behavior of the garbage collector. Here are some terms that refer to the implementation of a particular collector – note these are not mutually exclusive:


Stop-the-world – Stops all application threads while it works.
Concurrent – Allows application threads to run while it works.
Parallel – Multiple threads work on collecting at the same time.
A few of the traditional collectors are discussed below.


2.1 Copying Collector


A copying collector employs two or more storage areas to create and destroy objects. If it uses only two storage areas, they are called "semi-spaces." One semi-space (the "from-space") is used to create objects, and once it is full, the live objects are copied to the second semi-space (the "to-space"). Memory is compacted at no cost because only live objects are copied, and they are stored linearly. The semi-spaces now switch roles: new objects are created in the second one until it is full, at which point live objects are copied to the first. The semi-spaces exchange the from-space and to-space roles again and again. Dead objects are not freed explicitly; they're simply overwritten by new objects.


In a JVM, a copying collector is a stop-the-world collector. Nevertheless, it is extremely efficient because it traverses the object list and copies the objects in a single cycle, and thus simultaneously collects the garbage and compacts the heap. The time to collect the semi-space, the "pause duration," is directly proportional to the total size of live objects.


2.2 Mark and Compact Collector


In a mark-and-compact (more briefly, "mark-compact") collector, objects are created in a contiguous space, the heap. Once the free space falls below a threshold, the collector begins a marking phase, in which it traverses all objects from the "roots," marking each as either live or dead. After the marking phase ends, the compacting phase begins, in which the collector compacts the heap by copying live objects to a new contiguous area. In a JVM, a mark-compact collector is a stop-the-world collector. It suspends all of the application threads until collection is complete and the memory is reorganized, and then restarts them.


2.3 Mark and Sweep Collector


The marking phase of a mark-and-sweep ("mark-sweep") collector is the same as that of a mark-compact collector. When the marking phase is complete, the space each dead object occupies is added to a free list. Contiguous space occupied by dead objects is combined to make larger segments of free memory, but because a mark-sweep collector does not compact the heap, memory has a tendency to fragment over time.


2.4 Incremental Collector


An incremental collector divides the heap into small fixed-size blocks and allocates the data among them. It runs only for brief periods of time, leaving more processor time available for the application's use. It collects the garbage in only one block at a time, using the train algorithm [7]. The train algorithm organizes the blocks into train-cars and trains. In each collection cycle, the collector checks the cars and trains. If the train to which a car belongs contains only garbage, then the GC collects the entire train. If a car has references from other cars, then the GC copies objects to the respective cars, and, if the destination cars are full, it creates new cars as needed. Because the incremental collector pauses the application threads for only brief periods of time, the net effect is near-pauseless application execution.


2.5 Generational Garbage Collection


In most object-oriented languages, including the Java programming language, most objects have very short lifetimes, while a small percentage of them live much longer [6]. Of all newly allocated heap objects, 80-98 percent die within a few million instructions [6]. A large percentage of the surviving objects continue to survive for many collection cycles, however, and must be analyzed and copied at each cycle. Hence, the garbage collector spends most of its time analyzing and copying the same old objects repeatedly, needlessly, and expensively.


To avoid this repeated copying, a generational GC divides the heap into multiple areas, called generations, and segregates objects into these areas by age. In younger generations objects die more quickly and the GC collects them more frequently, while in older generations objects are collected less often. Once an object survives a few collection cycles, the GC moves it to an older generation, where it will be analyzed and copied less often. This generational copying reduces GC costs.


The GC may employ different collection algorithms in different generations. In younger generations, objects are ephemeral, and both space requirements and numbers of objects needing copying tend to be small, so a copying collector is extremely efficient. In older generations, objects tend to be more numerous and longer-lived, and copying costs make copying collectors (2.2) prohibitively expensive, hence mark-compact (2.1) or mark-sweep (2.3) collectors are preferred.


2.5.1 Generational Garbage Collection in Java Applications


Generational collection was introduced to the JVM in v1.2. The heap was divided into two generations, a young generation that used two semi-spaces and a copying collector, and an old generation that used a mark-compact or mark-sweep collector. It also offered an advanced collector, the concurrent, incremental mark-and-sweep ("concurrent inc-mark-sweep") collector, (see 2.6 Advanced Collectors in Java).


In the 1.3 and later JVMs [4], the heap is again divided into generations – by default, two generations. The young generation uses a copying collector, while the old generation uses a mark-compact collector. The 1.3 JVM also offers an incremental collector, introducing an optional intermediate generation between the young and old generations. Advanced collectors, like the concurrent inc-mark-sweep collector, are not available in 1.3 but are available in 1.2.2_07, and from the 1.4.1 JVM onwards.


More information about the collectors available from Sun can be found at: http://java.sun.com/docs/hotspot/index.html.


2.6 Advanced Collectors in Java Applications


The Java platform provides an advanced collector, the concurrent inc-mark-sweep collector. A concurrent collector [1] takes advantage of its dedicated thread to enable both garbage collection and object allocation/modification to happen at the same time. It uses external bitmaps [1] to manage older-generation memory, and "card tables" [1][2] to interact with the younger-generation collector. The bitmaps are used to scan the heap and mark live objects concurrently, in a "marking phase." Once the marking is complete, the unmarked objects are deallocated in a concurrent "sweeping phase." The collector does most of its work concurrently, suspending application execution only briefly.


Note: If "the rate of creation" of objects is too high, and the concurrent collector is not able to keep up with the concurrent collection, it falls back to the traditional mark-sweep collector.


2.7 The Sun Exact VM


The research for this paper was done using the 1.2.2_08 version of the JDK (Java 2 SDK, Standard Edition), on Solaris, with the advanced concurrent inc-mark-sweep collector.


The Exact VM [15] is a high-performance Java virtual machine developed by Sun Microsystems. It features high-performance, exact memory management. The memory system is separated from the rest of the VM by a well-defined GC interface. This interface allows various garbage collectors to be "plugged in". It also features a framework to employ generational collectors. More information about VMs for Solaris can be obtained from http://www.sun.com/solaris/java.
JVM垃圾回收器(GC)抖动(thrashing)是指JVM频繁进行垃圾回收,但每次回收释放的内存却非常有限,这通常会导致系统性能严重下降。在某些情况下,GC抖动可能会导致守护进程(daemon)超时甚至被操作系统终止(expiration),因为JVM长时间无法响应外部请求或心跳检测失败。 ### GC抖动的原因 1. **堆内存不足**:JVM堆内存配置过小,无法容纳程序运行时所需的对象,导致频繁GC。 2. **内存泄漏**:程序中存在未释放的无用对象引用,导致老年代(Old Generation)不断增长,最终触发Full GC。 3. **对象生命周期不合理**:大量短生命周期对象晋升到老年代(例如由于Survivor空间不足),导致老年代快速填满。 4. **GC算法选择不当**:不同的GC算法对不同类型的应用表现不同。例如,CMS(Concurrent Mark-Sweep)适用于低延迟场景,而G1(Garbage-First)则更适用于大堆内存管理。 5. **元空间(Metaspace)溢出**:类加载过多,导致元空间不断扩展,触发频繁的元空间GC。 ### GC抖动的表现 - **GC频率显著增加**:通过JVM监控工具(如JVisualVM、JConsole或Prometheus+Grafana)可以观察到GC事件频繁发生。 - **GC停顿时间增长**:尤其是Full GC的停顿时间显著增加,影响系统响应能力。 - **CPU使用率升高**:GC线程频繁运行,占用大量CPU资源。 - **守护进程超时**:例如Kubernetes中的Pod因健康检查失败而被重启,或ZooKeeper中的会话超时。 ### 解决方案 #### 1. 增加堆内存 适当增加JVM堆内存大小,尤其是老年代的大小,可以减少Full GC的频率。可以通过以下JVM参数调整堆大小: ```bash -Xms4g -Xmx8g -XX:NewRatio=2 ``` 上述配置将堆初始大小设为4GB,最大扩展到8GB,新生代与老年代的比例为1:2[^1]。 #### 2. 优化GC策略 根据应用类型选择合适的垃圾回收器: - **吞吐量优先**:Parallel Scavenge + Parallel Old - **低延迟优先**:G1 GC 或 ZGC 启用G1 GC的示例参数: ```bash -XX:+UseG1GC -XX:MaxGCPauseMillis=200 ``` 该配置启用G1垃圾回收器,并设置最大GC停顿时间为200毫秒[^1]。 #### 3. 检查内存泄漏 使用工具如Eclipse MAT(Memory Analyzer)分析堆转储(heap dump),查找未被释放的对象路径,尤其是`ClassLoader`、`ThreadLocal`、静态集合等常见泄漏源。 #### 4. 调整对象生命周期 减少短生命周期对象的晋升,可以通过调整Survivor空间大小或Tenuring阈值来实现: ```bash -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 ``` #### 5. 监控和调优元空间 限制元空间最大使用量,防止其无限制增长: ```bash -XX:MaxMetaspaceSize=512m ``` 同时,可以通过`jstat -gc`命令监控元空间使用情况。 #### 6. 避免守护进程超时 在容器环境中(如Kubernetes),应合理设置健康检查(liveness/readiness probes)的超时时间和重试次数,避免因短暂GC停顿导致进程被误杀。 --- ### 示例:G1 GC日志分析片段 ```log 2024-07-01T12:00:00.000+0800: [GC pause (G1 Evacuation Pause) (young), 0.0561234 secs] [Parallel Time: 52.3 ms, GC Workers: 8] [Marking Phase: 12.4 ms] [Evacuation: 35.2 ms] [Other: 4.7 ms] ``` 上述日志显示一次G1年轻代GC耗时约56毫秒,属于正常范围。若频繁出现超过200毫秒的GC事件,则应引起关注。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值