垃圾收集器与内存分配策略

当我们在讨论垃圾回收的时候,我们在讨论什么?

在前面我们介绍了java 运行时内存区域,其中程序计数器,虚拟机栈,本地方法栈,三个区域都是随着线程生而生随线程灭而灭 ,栈中的栈帧随着方法的进入和退出而有条不紊的进行着近栈和出栈,并且每一个栈帧中分配多少内存基本上在类结果确定下来时候就是已知的。而java堆和方法区则不一样,一个借口中的多个实现类需要的内存可能不一样。我们的程序只有处于运行期间才可能知道会创建哪些对象,这部分的内存分配和回收都是动态的,这就需要我们的垃圾收集器了
那么垃圾收集器主要做哪些事情呢?千言万语都是都是下面三句话

  • 哪些内存需要回收?
  • 什么时候回收?
  • 如何回收?

引用计数算法

给对象中添加一个引用计数器,每当有一个地方在引用它的时候就+1,当引用失效的时候就-1,任何时刻,计数器为0的对象就是不可能再被利用的,当然这种实现方式非常简单,而且效率也很高,但是有一种情况解决起来确实很棘手,那就是循环引用

	objA.instance=objB
	objB.instance=objA

在这种情况下,即使后面没有对这两个对象的使用,但是依然不会释放空间,如何解决这个问题,
于是有了可达性分析

可达性分析

基本思想:
就是通过一系列称之为"GC Roots" 对象作为起始点,从这些点开始向下搜索,搜索所走过的路径称之为引用链(Reference China),当一个对象到GC Root 之间没有任何引用链相连的时候,则证明此对象是不可用的,如图
在这里插入图片描述那么此时很多人就会问什么样的对象可以充当GC Root呢?

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中,类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNI(一般说的Native方法) 中引用的对象

再谈引用

当然无论是对于可达性分析,还是引用计数判断对象的存货与否都和引用相关,那么引用有分为强引用,软引用,弱引用,虚引用这四种

强引用 :就是类似于 **Object obj=new Object()**的引用,只要强引用还存在,垃圾收集器永远都不会收集掉它所占用的内存

软引用: 描述一些还有用,但并非必须的对象,对于软引用所关联的对象,在系统将要发生内存溢出异常的之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收之后还没有足够的内存没那么就抛出溢出异常

弱引用:比软引用强度还低,被 弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器工作的时候,无论当前内存是否足够,都会回收掉这部分内存

虚引用就是最弱的一种了,

那么在可达性分析中,就算一个对象不可达,也并不是说这个对象非死不可,要真正宣告一个对象死亡,至少需要经历两次标记

  • 如果对象在可达性分析后发现没有与GC root 相连接的引用链,那么它将被第一次标记,并且进行删选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过了,虚拟机将这两种情况视为没有必要执行

回收方法区

方法区的垃圾回收效率都比较低(原因想想方法区里面存放的都是什么样的内存,就很清楚了),一般回收这两部分类容

  • 废弃常量
    以常量池中字面量的回收为例,加入一个字符串 abc 已经进入了常量池,但是整个系统没有任何String对象引用常量池中的“abc” 如果这个时候发生内存回收,就会将这个“abc” 常量所占有的内存回收过来
  • 无用的类
    判断常量是否是废弃的可能还比较容易,但是判断一个类是否是废弃的那么条件就比较苛刻了。一个类需要同事满足一下三个条件才能算是无用的类

1、该类的所有实例都已经被回收,也就是Java堆中不存在该类的任何实例
2、加载该类的ClassLoader已经被回收
3、该类对应的Java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

可以通过-Xnoclassgc: 是否对类进行回收
-XX:+TraceClassLoading-XX:+TraceClassUnLoading 查看类加载和卸载信息

垃圾收集算法

标记-清除(Mark-Sweep):分为标记和清除两个阶段。首先标记所有需要回收的对象,然后统一对所有标纪要回收 的对象进行回收操作,是最基本的收集算法
在这里插入图片描述复制算法(Coping)将可用内存划分为大小相等的两块,每次只是用其中一块,当这一块的内存用完了,就还存活的对象复制到另一块上面去,然后再把已使用过的内存空间一次性清理掉,这样使得每次都是对整个半区进行回收,实现简单,效率高,但是代价就是将内存缩小为原来的一半
在这里插入图片描述标记整理算法(Mark-Compact):标记过程和原来一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存货的对象都向一段移动,然后直接清理掉端边界以外的内存
在这里插入图片描述当然咯,一般把Java堆分为新生代和老年代,这样就可以根据不同代的不同特性采用适当的垃圾收集算法。在新生代使用复制算法,在老年代中使用“标记-清理”或者“标记整理”算法

算法实现

垃圾收集器

在这里插入图片描述
上图是目前主流的垃圾收集器之间的关系,有连线的是能够相互一起使用
相关详细情况就查阅其它资料,会很详细

大对象直接进入老年代

大对象指需要连续内存空间的Java对象,最典型的大对象就是那种很长的字符串,以及数组,大对象对于虚拟机的内存分配来说就是一个坏消息,经常出现大对象容易导致内存还有不少空间时就提前出发垃圾收集以获取祖国的连续空间来安置“他们”
-XX:PretenureSizeThreshold参数(只对SerialParNew两款收集器有效),令大于这个设置值的对象直接在老年代分配,这样做的目的是避免Eden以及两个Survivor区之间发生大量的内存复制

长期存活的对象将进入老年代

虚拟机给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出生,每经过一次Minor GC 并且被Survivor容纳,年龄就会增加一岁,默认情况下到了15岁就会被放到老年代中,对象晋升到老年代的阈值可以通过-XX:MaxTenuringThreshold来设置

动态对象年龄判定

虚拟机并不是永远的要求对象的年龄必须达到MaxTenuringThreshold,才能晋升到老年代,如果在Survivor中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold

空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代中最大可用的连续空间是否大于新生代所有的对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的,如果不成立则虚拟机首先会检查HandlePromotionFailure (在复制算法中当Survivor空间不够用时,用老年代的一种分配担保)设置值是否允许担保失败。如果允许那么就会继续检查老年代中最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,,如果大于,将尝试一次minor GC 如果小于或者HandlePromotionFailure为不允许,那么就会进行一次Full GC
当然在JDK 1.6 update24之后,是只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行MInor GC,否则就会进行Full GC
参考:《深入理解Java虚拟机-jvm高级特性与最佳实践》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值