什么样的对象会被GC?
超出作用域或者引用计数为空的对象,从gc root开始搜索找不到的对象,并且经历过一次标记、清理后,仍然没有复活的对象。
解释:
对象被引用一次,引用计数+1。但是若出现两个对象互相引用,没有其他对象引用他们,那么他们会造成资源泄露。所以只适合一些简单的引用场景。
GC roots”,或者说tracing GC的“根集合”,就是一组必须活跃的引用,不是对象。
例如说,这些引用可能包括:
- 所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。
- VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。 JNI
handles,包括global handles和local handles - (看情况)所有当前被加载的Java类
- (看情况)Java类的引用类型静态变量
- (看情况)Java类的运行时常量池里的引用类型常量(String或Class类型)
- (看情况)String常量池(StringTable)里的引用
Tracing GC的根本思路就是:给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活,其余对象(也就是没有被遍历到的)就自然被判定为死亡。注意再注意:tracing GC的本质是通过找出所有活对象来把其余空间认定为“无用”,而不是找出所有死掉的对象并回收它们占用的空间。
GC roots这组引用是tracing GC的起点。要实现语义正确的tracing GC,就必须要能完整枚举出所有的GC roots,否则就可能会漏扫描应该存活的对象,导致GC错误回收了这些被漏扫的活对象。
这就像任何递归定义的关系一样,如果只定义了递推项而不定义初始项的话,关系就无法成立——无从开始;而如果初始项定义漏了内容的话,递推出去也会漏内容。
那么分代式GC对GC roots的定义有什么影响呢?
答案是:分代式GC是一种部分收集(partial collection)的做法。在执行部分收集时,从GC堆的非收集部分指向收集部分的引用,也必须作为GC roots的一部分。
具体到分两代的分代式GC来说,如果第0代叫做young gen,第1代叫做old gen,那么如果有minor GC / young GC只收集young gen里的垃圾,则young gen属于“收集部分”,而old gen属于“非收集部分”,那么从old gen指向young gen的引用就必须作为minor GC / young GC的GC roots的一部分。
继续具体到HotSpot VM里的分两代式GC来说,除了old gen到young gen的引用之外,有些带有弱引用语义的结构,例如说记录所有当前被加载的类的SystemDictionary、记录字符串常量引用的StringTable等,在young GC时必须要作为strong GC roots,而在收集整堆的full GC时则不会被看作strong GC roots。
换句话说,young GC比full GC的GC roots还要更大一些。
什么时候进行GC?
1、直接调用System.gc()进行GC
2、老年代空间不足时触发full GC。
老年代只有在新生代对象转为大对象、大数组时才会出现空间不足的现象,当执行Full GC后空间依然不足时,则会抛出java.lang.OutOfMemoryError: Java heap space 。为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
3、Permanet Generation(又称方法区、持久代)空间满
Permanet Generation中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:java.lang.OutOfMemoryError: PermGen space
为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。
4、统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间
这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。
例如程序第一次触发MinorGC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,则执行Full GC。
当新生代采用PSGC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否大于6MB,如小于,则触发对旧生代的回收。
程序员不能具体控制时间,系统在不可预测的时间调用System.gc()函数的时候;当然可以通过调优,用NewRatio控制newObject和oldObject的比例,用MaxTenuringThreshold 控制进入oldObject的次数,使得oldObject 存储空间延迟达到full gc,从而使得计时器引发gc时间延迟OOM的时间延迟,以延长对象生存期。
eden满了minor gc,升到老年代的对象大于老年代剩余空间full
gc,或者小于时被HandlePromotionFailure参数强制full
gc;gc与非gc时间耗时超过了GCTimeRatio的限制引发OOM,调优诸如通过NewRatio控制新生代老年代比例,通过
MaxTenuringThreshold控制进入老年前生存次数等
GC 时做了什么?
1、删除不使用的对象,回收内存空间
2、停止其他线程执行、运行finalize。
3、新生代整理内存时,进行复制整理。使用了两个内存空间from survivor、to survivor。
4、老年代进行标记清理、标记清理后进行压缩,清楚碎片。
GC的策略有哪些?优劣势?适用于什么场景?
Heap划分为两大类New Generation(新生代),Old Generation(老年代)。Minor GC是针对New Generation的,而Full GC是则是对新生代和老年代一起做GC, 所以Full GC的开销会非常大,要净量避免。
1、New Generation GC策略:Minor GC
Serial GC。采用单线程方式,用Copying算法。New Generation会再次被划分成Eden Space和S0、S1,Copying算法所需要的额外内存空间,S0和S1又称为From Space和To Space。
Parallel Scavenge。将内存空间分段来使用多线程,Copying算法。
ParNew,比Parallel Scavenge多做了与Old Generation使用CMS GC一起发生时的特殊处理。
2、 Old Generation的GC策略
Serial GC。采用单线程方式,使用Mark-Sweep和Mark-Compact gc算法。
Parallel Mark-Sweep、Parallel Mark-Compact。同样也是把Old Generation空间进行划分成regions,只是粒度更细了。
CMS(Concurrent Mark-Sweep) GC。我承认这个GC我真的没怎么看懂,目的是为了实现并发,结果就造成具体实现太麻烦了。有兴趣的朋友去看书吧,文末我说了是哪本书。这里有个地方可以说一下,就是算法使用的还是Mark-Sweep,对于内存碎片的问题,CMS提供了一个内存碎片的整理功能,会在执行几次Full GC以后执行一次。
GC算法
1、引用计数法
一个对象,每被引用一次计数+1,只要对象不在任何地方被引用,那么他将被回收。但是存在一个问题,两个对象互相引用,但是都不被第三个对象引用,则会造成资源泄露。故该算法只能用于简单的引用场景。
2、跟踪收集法
把系统的整个引用想象成一个允许有环路的树形结构,但根节点只有一个,然后从根节点出发查看对象是否可达。但是这个需要程序暂停,来保证一次扫描的现场不变。
3、复制法Copying
需要两块内存空间,把扫描的可达对象复制到另一个空间,然后把原空间全部清除。适用于存活对象较少的情况。
4、标记清除法Mark-Sweep
把扫描到的可达对象都标记起来,然后把未标记的对象清除。该清除法会有内存碎片。适用于存活对象较多的情况。
5、标记清除压缩法Mark-Compact
把扫描到的可达对象都标记起来,然后把未标记的对象清除,然后进行压缩,让内存连续起来清除碎片。
摘至:
http://blog.csdn.net/cy609329119/article/details/51771953
http://blog.csdn.net/u014421556/article/details/52396706
https://www.zhihu.com/question/53613423/answer/135743258