JVM学习笔记之一 -- GC的基本原理

一、GC概述

    GC的思想早在java诞生之前就已经出现,垃圾回收是内存“自动化”的一部分。

    判断对象是否可以回收有两种策略。第一种是引用计数法,方法顾名思义,但它的问题是不能解决循环引用的情况,循环引用如下所示:

public class Test{
	public Onject reference;

	public static void main(String[] args) {
		Test test1 = new Test();
		Test test2 = new Test();

		test1.reference = test2;
		test2.reference = test1;

		test1 = null;
		test2 = null;
	}
}

    主流jvm使用可达性分析算法,其核心思路是从一系列GC Root对象出发向下搜索,搜索的路径称为引用链。GC Root对象包括:

  • 虚拟机栈中的引用对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象。

    GC的区域是堆和方法区(永久代),这两个区域都是线程共享的。首先先说明清理方法区。方法区中主要存放的是类加载后的信息、常量、静态属性等。GC会清理没用的类和常量。我们知道java中String是存放在常量池中的,那么如果没有引用指向常量池中的某个字符串,gc就会将其清理。清理类条件相对苛刻,需要满足才可以被回收,并不是一定会回收,还跟jvm配置的相关参数有关:

  • 没有该类的实例
  • 加载该类的ClassLoader被回收
  • 类的java.lang.Class对象没被引用

    Gc的主要区域是java堆,本文以及后面博文中会对堆的回收做详细说明。

 二、GC算法

    这里会记录GC算法的思想,算法实现超出能力范围

1.标记-清除算法

    顾名思义,算法分为“标记”和“清除”两个阶段,它是GC中最基础的算法。但有明显缺陷:第一效率低;第二产生内存碎片,当程序运行需要分配大对象时,无法找到足够的连续内存,进而触发另一次GC操作。

    之前脑子故障一直想不通为什么不边标记边删除,- -想出答案时我简直“佩服”我的智商,,因为只能按照引用链标记可达对象。。。另外看了很多概念性的东西,我在想什么样的对象是不可达的。很多例子中直接把引用指向null,这种我觉得并没有什么意义。后来我注意到作用域在是否可达中起很大作用。不同大小的作用域,比如类、方法、甚至是某个代码块。当离开某个作用域时,作用域中对象变为不可达。那么gc就会回收这些内存。

2.复制算法

    复制算法产生的原因是为了解决标记-清除算法效率低的问题。将可用内存分为两块(原理并非实际),每次只使用其中一块,当这块用完,将存活对象复制到另一块中,并清除原来的半区。内存分配不再考虑碎片问题,仅仅是指针移动即可。不过算法的代价是内存利用率低。

    Sun的HotSpot在新生代采用这种回收算法。新生代中大部分,有书上写98%的对象都是“朝生夕亡”的,多以并不需要按照1:1来划分内存。将较大的分给Eden,S0、S1则较小。默认是8:1:1的比例,不过也可以配置相关的jvm参数调整。使用时对象存放在Eden+某个Survivor中,并将存活的复制到另一个Survivor上。这样仅仅浪费了10%的新生代空间。但也会出现存活对象多于10%的情况。此时需要依赖老年代进行分配担保,如果老年代提供担保,这些对象直接进入老年代。

3.标记-整理算法

    新生代采用复制算法的一个重要原因是新生代中对象存活率低。如果是老年代,对象存活率高,就需要大量的复制,内存利用率也不可能是90%。所以老年代不采用复制算法。采用标记-整理算法。

    标记-整理算法,在标记对象后,让存活对象向一端移动,最后清除边界以外的内存。

    新生代使用复制算法、老年代使用标记-整理算法,这种方式称为“分代收集算法”。

三、HotSpot的算法实现

    为了快速完成标记过程,减少gc的停顿时间(目前的虚拟机是无法避免的,任何一种垃圾回收器都会产生停顿,STW Stop The World。产生停顿的原因是垃圾回收需要确保一致性的快照中进行,保证标记清理时引用关系不变。其实CMS可以达到很短的停顿,但牺牲了一部分吞吐量,这个下一篇博文会说明),HotSpot中使用一组OopMap的数据结构来达到目的。ps:这里书上写的也不完全明确。类加载完成的时候HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。这样GC扫描时就直接得知这些信息。这里我也不是完全理解,以后慢慢理解。

    安全点。安全点有两个作用,1.程序运行到安全点才生成OopMap 2.程序执行时只有所有线程都停顿在安全点才开始GC。方法调用、循环跳转、异常跳转这些指令会产生安全点。目前所有的虚拟机都采用主动中断的方式让所有线程停在最近的安全点。每个线程在经过安全点时会检查一个终端标志,如果为真就停下来(挂起)。其实总结一下,安全点就是在编译的过程中在特定的地方加入特定的指令,功能分别是生成OopMap和轮询中断标志、中断线程。如果在GC的过程中有线程从sleep或挂起的状态变为运行状态,因为他是活动的,会对GC造成影响。虚拟机中还有成为安全区的机制。安全区域是不会改变引用关系的代码片段,如果醒来的线程在安全区中它可以继续运行至退出安全区,如果此时没有完成标记工作,该线程会停下来,等待可以离开安全区的信号。

    

    jvm基础总归会有很多概念性的,很理论的东西。我认为这些算法思路是可以应用到平时编码中的。一些看似显而易见的结论或算法,在得出结论和应用时也许是不简单的。下一篇博文会说明各种垃圾回收器。

    

转载于:https://my.oschina.net/u/2333484/blog/729317

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值