jvm学习——垃圾收集浅谈——丑九怪

前言

  • java的厉害之处就在于其对内存方面的自动管理。管理无非就是产生、移动或者移除其中的内容。对于内存中再也用不到的东西,当然有必要删除。垃圾收集就是在进行这个“删除”操作
  • jvm管理的空间分为五大部分:程序计数器,java栈,本地方法栈,java堆,方法区(java8中去除永久代,方法区中的类的源信息被存放在元空间内,运行时常量池移入java堆,这部分不理解的可以参考我的博文:java内存区域与内存溢出异常),其中的程序计数器,java栈和本地方法栈随着线程产生和消亡,其中存储的内容相对固定,不作为垃圾回收的目标。而相比java堆和方法区内的内容,由所有线程共用,所以这部分的内存需要动态分配与回收,这就是垃圾回收的目标区域

什么该回收?

  • 我们要回收垃圾,首先要确定目标是不是垃圾,jvm提出了两种算法,如下
引用计数法
  • 垃圾收集的主要目标区域在java堆中,java堆中对的主要内容是对象(指针),引用计数法的大致内容是:在对象中添加一个“引用计数器”,每当有一个地方对它进行了引用操作,就将计数器+1,引用失效就给计数器-1,当计数器为0时,表示这个对象已死,表示可回收。
  • 这个方法存在漏洞:无法处理两个或多个对象之间的循环引用问题。假设有两个对象,他们之间互相引用,而外界并没有他们的引用,他们的引用计数器始终不为零,也就无法回收。
可达性分析法
  • 这个方法是目前的主流方法,它通过一系列称为GC Root的节点为起始点,从他们开始向下搜索,搜索形成“引用链”,凡是不在引用链上的对象,一律被判定为可回收对象(垃圾),如图所示:对象1, 2, 3, 4均在引用链上,而对象5, 6, 7, 8之间虽然有引用关系,却没有处于任何GC Root下,故:对象5, 6, 7, 8被判定为可回收对象。
    在这里插入图片描述
  • GC Root可以有多个,他们产生于以下四种位置:
    虚拟机栈中的引用对象(Reference)
    本地方法栈中引用的对象
    原来方法区中,类静态属性中引用的对象
    方法区中常量引用的对象

什么时候回收?

  • 判定是否是垃圾的算法中,可达性分析法是主流。
  • 对象一旦退出了GC Root引用链,就变为了可回收对象,等待回收。其实在这中间还有一个对象自我拯救的过程。关键在于下面的finalize()方法:
    在这里插入图片描述
  • 这个finalize()方法是基类Object类中的方法,在对象被标记为可回收对象时,先进性一轮判断,如果对象覆盖了finalize()方法,并且finalize()方法之前没有被执行,那么这个对象将被放入一个叫做F-Queue的队列中,有专门的的线程Finalizer依次执行F-Queue中对象的finalize()方法(不一定执行完方法或遍历完所有元素),当下一次从根节点开始遍历引用链时,如果F-Queue中有对象通过finalize()方法的执行重新回到引用链上,那么这个对象便又“活过来了”。否则,对象基本上真的就被回收了。

怎么回收?

  • 在判定的对象之后,就要对对象进行回收了,主流的方法基于一个理论:分代收集法。
  • 顾名思义,对于内存不采用单一方法进行管理,根据对象的特点将内存分为新生代和老年代,分别管理。新生代:这里面的对象大都产生不久,就完成了自己的使命,每次垃圾回收都有大批对象死去。老年代:这里的对象大都是存活了很久的对象,因为一直在被某个地方使用,所以不会轻易被清除,每次垃圾回收变动不大
  • 具体的回收的算法大致分为三种:
标记清除算法
  • 这个方法是最基础的回收算法。主要思想是将内存中的可回收对象标记,然后清除,不对其他内容作出改变。
  • 这样做的弊端:效率低下:标记和清除两个过程的效率都不高
    空间问题:清除后不对内存做出任何改变,以一段时间之后内存会碎片化,导致后面如果要存放较大对象,可能倒追无法存放。
标记复制算法
  • 为了解决上面标记清除法的效率问题,提出复制算法。这个算法的思想:将空间分为两块,只使用一块空间存放对象,当这块空阿需要进行垃圾回收时,将这块空间中有用的对象整齐的放入第二块“空”空间内存中,直接将第一块空间设置为“空”,循环往复。
  • 现代商业虚拟机的新生代(后面会解释新生代和老年代)基本采用这种回收算法,后面还提出了这个算法的改进版:因为新生代中的对象大部分都是刚产生不久,就会被回收,如果用原先的复制算法将会降低空间利用率(原因是每次只能用一半空间),所以提出将内存分为一块较大的区域(Eden区)和两块较小的区域(Servior空间),虚拟机默认比例是1:8,如图:
    在这里插入图片描述
  • 整个过程是这样的:第一次先使用Eden空间和一块Servior空间存储对象,当需要进行垃圾回收时,将Eden空间和第一块Servior中可以存活的对象复制到另一块Servior空间中,由于是在新时代发生的垃圾回收,所以只有少部分对象会被复制过去,然后将Eden空间标记为空,继续使用Eden空间和第二块Servior空间存放对象,如此循环往复。
  • 在上面的过程中,有一个漏洞,就是:如果复制的过程中发现第二块Servior空间不够大,无法存放前面要复制过来的全部对象,怎么办?于是jvm又提出了分配担保机制:将这部分多出来的对象直接存放到老年代。
标记整理法
  • 前面提出的复制算法其实也存在弊端,如果不想浪费50%的空间,就需要有多余出来的空间作为分配担保的空间存在。
  • 在老年代的回收算法中提出了标记整理法,这个方法是指:在发生回收时,将内存中的存活对象统一向内存的一端进行移动,也就是整理,全部移动完毕之后,将其他部分标记为可用空间

上面几种算法的具体实现

  • 上面算法实现的前提是:已经可以确定那些对象是已经死亡的,但是怎样判定对象是否死亡,上述给出了两种方法:引用计数法和可达性分析法。这里就讨论一下可达性分析法中的一些细节
  • 在可达性分析法中,要先找到GC Root,GC Root多存在于全局性引用(常量和静态变量)和执行上下文(栈帧中本地变量表),可是遍历这些地方会消耗大量时间,所以jvm提出了一个名为OopMap的数据结构,这个数据结构中存储有可能是GC Root的引用,当需要进行可达性分析时,通过遍历一组OopMap,可以快速完成GC Root的枚举。
  • 虽然OopMap很好用,但也毕竟也占用空间,OopMap过多会导致GC的空间成本增加,所以只在特定位置生成OopMap,这里的特定位置有以下几个:
    • 循环的结尾
    • 方法返回前/调用方法指令后
    • 可能抛出异常的位置
    • 程序不执行,当前线程处于sleep或者blocked的状态
  • 前三个位置被称为安全点:jvm在GC时,需要让所有线程都停止(Stop The World),如果不停止,有可能会导致引用关系发生变化,GC的准确性无法保证。
  • 最后一个被称为安全区域,在这种情况下,当前的引用关系不会发生变化,线程进入该区域后会标识自己进入安全区域,可以开始进行GC Root枚举,当线程要离开安全区域时,如果虚拟机没有完成GC Root枚举,必须等虚拟机完成,才可以继续执行自己的程序。
  • 有关让所有线程都停下来的方式,分为两种:
    • 抢先试中断:让所有线程全部断掉,如果发现其中某个线程不在安全点处,则恢复该线程,让其运行至安全点处后停止。
    • 主动式中断:让所有线程依次访问一个值,当这个值为真,则让自己中断。这个方式是目前的主流方式。

几种垃圾收集器

  • 垃圾收集器是jvmGC的主要体现,下面列举出几个垃圾收集器,如图:
    在这里插入图片描述图中的连线表示相连的两个垃圾收集器可以协同工作。
  • 这里只大概讲一下G1收集器:这是一款面向服务端应用的垃圾收集器。他有如下特点:
    • G1是一个多线程的收集器,在多CPU的情况下缩短Stop The World的时间。并且G1还允许用户线程与收集器线程并发,只不过用户线程的速度会响应降低
    • 从上图中可以看出,G1实现了分代收集,也就是说,G1可以独自管理java堆等GC区域
    • 从整体上来看,G1是基于标记整理算法实现的收集器
    • 除了追求更小的停顿时间(Stop The World的时间),他还实现了能够让使用者规定GC发生的时机及频率
    • 从内部布局上来讲,G1的内部被分为了一个一个的小块,再将小块分为新生代和老年代。并且它在后台维护了一个优先级列表,列表里将每个小块,根据小块里垃圾堆积的价值大小(如果回收,可以获得多少空间以及回收所要消耗的时长)排列。在回收时,根据优先回收列表,首先回收价值最大的小块(Region)。

内存分配与回收策略

  • 对象首先在Eden区分配
  • 大对象,直接进入老年代
  • 长期存活的对象将进入老年代,jvm给每个对象定义了Age,每进行一次GC就将剩余对象的Age加1,默认加到15时,该对象就会被加入到老年代。
  • 动态对象年龄判定:当新生代Servior空间中,有一半以上的对象年龄相同为age,则年龄大于age的对象会被直接移入老年代。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值