gc算法与内存分析(jvm)

参考资料:GC算法分类-https://zhuanlan.zhihu.com/p/41666328 《深入理解java虚拟机》第三章垃圾收集器与内存分配策略

 

一:GC分代--新生代跟老年代

gc活动空间为堆区域,堆按gc处理方式也可以分为新生代跟老年代。

对象内存分配跟使用的垃圾收集器有关,默认是在新生代的Eden区上。

复制算法在新生代中,复制多次后还在就会移到老年代去(在Hotspot中当复制用的那块survivor不够用,也会将这些对象分配到老年代),老年代采用标记-清除或标记-整理(压缩)算法

 

  1. MinorGC是发生在新生代中的GC,由于对象回收概率大,GC频繁且回收速度较快,采用的是复制算法。
  2. FullGC是发生在老年代的GC动作,当前主要采用的是标记-清除/整理算法

 

New一个对象的过程:一个对象被创建以后首先存放到新生代中的Eden内存中,如果存活期超几个Survivor之后就会被转移到老年代内存(Old Generation)中。

 

二:GC算法

 

  1. 1.        复制算法:

复制算法的思想是,将堆内存分为两块大小完全相同的内存(不一定是全部堆内存空间),每一次只用一块(活跃空间),另一块(空闲空间)闲置不用。当其中的活跃空间使用完后,就将活跃空间中还存活的对象复制到空闲空间里去,按照地址整齐排好序。之后清除活跃空间中所有的对象。然后两者交换使用职位,现在空闲空间成为了活跃空间,活跃空间成为了空闲空间。

 

IBM公司的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor [1] 。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)一块Survivor空间没有足够空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代

 

  1. 2.     标记-清除算法:

标记-清除法将垃圾回收分为两个阶段,即标记阶段和清除阶段。做法是,首先在标记阶段,遍历所有的根结点,将这些根结点的可达对象进行标记;而在清除阶段,遍历堆中的所有对象,对那些没有被标记的对象进行清除。(标记活着的对象)采用对象引用遍历,从一组GC Root对象开始(GC Root包括局部变量,静态变量及线程对象),沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根 对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,GC必须记住哪些对象可以到达,以便删除不可到达的对象;相当于没有指向根路径的对象。

 

1.首先是速度慢,因为标记-清除算法在标记阶段需要使用递归的方式从根结点出发,不断寻找可达的对象;而在清除阶段又需要遍历堆中的所有对象,查看其是否被标记,然后清除;并且其实在程序进行GC的时候,JVM中所有的Java程序都要进行暂停,俗称stop-the-world,后面会提到。

2.其次是其最大的缺点,使用这种算法进行清理而得的堆内存的空闲空间一般是不连续的,我们知道对象实例在堆中是随机存储的,所以在清理之后,会产生许多的内存碎片,如果这个时候来了一个很大的对象实例,尽管显示内存还足够,但是已经存不下这个大对象了,内存碎片太多会导致当程序需要为较大对象分配内存时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。再者,这种零散的碎片对于数组的分配也不是很方便。

 

  1. 3.     标记-压缩算法:

基于上一种算法的缺点,标记-压缩算法是对标记-清除算法的一种改良,它们的工作原理差不多,也是分为两个阶段:标记阶段和压缩阶段。标记阶段和标记-清除算法一样;而在压缩阶段的时候,它不是将所有的未标记的对象清除,而是将所有的标记对象压缩熬堆内存的一段(所有存活的对象向一端移动),然后清除边界以外的所有空间。

 

1.首先这种算法克服了标记-清除算法中会产生内存碎片的缺点,也解决了复制算法中内存减半使用的不足。

2.而其缺点则是速度也不是很快,不仅要遍历标记所有可达结点,还要一个个整理可达存活对象的地址,所以导致其效率不是很高。

 

三:对象是否存活

 

  • · 判断对象是否存活算法--可达性分析算法

可达性分析(Reachability Analysis)来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

 

  • · 对象是否死亡:

就算是通过可达性分析为不可到达的对象,也并非是“非死不可”。这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

 

如果对象需要执行finalize()方法,该对象将放置在一个F-Queue的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行。所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是以防一个一个对象执行finalize时过慢或者死循环导致F-Queue队列中其他的对象一直处于等待中。甚至导致整个内存回收系统崩溃。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的被回收了

 

  • · 哪些对象可以作为根对象(GC Roots)呢?

1. Java虚拟机栈中引用的对象;

2. 方法区中的类静态成员引用的对象(static修饰的);

3 .方法区中的常量引用的对象(主要是final修饰的);

4. 本地方法栈中JNI(Java Native Interface)引用的对象。

jdk1.8中已经没有永久代了(方法区只是一个概念,描述为堆中的逻辑部门,永久代或者元空间是它的实现),取而代之的是元空间

 

 

posted @ 2019-09-15 16:40  像灭霸一样看日出  阅读(421)  评论(0编辑  收藏  举报