目录
1、什么是垃圾
简单的说就是内存中已经不再被使用的空间就是垃圾
2、如何判断是垃圾
1)引用计数法(已被淘汰的算法)
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
目前主流的java虚拟机都摒弃掉了这种算法,最主要的原因是它很难解决对象之间相互循环引用的问题。尽管该算法执行效率很高。
2)枚举根节点做可达性分析
所谓GC Roots或者说tracing GC的“根集合”就是一组必须活跃的引用,不是对象。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
3、哪些可以作为GC Root
GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象的引用作为GC roots,被GC roots引用的对象不被GC回收。可以作为GC Root 引用点的是:
1)JavaStack(栈帧中的局部变量区,也叫做局部变量表)中的引用的对象。
2)方法区中的类静态属性引用的对象。
3)方法区中常量引用的对象。
4)本地方法栈中JNI(Native方法)引用的对象。
4、GC常用的算法
1) 标记清除算法(标记-清除)
算法分为 “标记” 和 “清除” 两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,这个是最基础的收集算法,为什么说它是最基础的,因为后续的收集器都是基于这种思路并对其不足进行改进而得到的。
这种算法相对比较简单,在存活对象比较多的情况下效率比较高,它需要经历两次扫描,第一遍扫描是找到那些有用的,第二遍扫描是把那些没用的找出来清理掉。这里会有两个问题:
一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的空间碎片,如果空间碎片太多会导致以后的程序在运行过程中需要分配较大对象的时候,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
2)复制算法 (标记-复制-清除)
为了解决效率的问题,所以有了复制(Copying)算法的出现,它将可用的内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象赋值到另一块上面,然后再把已使用过的内存空间一次清理掉,这样使得每次都对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只需要移动堆顶的指针,这种适用于存活对象较少的情况,所以比较适合eden区,只扫描一次,效率提高了没有碎片,但是会造成空间的浪费,将内存缩小为原来的一半,未免太高了一点,而且移动复制对象,需要调整对象的引用。
3)标记压缩算法(标记-清理-压缩)
标记压缩就是把所有的东西整理的过程,清理的过程同时压缩到头上去。回收之前,有用的往前面走,将剩下的清理出来,但是标记压缩算法依然有它的问题,我们都是通过GC Roots 找到那些不可回收的对象,然后把不可回收的往前挪,这个时候我们需要扫描两次而且需要移动对象,第一遍扫描出有用的对象,第二遍进行移动,而且移动如果是多线程还需要进行同步,所以这个效率会低很多,但是它不会产生碎片,分配对象也不会产生内存减半。
- Mark-Sweep(标记清除): 标记为垃圾之后就给清理掉,别的空间还是固定的,效率还可以,就是容易产生碎片
- Copying(复制算法): 将内存一分为二,只使用一半,如果垃圾太多了,拷贝有用的到另外一边,剩下的清理就直接整个内存进行清理,效率比较高,内存利用率不高,该算法主要用于新生代回收
- Mark-Compact(标记压缩): 将所有的对象凑在一起,把垃圾全部清理走,接下来剩下的这个空间还是连续的,在里面分配任何内容的时候直接往里面分配就行了,效率较低,该算法用于老年代回收