GC
(Garbage Collection
)很大程度上帮助Java
程序员解决了内存释放的问题,有了GC
,就不需要再手动的去控制内存的释放。
在阅读之前需要了解的相关概念:
Java
堆内存分为新生代和老年代,新生代中又分为1
个Eden
区域 和2
个Survivor
区域。
什么是GC(Garbage Collection)
GC
垃圾收集,Java
提供的GC
可以自动监测对象是否超过作用域从而达到自动回收内存的目的。
每个程序员都遇到过内存溢出的情况,程序运行时,内存空间是有限的,那么如何及时的把不再使用的对象清除将内存释放出来,这就是GC要做的事。
需要GC
的内存区域
JVM
中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 JAVA
堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的。
注意:
对于Java8
,HotSpots
取消了永久代,那么是不是也就没有方法区了呢?当然不是,方法区是一个规范,规范没变,它就一直在。那么取代永久代的就是元空间。它可永久代有什么不同的?存储位置不同,永久代物理是是堆的一部分,和新生代,老年代地址是连续的,而元空间属于本地内存;存储内容不同,元空间存储类的元信息,静态变量和常量池等并入堆中。相当于永久代的数据被分到了堆和元空间中。
GC
的对象
当一个对象到GC Roots
不可达时,在下一个垃圾回收周期中尝试回收该对象,如果对象重写了finalize()
,并在这个方法中成功自救(将自身赋予某个引用),那么这个对象不会被回收。但如果这个对象没有重写finalize()
方法或已执行过这个方法,该对象将会被回收。
需要进行回收的对象就是已经没有存活的对象,判断一个对象是否存活常用的有两种办法:引用计数算法和可达性分析算法。
- 引用计数算法:
每个对象有一个引用计数属性,新增一个引用时计数加1
,引用释放时计数减1
,计数为0
时可以回收。此方法简单,无法解决对象相互循环引用的问题。
- 可达性分析算法(Reachability Analysis):
从GC Roots
开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots
没有任何引用链相连时,则证明此对象是不可用的,不可达对象。
在Java语言中,GC Roots
包括:
+ 虚拟机栈中引用的对象;
+ 方法区中类静态属性实体引用的对象;
+ 方法区中常量引用的对象;
+ 本地方法栈中JNI
引用的对象。
什么时候触发GC
- 程序调用
System.gc
时,但不是必然执行
- 系统自身来决定
GC
触发的时机(根据Eden
区和From Space
区的内存大小来决定。当内存大小不足时,则会启动GC
线程并停止应用线程)
GC
又分为Minor GC
和Full GC
(也称为Major GC
)Minor GC
触发条件:当Eden
区满时,触发Minor GC
。Full GC
触发条件:
+ 调用System.gc
时,系统建议执行Full GC
,但是不必然执行
+ 老年代空间不足
+ 方法去空间不足
+ 通过Minor GC
后进入老年代的平均大小大于老年代的可用内存
+ 由Eden
区、From Space
区向To Space
区复制时,对象大小大于To Space
可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
GC
做了什么事
主要做了清理对象,整理内存的工作。Java
堆分为新生代和老年代,采用了不同的回收方式。
GC常用算法
GC
常用算法有:标记-清除算法,标记-压缩算法,复制算法,分代收集算法。
目前主流的JVM
(HotSpot
)采用的是分代收集算法。
标记-清除算法(Mark-Sweep
)
首先标记出所有需要回收的对象,标记完成后回收所有被标记的对象。不足主要体现在效率和空间,从效率的角度讲,标记和清除效率都不高;从空间的角度讲,标记清除后会产生大量不连续的内存碎片, 内存碎片太多可能会导致需要分配较大对象时,无法找到足够的连续内存而提前触发一次垃圾收集动作。
从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象,如果活着,就标记。只有全部标记完毕的时候,清理动作才开始。在清理的时候,没有标记的对象将会被释放,不会发生任何动作。但是剩下的堆空间是不连续的,垃圾回收器要是希望得到连续空间的话,就得重新整理剩下的对象。
优点:标记—清除算法中每个活着的对象的引用只需要找到一个即可,找到一个就可以判断它为活的。此外,更重要的是,这个算法并不移动对象的位置。
缺点:它的缺点就是效率比较低(递归与全堆对象遍历)。每个活着的对象都要在标记阶段遍