垃圾回收
垃圾回收:
JVM中如何将对象视为垃圾??
1.那些内存需要回收?(对象是否可以被回收的两种经典算法: 引用计数法 和 可达性分析算法)
2.什么时候回收? (堆的新生代、老年代、永久代的垃圾回收时机,MinorGC 和 FullGC)
3.如何回收?(三种经典垃圾回收算法(标记清除算法、复制算法、标记整理算法)及分代收集算法 和 七种垃圾收集器)
在JVM提供垃圾回收机制,将内存空间不再使用的对象进行回收,
垃圾回收主要针对堆空间,垃圾回收操作需要消耗一定的资源和时间。
-
堆空间分区:
JVM中对堆空间进行分区:年轻代、老年代、永久代(1.8中无);
垃圾回收有两种类型:Minor GC 和 Full GC。
(1)Minor GC:对新生代进行回收,不会影响到年老代。因为新生代的 Java 对象大多死亡频繁,所以 Minor GC 非常频繁,一般在这里使用速度快、效率高的算法,使垃圾回收能尽快完成。
(2)Full GC:对年轻代和老年代同时作用的GC称之为Full GC;也称Major GC,对整个堆进行回收,包括新生代和老年代。由于Full GC需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数,导致Full GC的原因包括:老年代被写满、永久代(Perm)被写满和System.gc()被显式调用等。
java中4中引用:强引用、软引用、弱引用、虚引用;
a. **强引用:一个对象如果只有强引用,那么垃圾回收器绝不会回收它;**
new String();//强引用
即使在内存不足的情况下,JVM宁愿抛出内存不足的异常都不会回收它;
String s = new String(“”);
s = null;//会被回收
b. **软引用:如果一个对象只有软引用,如果内存充足的情况下,垃圾回收器不会回收它;如果内存不足的情况下,垃圾回收器才会回收该对象;**
java中提供SoftReference处理软引用;
c. **弱引用:弱引用所作用的对象,一旦发生垃圾回收,该引用所作用的对象会被立马回收掉;**
弱引用和软引用的对象比较:相对弱引用的生命力更强
java中提供WeakReference来处理弱引用;
d. **虚引用:虚引用无法左右当前对象的生命周期;**
java中提供PhantomReference来通知当前对象被回收掉;
a、b、c、d生命周期越来越长;
-
JVM如何识别java对象是垃圾??
-
引用计数法:
对对象添加引用标识,每对对象增加一个引用,引用标识+1,减少引用标识-1,当标识为0时,说明对象不存在引用,可以被垃圾回收掉;
引用计数无法处理相互引用的问题;
class A{ private B _b; } class B{ private A _a; } class Test{ public add(){ A a = new A(); //a引用计数为1 B b = new B(); //b引用计数为1 a._b = b; //b的引用计数为2 b._a = a; //a的引用计数为2 } } 当退出add()方法,局部变量a、b都失效
-
可达性分析:(根搜索法)
判断对象是否存活将堆中对象想象成一个树,从树根(Root)开始遍历所有的对象,能够达到的对象称之为可用对象(存活对象),不可达的对象称之为垃圾;
树根:GC Roots 一定可达;
java中哪一些对象一定是可达的呢???或者是java中哪一些可以作为GC的Roots呢
(1)虚拟机栈中的对象;
(2)本地方法栈中JNI(Native 方法)引用的对象;
(3)方法区中静态属性引用的对象;
(4)方法去中常量引用的对象;
-
垃圾回收算法
- 标记-清除(Mark-Sweep):
“标记-清除”算法是最基础的算法;
分为“标记”和“清除”两个阶段:
标记阶段:jvm会扫描所有的对象实例,通过根搜索算法,将活跃对象进行标记(对象是否可达,分析对象之间的引用关系,可达性分析);
清除阶段:jvm再一次扫描所有对象,将未标记的对象(不可达)进行清除,只有清除动作,不作任何的处理,这样导致的结果会存在很多的内存碎片。
- 它主要由两个缺点:
(1)效率问题,标记和清除过程的效率低,即标记阶段和清除阶段都要遍历一次整个堆内存空间;
(2)空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。内存碎片化,即对象连续的内存空间;
- 复制算法(Copying):
复制算法中,会将内存划分为两块相等大小的内存区域A/B,然后生成的数据会存放在A区,当A区剩余空间不足以存放下一个新创建的对象时,系统就会将A区中的有效对象全部复制到B区中,而且是连续存放的。然后直接清空A区中的所有对象。
解决了标记-清除缺点,但仍存在问题。
- 缺点:
(1)效率问题:将内存空间分为两大块,对内存空间只使用了一半,内存空间使用效率低;
(2)拷贝效率低;不适合用来生命周期比较长的对象;朝生夕灭(年轻代)使用此算法;
- 标记-整理算法:
标记-压缩算法是一种老年代的回收算法,它在标记-清除算法的基础上做了一些优化。
首先也需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。
- 分代回收算法:
分代收集算法就是目前虚拟机使用的回收算法,它解决了标记整理不适用于老年代的问题,将内存分为各个年代。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。
在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率搞,没有额外空间对它进行分配担保,所以只能使用标记清除或者标记整理算法。
内存担保策略 --> 老年代给的(Tenured)
垃圾回收过程
年轻代分为Eden区和survivor区(两块儿:from和to),且Eden:from:to==8:1:1。
jvm内存结构
1)新产生的对象优先分配在Eden区(除非配置了-XX:PretenureSizeThreshold,大于该值的对象会直接进入年老代);
2)当Eden区满了或放不下了,Eden区进行一次Minor GC,清空Eden区,这时候会将Eden区中存活的对象会复制到from区。
3)之后产生的对象继续分配在Eden区,当Eden区又满了或放不下了,这时候将会把Eden区和from区存活下来的对象复制到to区(同理,如果存活下来的对象to区都放不下,则这些存活下来的对象全部进入年老代),之后回收掉Eden区和from区的所有内存。
4)如上这样,会有很多对象会被复制很多次(每复制一次,对象的年龄就+1),默认情况下,当对象被复制了15次(这个次数可以通过:-XX:MaxTenuringThreshold来配置),就会进入年老代了。
5)当年老代满了或者存放不下将要进入年老代的存活对象的时候,就会发生一次Full GC(这个是我们最需要减少的,因为耗时很严重)。