Garbage Collection
垃圾收集需要完成的三件事
1、哪些内存需要回收
2、什么时候回收
3、如何回收
java内存运行时区域的部分中,程序计数器,虚拟机栈,本地方法栈三个区域随线程而生而灭,栈中的栈侦随方法的进出而有条不紊的执行出栈入栈操作。每个栈中分配多少内存基本上在类结构确定下来时就已知的,因此,这些部分是内存分配和回收都具确定性。
java堆和方法区不一样。一接口实现,一方法的分支等需要的内存可能不一样,我们只有在运行时才知道要创建哪些对象,这部分都是动态的,GC关注的就是这部分内存.
判断对象已死
1、引用计数算法
引用计数的实现简单,效率也高,但是java没有采用这种方式,原因是很难解决对象之间的相互循环引用的问题
-verbose:gc -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:SurvivorRatio=8
public class ReferenceCountingGC {
public Object instace = null;
private static final int _1MB = 1024*1024;
private byte [] bigSize = new byte[2* _1MB];
public static void main(String[] args) {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA. instace = objB;
objB. instace = objA;
objA = null;
objB = null;
System. gc();
}
}
2、根搜索算法
主流商用程序语言中,都使用根搜索算法。基本思路是通过一系列名为“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连,则为不可用。
java中可作为GC Roots的对象包括
1.虚拟机栈中的引用的对象
2.方法区中的类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI(即一般说的native方法)的引用的对象
3.引用概念的扩充
从JDK1.2之后,java对引用的概念进行了扩充,将引用分为:
强引用(Strong Reference):
软引用(Soft Reference):当内存溢出异常之前,会把这些对象列进回收范围之中
弱引用(Weak Reference): 被弱引用关联的对象只能生存到下一次垃圾收集。
虚引用(Phantom Reference): 为一个对象设置虚引用的关联的唯一目的就是希望能在对象被收集器回收时收到一个系统的通知
死亡过程
当被搜索到对象不可达GC Roots的时候,该对象会进入两次的标记过程。
判定它为不可达之后,标记一次。
然后筛选有必要执行finalize方法的对象,条件为该对象已经覆盖finalize()方法,或者finalize方法还未被执行
放进F-Queue队列中,之后由一条虚拟机自动建立,低优先级的Finalizer线程去执行(触发)。但并不承诺会等待它运行结束(不然这个队列都会在等它)。
在进行第二次标记之前,finalize方法是最后一次对象逃脱死亡的机会,只要重新与引用链上的任何一个对象建立关联,那在第二次标记的时候它将被移除集合。但是finalize只被系统调用一次,所以对象面临下一次回收,它的finalize不会再执行了
-verbose:gc -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:SurvivorRatio=8
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
private static final int _1MB = 1024*1024;
private byte [] bigSize = new byte[2* _1MB];
public void isAlive() {
System.out.println("yes, i am still alive" );
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!" );
FinalizeEscapeGC. SAVE_HOOK = this ;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEscapeGC();
SAVE_HOOK = null ;
System. gc();
Thread. sleep(500);
if(SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System. out.println("no i am dead" );
}
SAVE_HOOK = null ;
System.gc();
Thread.sleep(500);
if(SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no i am dead" );
}
}
}
finalize其实是java刚诞生时为了让c++程序员更容易接受的一种妥协。
缺点:代价高,不确定性大,无法保证各个对象的调用顺序。关于那它做外部资源的关闭,也是没必要的,因为try-finally就可以更好更及时做到。
结论:把finalize这东西给忘记吧
java虚拟机规范中说方法区(HotSpat虚拟机中的永久代)是可以不要求在方法区实现垃圾收集的,因为在这里收集垃圾“性价比”不高,在堆中,尤其是新生代,一次垃圾收集可以回收70-95%的空间,而永久代效率远远低于此。
永久代垃圾收集主要两部分:
回收废弃常量:和在堆中回收对象很像。
无用的类:1.该类的所有实例都被回收 2.加载该类的ClassLoader已经被回收 3.该类对应的java.lang.Class对象(Test.class)没有下载任何地方被引用。如果这个类符合这三个条件,jvm就会在方法区垃圾回收的时候对类进行卸载(就是指清除类信息,类生命周期的结束,涉及到类的生命周期:加载、连接、初始化、使用、卸载)
在大量使用反射、动态代理、CGLib等bytecode框架的场景,以及动态生成jsp和OSGI这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,保证永久代不会溢出
垃圾收集算法
标记-清除算法:只是标记,然后清除。最基础的算法,但是两个缺点:效率问题,空间问题(很多碎片,会导致找不到连续空间之后,会不得不提前出发另一次垃圾收集动作)
复制算法:大小相等的两块内存区域,当一块用完之后,将活着的对象复制到另外一块上面,然后一次性清理旧块
现在的商业虚拟机都采用这种收集算法来回收新生代,但是IBM的专门研究表明,新生代中的对象98%是朝生夕死的,所有没必要1:1的空间划分。
而是将内存分为一块较大Eden空间和两块Survivor空间比例8:1,回收时,将Eden和其中一块survivor里面的存活对象拷贝到另外一块Survivor。所有要保证存活对象不能多于10%,如果多了,需要依赖其他内存(老年代)进行分配担保。
有个小缺点:在对象存活率较高的情况下,执行复制将会使效率变低
标记-整理算法:在新生代可以用复制算法,但是在老年代就不能直接拿来用。在标记之后,让所有存活的对象往一端移动,然后直接清理掉边界以外的内存。
分代收集算法:当前的商业虚拟机的垃圾收集都采用“分代收集(Generation Collection)”算法,根据对象存活的时间不同将内存划分为几块。一般分为新生代和老年代。在新生代中,每收集一次发现有大量对象死亡,选复用算法。而老年代中对象存活率高、没有额外空间给担保,就必须使用“标记-清理”或“标记-整理”来进行回收。
java虚拟机规范中对垃圾收集器如何实现没有任何规定,所以不同厂商、版本的虚拟机锁提供的收集器都可能差别很大。一般会提供参数让我妈选择各个年代所使用的收集器。
基于HotSpot虚拟机1.6版所包含的收集器
* "Serial" is a stop-the-world, copying collector which uses a single GC thread.
* "ParNew" is a stop-the-world, copying collector which uses multiple GC threads. It differs from "Parallel Scavenge" in that it has enhancements that make it usable with CMS. For example, "ParNew" does the synchronization needed so that it can run during the concurrent phases of CMS.
* "Parallel Scavenge" is a stop-the-world, copying collector which uses multiple GC threads.
* "Serial Old" is a stop-the-world, mark-sweep-compact collector that uses a single GC thread.
* "CMS" is a mostly concurrent, low-pause collector.
* "Parallel Old" is a compacting collector that uses multiple GC threads.
Serial收集器
1、jdk1.3之前新生代唯一的选择。
2、单线程收集器。
3、适合运行在Client模式下
ParNew收集器
1、是Serial的多线程版本,意思就是说除了多线程进行垃圾收集那一部分,其他的都和Serial一样(代码共用)
2、默认开启的收集线程数与CPU的数量相同,在CPU非常多的情况下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数
Parallel Scavenge收集器
1、关注点和上面两者不一样,关注于吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。最高效的利用CPU时间,适合在后台运算而不需要太多的交互任务(关注于尽可能缩短垃圾收集时用户线程的停顿时间,适合用户的交互)
2、无法与CMS配合使用
3、两个参数精确控制吞吐量:控制最大垃圾收集停顿时间-XX:MaxGCPauseMillis;直接设置吞吐量大小-XX:GCTimeRatio。
停顿时间参数的缩短是以牺牲吞吐量和新生代空间来换取的:减少新生代,提高收集的频繁。这样空间小了,吞吐量小了,提顿时间也小了
4、开关参数:-XX:+UseAdaptive(与上述两者重要的区别)自适应调节,打开后就不需要手动设置新生代大小、Eden与Survivor比例(-XX:SurvivorRatio)、晋升老年代对象的年龄(-XX:PretenureSizeThreshold)
http://docs.oracle.com/javase/1.5.0/docs/guide/vm/gc-ergonomics.html
Serial Old
Parallel Old
CMS(Concurrent Mark Sweep)
CMS收集器是以获取最短回收停顿时间为目标的收集器。
运作过程比较复杂 4步:
1、初始标记
仅仅标记GC Roots能直接关联到的对象,时间很短,可见下面标红
2、并发标记
GC Roots Tracing过程(根搜索算法;最耗时的过程,不过已经和用户线程并发了)
3、重新标记
修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分
4、并发清除
CMS收集器运作日志
0.104: [GC [1 CMS-initial-mark: 32073K(57344K)] 32976K(64768K), 0.0005721 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Total time for which application threads were stopped: 0.0006903 seconds
0.104: [CMS-concurrent-mark-start]
0.105: [GC 0.105: [ParNew: 7424K->768K(7424K), 0.0080862 secs] 39497K->39446K(64768K), 0.0081270 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Total time for which application threads were stopped: 0.0082087 seconds
0.115: [GC 0.115: [ParNew: 7424K->768K(7424K), 0.0080369 secs] 46102K->46062K(64768K), 0.0080786 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Total time for which application threads were stopped: 0.0081630 seconds
0.124: [GC 0.124: [ParNew: 7424K->768K(7424K), 0.0081382 secs] 52718K->52680K(64768K), 0.0081783 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
Total time for which application threads were stopped: 0.0082574 seconds
0.139: [CMS-concurrent-mark: 0.010/0.034 secs] [Times: user=0.05 sys=0.00, real=0.03 secs]
0.139: [CMS-concurrent-preclean-start]
0.139: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.139: [CMS-concurrent-abortable-preclean-start]
0.139: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.139: [GC[YG occupancy: 7198 K (7424 K)]0.139: [Rescan (parallel) , 0.0004863 secs]0.139: [weak refs processing, 0.0000033 secs] [1 CMS-remark: 51912K(57344K)] 59110K(64768K), 0.0005244 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Total time for which application threads were stopped: 0.0005979 seconds
0.139: [CMS-concurrent-sweep-start]
0.141: [CMS-concurrent-sweep: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.141: [CMS-concurrent-reset-start]
0.141: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
缺点:
1、对CPU资源非常敏感,CMS默认启动的回收线程是(CPU数量+3)/4
2、无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC 的产生
就是在并发清理阶段,用户线程还在运行,就会产生新的垃圾,只能是下一次GC的时候清理,这部分叫"浮动垃圾"。
所以这部分的浮动垃圾必须要有空间留着,如果剩余的老年代空间无法满足,就会报concurrent mode failure。
0.125: [GC 0.125: [ParNew: 7424K->768K(7424K), 0.0079396 secs] 52718K->52680K(64768K), 0.0079763 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Total time for which application threads were stopped: 0.0080435 seconds
(concurrent mode failure): 51912K->148K(57344K), 0.0119054 secs] 58977K->148K(64768K), [CMS Perm : 2106K->2105K(12288K)], 0.0119676 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Total time for which application threads were stopped: 0.0120395 seconds
这个时候,虚拟机将启动后备预案,临时启动Serial Old收集器进行一次full gc 这个gc时间比起其他时间相当长
3、碎片过多
当找不到足够大的连续空间的时候,不得不触发Full GC。可以试着-XX:+UseCMSCompactAtFullCollection来使Full GC之后进行一次碎片整理(这个无法并发,还得stop),-XX:+CMSFullGCsBeforeCompaction设置在每执行多少次Full GC之后进行一次碎片整理
DefNew是HotSpot的GC框架里serial GC里的young gen空间。只对它的收集就是这个上下文里的minor GC或者说young GC。
G1收集器
和CMS相比显著的改进
1、因为是基于“标记-整理”算法,不会产生空间碎片
2、精确控制停顿(它可以实现基本不牺牲吞吐量的情况下完成低停顿的内存回收),能让使用者明确指定在一个长度M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
它将整个java堆划分为多个大小固定的独立区域(Region),根据这些区域的垃圾程度,维护优先列表来优先回收垃圾最多的区域
分享到:
相关推荐
Java垃圾回收GC机制 Java垃圾回收GC机制是Java虚拟机(JVM)中的一种自动内存管理机制,它主要是为了解决Java程序中的内存问题。GC机制的出现使得Java程序员不再需要手动管理内存,提高了开发效率和程序的稳定性。 ...
Java垃圾回收GC.xmind
NETC#栈堆垃圾回收GC借鉴.pdf
在.NET Framework环境中,虽然开发人员不必直接管理内存和处理垃圾回收(Garbage Collection, GC),但了解这些底层机制对于优化应用程序至关重要。这不仅能提升程序的性能,还能帮助开发者更好地理解程序变量的行为...
垃圾回收(GC,Garbage Collection)是编程语言中用于自动管理内存的重要机制,尤其是在像Java、Python这样的语言中。GC的主要任务是识别并回收不再使用的内存块,避免内存泄漏,保持系统的高效运行。 1. 什么是...
C#的垃圾回收机制(GC)是.NET框架中一个至关重要的特性,它负责自动管理和释放内存,从而减轻程序员的工作负担。GC的主要目标是回收不再使用的对象所占用的内存,以防止内存泄漏。在这个机制下,程序员无需手动管理...
Java虚拟机(JVM)的垃圾回收(GC)机制是Java程序高效运行的关键部分,它自动管理内存,释放不再使用的对象以避免内存泄漏。本文主要探讨JVM堆内存的结构和GC的工作原理,以及如何进行性能调优。 JVM堆是Java应用...
【C#垃圾回收机制GC】深入解析 垃圾回收(Garbage Collection, GC)是现代编程语言中用于自动管理内存的一种机制。它的核心思想是通过跟踪和回收那些不再被程序引用的对象,以避免内存泄漏和提高内存利用率。在.NET...
Java垃圾回收(GC)机制是Java语言管理内存的自动化机制,它能够自动释放不再使用的内存空间,从而避免内存泄漏和程序崩溃等问题。在介绍Java GC机制之前,我们首先要了解垃圾回收的目的和意义。在任何程序中,内存...
### Java GC垃圾回收调优指南 #### 概述 在Java开发过程中,垃圾回收(Garbage Collection, GC)是管理内存资源的关键技术之一。合理的GC配置可以显著提高应用程序的性能和稳定性。本指南旨在帮助开发者深入理解...
Java垃圾回收(GC)机制是Java编程语言中的一个重要特性,它自动管理程序的内存空间,负责识别并清除不再使用的对象,以防止内存泄漏。本文将深入探讨Java GC的工作原理、类型、过程以及优化策略。 一、Java垃圾...
Java垃圾回收(Garbage Collection, 简称GC)是JVM(Java Virtual Machine)管理内存的重要机制。在Java应用程序运行过程中,如果发现并回收不再使用的对象,可以防止内存泄漏,确保程序稳定运行。当我们遇到应用...
GC 垃圾回收机制 GC 垃圾回收机制是 .NET 中一个复杂的机制,对于提高程序性能和减少内存泄露非常重要。CLR 垃圾回收器根据所占空间大小划分对象,大对象和小对象的处理方式有很大区别。 大对象堆和垃圾回收 在 ...
GC总结
迷你垃圾回收器(Mini GC for C)是一种针对C语言实现的轻量级内存管理工具,主要功能是自动回收不再使用的内存,以防止内存泄漏。在C语言中,程序员需要手动管理内存分配和释放,而垃圾回收器则可以自动化这一过程...
垃圾回收(Garbage Collection, GC)是指在程序运行过程中自动检测并释放那些不再被使用的对象所占用的内存空间的过程。这一过程通常是由语言的运行环境(如Java虚拟机)自动完成的,而不需要程序员手动干预。 **2....
垃圾回收(Garbage Collection, GC)是编程语言中用于自动管理内存的重要机制,尤其是在像Java、C#等语言中。这个技术示例旨在深入探讨垃圾回收的原理、工作流程以及在实际开发中的应用。 垃圾回收的基本目标是识别...
JVM GC垃圾回收.pdfJVM GC垃圾回收.pdfJVM GC垃圾回收.pdfJVM GC垃圾回收.pdfJVM GC垃圾回收.pdf