前言
在java中,存在大量的设计概念,在概念中衍生出了很多理论基础,这类的学习对其他语言的深入理解有很大的帮助,因为本身java就是一个很大的知识库,于是,在飞机上,抽空下载了《深入理解JAVA虚拟机》,并学习了其中几张,发现了很多知识点在之前学习Golang时常常是没有提及的,也坚定了后续继续深入阅读java书籍的目标。
关于垃圾收集
垃圾收集主要还是针对堆内存中的对象,因为在栈中的对象,是随着线程的执行,会自动消逝,而堆中的对象,则是没有维护上级,对于C、C++来说,需要开发者自身通过free方法来释放,也衍生出了内存池的概念,而在更多编程语言中,例如最初的lisp、近期的golang,为了开发效率,不再提倡手动维护这类对象,而且交由应用程序运行时来管理对象的生命周期,这样也可以避免出现大量对象释放后,导致进程core的bug。
而在整体的垃圾收集算法中,核心还是,如何判断一个对象是否已死?最初的死亡定义是:当一个对象不再有另一个对象引用时,便认为其已死亡。这样也就衍生出引用计数算法,这类算法在最初的PHP有实现,包括在redis中也是有简易的实现。该算法原理简单,效率高,对于简单场景是十分适用,但是该算法并解决不了最困难的问题:循环引用。
所谓循环引用,就是2个对象互相引用,但是不在被其他对象引用,按理说,这类孤岛对象,算是僵尸对象,有人引用,但是不会再被使用,所以也应该被当做垃圾清理。而针对这类case,也衍生出了可达性分析算法,通过一个GC Roots的根对象来进行广度优先搜索,最终判断对象是否可达。这样也就可以解决循环引用的问题,而针对可达性分析算法,也衍生出各种各样的实现,其中也就包括golang的三色标记、php的n色标记、java的分代算法。
分代垃圾收集算法
分代收集的核心理论是建立2个假说:
1)弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
2)强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
因此,在java中,会分开新生代和老年代2块区域,针对新生代而言,大部分的对象都在垃圾收集时直接消亡,而剩余的对象逐步晋升为老年代中。这样就可以针对新生代和老年代进行分别垃圾清理。但中间还存在一个问题是当一个新生代对象被老年代引用时,原本只需要在新生代做垃圾收集算法,却必须要同时对老年代进行,这样完全没有达到分代的效果。于是,又引入了一个假说:
3)跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数。
即存在互相引用的2个对象,最终肯定是同时生存、死亡,所以,应该同时晋升为老年代,这样跨代的问题便消失了。