Java-GC工作原理
参考:https://segmentfault.com/a/1190000002579346 《深入理解Java虚拟机JVM高级特性》
-
何为GC
垃圾回收机制是由垃圾收集器Garbage Collection GC来实现的,GC是后台的守护进程。它的特别之处是它是一个低优先级进程,但是可以根据内存的使用情况动态的调整他的优先级。因此,它是在内存中低到一定限度时才会自动运行,从而实现对内存的回收。这就是垃圾回收的时间不确定的原因。
-
为何要这样设计:
因为GC也是进程,也要消耗CPU等资源,如果GC执行过于频繁会对java的程序的执行产生较大的影响(java解释器本来就不快),因此JVM的设计者们选着了不定期的gc.在垃圾回收器回收之前,还需要一些清理工作,因为垃圾回收gc只能回收通过new关键字申请分配的,还有一些本地方法,这部分“特殊的内存”如果不手动释放,就会导致内存泄露,gc是无法回收这部分内存的,所以需要在finalize中用本地方法如free操作等,再使用gc方法,显示的gc方法是System.gc()
-
何为垃圾
java中那些不可达的对象就会变成垃圾,那么什么叫做不可达?其实就是没有办法引用到该对象,主要有以下情况使对象变为垃圾:
1.对于非线程的对象来说,所有的活动线程都不能访问该对象,那么该对象就会变成垃圾。
2.对线程对象来说,满足上面的条件,且线程未启动或者停止。如下代码:
//1.改变对象的引用,如置为null或者指向其他对象
Object o1 =new Object();
Object o2 = new Object();
o1=o2;//o1变为垃圾
o1=null;//o1变为垃圾
o2=null;//o2变为垃圾
//2.超出作用域
if(i==0){
Object o3=new Object();
}//括号结束后o3将无法被引用,变为垃圾
//3.类嵌套导致未完全释放
class A{
A a;
}
A a1 = new A();//分配了一个空间
a1.a = new A();//又分配了一个空间
System.out.println(a1);
System.out.println( a1.a);
a1=null;//将会产生两个垃圾
//4.线程中的垃圾
class B implements Runnable{
void run(){
//....
}
//main
B b = new B();
b.start();
b=null;//等线程执行完后b才被认定为垃圾
}
}
-
何时发生gc
1.在新生代有一个Eden区和两个survivor区,首先将对象放入Eden区,如果空间不足就向其中一个survivor区上放,如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中,然后清空Eden和之前的那个survivor区的内存里去.
2.大对象以及长期存活的对象直接进入老年区.
3.当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析,如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次Full GC以尽可能的获得老年区的空间.
-
Minor GC和FullGC的区别?
新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也非常快!
老年代GC(MajorGC/FullGC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC,Major GC的速度一般会比Minor GC慢10倍以上。
-
空间分配担保
在发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次Full GC,如果小于,则查看HandlePromotionFailure设置是否允许担保失败;如果允许,则只会进行Minor GC;如果不允许,则也要进行一次Full GC.
-
垃圾收集算法
1.标记-清除算法
最基础的收集算法是“标记-清除”(Mark-Sweep)算法,如同它的名字一样,算法分为“标记”和“清除”两个阶段。
1).首先标记出所有需要回收的对象 2).在标记完成后统一回收所有被标记的对象。
缺点:
效率问题:标记和清除两个过程的效率都不高 空间问题:标记清除之后产生大量不连续的内存碎片,空间碎片太多可能会导致以后程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
2.复制算法(为了解决效率问题)
将可用内存按容量大小划分为大小相等的两块,每次只使用其中的一块。当一块内存使用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。
缺点:
将内存缩小为了原来的一半。
现代的商业虚拟机都采用这种收集算法来回收新生代,IBM公司的专门研究表明,新生代中对象98%对象是“朝生夕死”的,所以不需要按照1:1的比例来划分内存空间,而是将内存分为较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。HotSpot虚拟机中默认Eden和Survivor的大小比例是8:1。
3.标记-整理算法
复制收集算法在对象存活率较高时,就要进行较多的复制操作,效率就会变低。 根据老年代的特点,提出了”标记-整理“算法。 标记过程仍然与”标记-清除“算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
4.分代收集算法
一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法。
在老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须采用 “标记-清除” 或 “标记-整理” 算法来进行回收。
总结:Java虚拟机中如何做的?
java中做法,我们称为 “自适应” 的垃圾回收器,都是“自适应,分代的,停止-复制,标记-清扫”式垃圾回收器,它会根据不同的环境和需要选择不同的处理方式。
浅谈引用
强引用: 强引用就是指在程序代码之中普遍存在的,类似 “Object obj = new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
软引用: 软引用是用来描述一些还有用,但并不是必须的对象,对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进会搜狐范围之中进行第二次回收,如果这次回收还是没有足够的内存,才会抛出内存溢出异常。在JDK1.2后,提供了SoftReference类来实现软引用。
弱引用: 弱引用也是用来藐视非必须对象的,但是他的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象,在JDK1.2之后,提供了WeakReference类来实现弱引用。
虚引用: 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收收到一个系统通知,JDK1.2之后,提供了PhantomReference类来实现虚引用。
如何判断一个类是可回收的?
判定一个常量是否是“废弃常量”比较简单,而判定一个类是否是“无用的类”的条件则相对苛刻许多,类需要同时满足以下3个条件才能算是“无用的类”:
1.该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
2.加载该类的ClassLoader已经被回收;
3.该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法。
注:此处说的是可以,而不是和对象一样,不使用就必然会回收。