什么是垃圾
垃圾就是在程序运行中没有任何指向的对象,这个对象就是需要被回收的垃圾
Java中的垃圾回收
Java不像C语言那样,需要自己手动垃圾回收,而是将运行时产生的垃圾交给JVM处理。在Java的内存模型中主要分为堆、方法区、虚拟机栈、本地方法栈、程序计数器。垃圾回收器主要负责对堆中产生的垃圾对象进行回收。
Java中堆内存一般分为新生代和老年代,根据各个年代的特点采用不同的收集算法。据调查统计新生代中的对象只会在一次垃圾回收后存活10%(少量存活),因此使用拷贝算法,老年代(对象存活率高)使用”标记清除”算法
垃圾搜索算法
引用计数算法(Reference Counting)
给对象添加一个引用计数器,每当一个地方引用时,计数器加1;当引用失效时,计数器减1。计数器为0时,即可被回收
根搜索算法(GC Root Tracing)
过一系列的名为GC Root的对象作为起始点,从这些节点开始向下搜索,搜索所有走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时(用图论来说就是GC Root到这个对象不可达时),证明该对象是可以被回收的。
Java采用了根搜索算法
在Java中哪些对象可以成为GC Root?
- 虚拟机栈(栈帧中的本地变量表)中的引用对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用对象
- 本地方法栈中JNI(即Native方法)的引用对象
垃圾清除算法
标记清除算法(Mark-Sweep)
最基础的垃圾处理器算法,分为“标记”和“清除”两个阶段。先标记需要回收的对象,标记完成后,统一回收掉所有被标记的对象。js通常采用此算法
缺点:
- 效率低,标记和清除的效率都不高
- 清除后会产生大量的不连续内存碎片,可能会导致在程序需要为较大对象分配内存时无法找到足够连续的内存,不得不提前触发垃圾回收动作。
拷贝算法(Copying)
将内存容量分成大小相等的两块,每次只使用其中一块,当一块用完时,将还存活的对象复制到另一块去,然后把之前使用满的那块空间一次性清理掉,如此反复
缺点: 内存空间浪费大,每次只能使用当前能够使用内存空间的一半;当对象存活率较高时,需要有大量的复制操作,效率低
标记整理算法(Mark-Compact)
标记整理是在标记-清除上改进得来,前面说到标记-清除内存碎片的问题,在标记-整理中有解决。同样有标记阶段,标记出所有需要回收的对象,但是不会直接清理,而是将存活的对象向一端移动,在移动过程中清理掉可回收对象
分代收集算法
- 堆内存被划分为两块,一块是年轻代,另一块是老年代
- 年轻代又分为Eden和Survivor他俩空间大小比例默认为8:2
- 幸存区又分为s0和s1这两个空间大小是一模一样的,就是一对双胞胎,他俩是1:1比例
回收过程
- 新生成的对象首先放到Eden区,当Eden区满了会触发Minor GC
- 第一步GC活下来的对象,会被移动到Survivor区中的S0区,S0区满了之后会触发Minor GC,S0区存活下来的对象会被移动到S1区,S0区空闲。S1满了之后再GC,存活下来的再次移动到S0区,S1区空闲,这样反反复复GC,每GC一次,对象的年龄就涨一岁,达到某个值(默认15)后,就会进入老年代
- 在发生一次Minor Gc后(前提条件),老年代可能会出现Major GC
Full GC触发条件
- 手动调用System.gc,会不断的执行Full GC
- 老年代空间不足/满了
- 方法区空间不足/满了