JVM读书日记2(垃圾收集器与内存分配策略)

对象已死?

引用计数器

  1. 在对象中添加一个引用计数器,没到有一个地方引用它时,计数器值+1,引用取消计数器值减一。
  2. 要额外消耗内存,并且考虑情况要更多比如循环引用,A调用B,B调用A。

可达性分析法

  1. 核心“GC roots”,以GC roots为起始点的集合,从这些节点开始根据引用关系向下搜索,走过的路径称为引用链
  2. 什么样的对象可以作为GC roots
    1) 在虚拟机栈中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等
    2) 在方法区中类静态属性引用的对象,如java类的引用类型静态变量。
    3) 在方法去中常量引用的对象,字符串常量池里的引用。
    4) 在本地方法栈中JNI引用对象。
    5)Java虚拟机内部的引用,如基本数据类型对应的class对象,常驻异常对象,类加载器等
    6)所有被同步锁持有的对象
    7)反应Java虚拟机内部的JMXBean、JVMTI中的注册的回调、本地代码缓存等。

再谈引用

  1. 强引用:不会被垃圾回收机制回收的 如 Objetc obj = new Object();
  2. 软引用:用来描述一些还有用,但不是必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出前,会把这些对象列进回收范围中进行第二次回收。
  3. 弱引用:也是描述非必须对象,无论内存是否足够都会被回收。
  4. 虚引用:幽灵引用幻影引用一个对象是否有虚引用存在并不影响它的生存时间,为对象设置虚引用的唯一目的是为了在对象被回收的时候收到一个通知。

生存还是死亡?

  1. 当一个对象被垃圾回收机制第一次标记的时候并不会立刻回收,而是会先查看finalize()方法是否需要被执行。假设对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过了,那么虚拟机就会把它视为不必要执行,而回收对象。
  2. 如果这个对象被判定为有必要执行finalize()方法那么他就有自救的机会。譬如把自己赋值给某个类的变量或者对象的成员变量,那么它将不会被回收。

回收方法区

必须满足3个条件

  1. 该类的所有实例都已经被回收,也就是java堆中不存在该类及其子类的实例。
  2. 加载该类的类加载器已经被回收,这个条件必须是精心设计的可替换类的类加载器如OSGI,JSP的重加在等,否则通常是很难达成的
  3. 该类对对应的java.long.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

垃圾收集算法

分代收集理论

三假说
  1. 弱分代假说:绝大部分的对象多是朝生夕死。
  2. 强分代假说:熬过多次垃圾收集过程的对象就越难消亡。
  3. 跨代引用假说:跨代引用相对同代引用来说占用极少。

标记-清除算法

分为两部分 第一部分是标记,通过可达性分析法标记处需要回收的对象,第二部分清除标记出来的对象。

缺点
  1. 容易产生内存空间碎片。
  2. 执行效率不稳定,如果java堆越大,那么标记清除的过程就越慢。

标记-复制算法

将内存分为两部分,我们创建对象只使用其中一部分,然后通过标记标记出需要清除的对象,然后把存活的对象复制到预留的内存空间,在清除已经使用的内存空间。

优缺点
  1. 减少了内存空间碎片的产生,但是我们实际使用的内存空间却只有原来的一半

标记-整理算法

标记和上述一样,整理是值将存活的对象整理到一个集中的部分。

优缺点
  1. 不会产生内存空间碎片,但是如果存活的对象过多,那么效率会很底下,而且整理是也会造成指针的重新定位。

hotspot的算法细节实现

根节点枚举

因为根节点枚举必须在一个能保障一致性的环境中进行,HotSpot的做法是通过一种OopMap的数据结构来解决这些问题,在类加载动作完成的时候将对象内什么偏移量上的时什么类型的数据计算出来,在即时编译过程中,也会在特定的位置记录下栈和寄存器哪些位置时引用。

安全点

在OopMap的协助下可以快速的完成GCroots枚举,可能导致引用关系变化,或者说导致OopMap内容变化的指令非常多,那么生成OopMap的代价会很高昂。HotSpot是在一些特定的点上来维护OopMap,这些点就叫做安全点。

如何让所有的线程都跑到安全点呢?
  1. 抢先式中断:不需要线程的执行代码主动配合,在垃圾收集发生时,系统主动把所有用户线程中断,然后查看是否所有线程都在安全点上,如果有没在安全点上的线程,那就启动该线程知道它到达安全点上为止。(现在几乎没有使用的)
  2. 主动式中断:当垃圾收集需要中断线程的时候,会设置一个标志位,各个线程执行过程时会不行的去轮询这个标志,一但发现中断标志为真的时候就自己在最近的安全点上主动中断挂起。

安全区

因为有些时候当垃圾收集发生时,线程无法自动到达安全点,如线程Sleep时,就需要安全区来解决这个问题。当用户线程执行到安全区域里面的代码时,首先会标志自己已经进入了安全区中,这样垃圾收集就不会区管声明了自己在安全区的线程,如果完成了那么线程就会继续进行,如果没完成就会一直等待信号,直至完成为止。

记忆集与卡表

记忆集是为了处理垃圾收集器中跨代引用的存在的,为了避免把整个老年代加入GC Roots中。事实上并不是只有老年代才有跨域引用问题,所有涉及部分垃圾收集(Partial GC)的地方都存在。
记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。不考虑效率和成本,最简单的实现可以用非收集区域中所有含跨代引用的对象的数组来实现这个数据结构。

记忆集的记录精度
  1. 字长精度:每个记录精确到一个机器字长,该字段包含跨代指针。
  2. 对象精度:每个记录精度到一个对象,该对象里有字段还有跨代指针。
  3. 卡精度:每个记录精确到一块内存区域,该区域内所有对象还有跨代指针。
    卡精度所指的是用一种称为卡表的方式区实现记忆
    集。这也是目前最常用的一种记忆集实现方式。卡表记忆集的区别是卡表是记忆集的具体实现,记忆集是抽象的是行为意图,卡表是它的具体实现。它们的关系可以理解成HashMap和Map的关系。

写屏障

HotSpot虚拟机通过写屏障技术维护卡表状态,(处理卡表元素变脏等情况),写屏障可以看作是在虚拟机层面堆**“引用类型字段赋值”**这个动作的AOP切面,在引用对象赋值时会产生一个环形通知,供程序执行额外的动作,也就是说赋值的前后都在写屏障的覆盖范围内。
在G1之前HotSpot使用的都是写前屏障(值在赋值之前)。

并发的可达性分析

  1. 白色:表示对象尚未被垃圾收集器访问过,显然在可达性分析刚刚开始的阶段,若可达性分析结束还是白色就表示不可达。
  2. 黑色:表示对象已经被可达性表示法标记为可达。
  3. 灰色:表示对象已经被可达性分析法访问过,但是对象还有引用没有被访问。
    在这里插入图片描述
    当且仅当一下两个条件同时满足时,会产生对象消失问题,即原本应该被标记黑色的对象被标记为白色。
    1 赋值器插入了一条或多条从黑色对象到白色对象的新引用。
    2 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。
    解决方式一般分为两种
    1 增量跟新:增量更新时破坏第一种条件,当黑色对象插入新的指向白色对象的引用关系时,就将新的引用信息记录下来,在并发扫描结束后以黑色为根再次扫描,即黑色对象一但插入新的引用信息,黑色对象就当作灰色对象处理。
    2 原始快照:原始快照破坏的时第二种情况,指的是当灰色删除了直接或间接的引用时,会记录下被删除的引用,等并发扫描结束后,在从这些记录过的灰色对象为根再次扫描,无论引用关系是否被删除,都会按照刚开始扫描的那一刻的对象图快照来搜索。

经典垃圾收集器

Serial收集器

Serial收集器是最基础、历史最悠久的收集器,它是一款单线程收集器,这里的单线程不仅仅说明它只会使用一个处理器或一条手机线程去完成垃圾收集工作,更重要的是强调它在进行垃圾收集工作的时候会暂停所有其他线程,知道工作结束,会造成Stop the world
在这里插入图片描述
优点
在于内存资源受限的环境,它是所有收集器里额外内存消耗最小的,对于单核处理器或处理核心数较少的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率

ParNew

ParNew收集器是多线程版本的Serial收集器,除了同时使用多线程外,基本与serial一致。
在这里插入图片描述

Parallel Scavenge收集器

Parallel Scavenge收集器是Java虚拟机中垃圾收集器的一种。
又称为吞吐量优先收集器,和ParNew收集器类似,是一个新生代收集器。使用复制算法的并行多线程收集器。Parallel Scavenge是Java1.8默认的收集器,特点是并行的多线程回收,以吞吐量优先。
吞吐量=运行用户代码时间/(运行用户代码时间+运行垃圾收集时间)

Serial Old收集器

Serial老年代版本也是一个单线程收集器,使用的是标记-整理算法,也作为CMS收集器发生失的后背预案。

Parallel Old收集器

Parallel Scavenge收集器收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。

并发 指用户线程和垃圾收集线程同时工作。

并行 指垃圾收集时多个垃圾收集线程同时协作进行。用户线程通常处于等待状态。

CMS收集器

CMS收集器是一种以获取最短回收停顿时间未目的的收集器,使用的标记-清楚算法,他的垃圾标记过程相对复杂一些。

  1. 初始标记:仅仅标记GC Roots 能直接关联的到对象速度快。Stop The World
  2. 并发标记:机遇GC Roots直连的对象向下标记,遍历整个对象图。
  3. 重新标记:修正并发标记期间,因为用户程序继续运行而导致的标记变动的一部分(增量更新)会有Stop The World
  4. 并发清除:清理删除已经死亡的对象。

在这里插入图片描述
CMS收集器因为占用了一部分线程会导致程序变慢,吞吐量降低等问题,
CMS默认启动的回收线程数是(处理器核心数量+3)/4;
JDK 9 及以上版本使用CMS会被提示CMS将会在未来废弃。

Garbage First(G1)

G1开创了收集器面向局部收集的设计思路和基于Region的内存布局形式,主要面向服务端 。
G1仍然遵循分代理论,但其堆内空间和其他收集器有很大不同,他把连续的java堆划分为多个大小雄等的独立区域,每一个Region都可以根据需求,扮演不同新生代角色或老年代角色。

  1. G1 在做垃圾回收的时候是会选择收益最大的区域进行的。
G1执行顺序
  1. 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要 停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际 并没有额外的停顿。
  2. 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以 后,还要重新处理SATB记录下的在并发时有引用变动的对象。
  3. 最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下来的最后那少量的SATB记录
  4. 筛选清除(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值