java垃圾回收机制

对象的内存布局

  • 新建对象的方式:new 反射机制 Object.clone 反序列化 Unsafe.allocateInstance,Object.clone和反序列化都是复制已有的数据初始化新建对象的实例字段,Unsafe.allocateInstance方法没有初始化实例字段,new和反射机制通过调用构造器初始化字段

  • 调用构造器时将优先调用父类构造器直到Object,所以new出来的对象涵盖了所有父类的字段,子类实际会分配内存

  • 压缩指针:

    • 在Java虚拟机中,每个对象都有对象头(object header)由标记字段和类型指针构成,标记字段存储Java虚拟机有关该对象的运行数据(哈希码,GC信息以及锁信息)类型指针指向该对象的类

    • 64虚拟机中,标记字段64位,类型指针64位,每个对象在内存中的额外开销时16字节,比如Integer,只有一个int(四个字节)每个Integer对象的额外内存开销时400%,所以这是引入基本类型的原因之一。

    • 为了减少对象内存的使用引入了压缩指针

    原理:

    • 内存对齐:虚拟机堆中对象的起始地址需要对齐至8的倍数,如果得不到8N个字节,那么空白的空间就浪费,浪费的空间叫做对象间的填充

    • 默认情况下,Java虚拟机中的32位压缩指针可以寻址2的35次方个字节也就是32GB,超过这个地址空间就会关闭(压缩指针)

    • 字段重排列

垃圾回收

1.对象的死亡判断方法

  • 引用计数法,为每个对象添加引用计数器统计指向该对象的引用个数,如果为0则说明可以被回收,这个方法不能处理循环引用

  • 可达性分析算法:为了解决循环引用,将一系列GC Roots 作为初始的存活对象合集,从合集出发,搜索能够被集合引用到的对象加入集合中,这个过程称之为标记,其他的就是死亡的 GC Roots(堆外指向堆内的引用)

    • 能够作为GC Roots的节点包括但不限于:

      1.Java方法栈帧中的局部变量
      2.已加载类的静态变量
      3.JNI handles
      4. 已启动且未停止的Java线程

    • 问题

      1. 多线程环境下,其他线程可能更新已经访问过的对象中的引用,倒是误报或者漏报
      2. 漏报比较麻烦,垃圾收集器回收仍被引用的对象内存,可能导致虚拟机的崩溃

 

  • Stop - the - world:为了解决上一点中的问题,停止其他非垃圾回收线程的工作,直到垃圾回收完成,这就造成了垃圾回收的暂停时间(GC pause),stop - the - world通过安全点机制实现,当虚拟机收到请求,便等待所有的线程到达安全点,然后才允许stop - the -world 的线程独占,安全点的初始目的似乎让线程找到稳定执行状态,这个状态堆栈不会发生变化

  • Stop -the - world导致的停顿问题:在进行可达性分析时,必须处于停顿状态,目前主流Java虚拟机采用的准确式GC,执行系统停顿时,不需要检查完所有的执行上下文和引用位置,虚拟机通过OopMap的数据结构存放引用对象,类加载完成之时,Hospot把对象内什么偏移量上的什么类型数据计算出来,在JIT编译过程中也会在特定位置记录下栈和寄存器中的引用

  • 安全点(SafePoint):在OopMap的协助下,可以完成GC ROOTS的枚举,问题是可能导致的引用变化,就是说OopMap内容变化的指令非常多,如果为每一条都生成OopMap太占空间,HotSpot中只在特定位置记录,就是安全点,一般到达安全点是开始GC,GC发生时,如何让所有线程跑到安全点,有两种方式

    • 抢先式中断
      发生GC时,停掉所有的线程,如果有没到达安全点的回复执行到safepoint

    • 主动式中断

  • 安全区域

2. 垃圾回收的三种方式:

  • 清除(sweep),把死亡对象所占据的内存标记为空闲内存记录在一个空闲列表(free list)之中,需要新建对象时(内存管理模块寻找空闲内存) 问题

    • 造成内存碎片(对象要连续的空间)

    • 分配效率较低如果是一块连续的内存空间,通过指针加法来分配,空闲列表 (逐个访问查找)

  • 压缩(compact):存活的对象聚集到内存区域的起始位置(解决了内存碎片,加大压缩算法的性能开销)

  • 复制(copy):内存区域分为两等分,用指针from to 维护,并且只用from指针指向的内存区域来分配,垃圾回收时,存活的对象复制到to指针的内存区域,交换from to 指针的内容(虽然解决了问题,但是堆的利用率低)

  • 分代回收:堆空间分为新生代和老年代,新声代存储新建的对象,存活时间长了就移动到老年代,不同代采用不同的回收算法,新生代大部分对象存活时间短,频繁的次啊用耗时短的垃圾回收算法,老年代:大概率继续存活,如果触发到老年代的回收,则代表出错或者堆的空间耗尽,就要做一次全堆扫描

  • 垃圾收集器(CMS G1重点)

    • Serial

      • 单线程:在进行GC时,必须暂停其他所有的线程(Stop the world)

      • 简单而高效,一般还运行的Client模式下的新生代收集器

      • 运行流程
         

    • ParNew

      • Serial的多线程版本,只有它能和CMS配合

      • -XX:ParallelGCThreads 配置垃圾回收的线程数

    • Parallel Scavenge

    • Serial Old

    • Parallel Old

    • CMS(Concurrent Mark Sweep)

      • 基于标记-清除算法,以获取最短停顿事件为目的

      • 运行步骤:

        • 初始标记:只标记一下GC Roots能直接关联到的对象,速度快

        • 并发标记:进行GC Roots Tracing的过程

        • 重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的标记记录,停顿事件长于初始标记

        • 并发清除:并发标记和并发清除和用户线程一起执行,其他两个阶段需要停顿

      • 缺点
        • 占用CPU资源导致应用程序变慢
        • 无法处理浮动的垃圾
        • 基于标记-清除,会产生大量空间碎片
    • G1

      • 特点:

        • 并行与并发

        • 分代收集

        • 空间整合:标记-整理

        • 可预测的停顿:有计划的避免在整个Java堆中进行全局的垃圾搜索

      • G1之前搜索的范围是整个新生代和老年代,但是G1不同,将Java堆分成多个大小相同的独立区域,保留新老的概念,但是不再是物理的隔离,G1跟踪各个Region里面垃圾堆积的价值而维护一个优先列表根据允许的时间进行较高价值的回收

      • Region中以及其他新生代和老年代的引用,虚拟机通过Remembered Set来避免全局扫描,每个Region都有一个set

      • 运行流程:

        • 初始标记

        • 并发标记

        • 最终标记

        • 筛选回收

      •  

  • 新生代的Minor GC:

    堆划分:新生代被划分为Eden区,以及两个一样的Survivor区,虚拟机默认采用动态分配的策略,根据生成对象的速率以及survivor的使用情况动态调整划分区域的比例,新建对象时在Eden中分配内存,堆空间线程共享,划空间要同步进行

 

  • 多个线程的的抢车位的问题:TLAB,每个线程可以向申请一段连续的内存,作为私有的TLAB,这个操作需要加锁,线程维护两个重要指针,一个指向TLAB中空余内存的起始位置,一个指向末尾,接下来的新建指令,通过指针加法(bump the pointer,指向空余位置的指针加上请求的字节数,加法后空余内存的指针<= 末尾指针,分配成功,否则失败需要线程重新申请TLAB
  • 当Eden区的空余空间耗尽,虚拟机出发Minor GC,存活下来的送入Survivor区,两个Survivor区用from to表示,to指向空的,发生Minor GC时,Ed en区和from区的存活对象复制到to,交换from to 的指针】【虚拟机记录复制的次数,如果为15晋升到老年代,如果单个Survivor区被占用50%,较高的也会进入老年代
  • 空间分配担保
  • Minor GC,不用堆整个堆进行回收,但是,如果老年代引用了新生代中的对象,当标记存活的时候需要扫描老年代,就又回到了全堆扫描,为了解决这个问题引入Region
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值