前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM学习笔记——垃圾收集器与内存分配策略(2)

JVM学习笔记——垃圾收集器与内存分配策略(2)

作者头像
用户1665735
发布2019-02-19 11:13:34
4810
发布2019-02-19 11:13:34
举报
文章被收录于专栏:kevindroidkevindroid

垃圾收集器

java虚拟机规范中并没有对垃圾收集器如何实现有任何规定,因此,不同的厂商,不同版本的虚拟机所提供的垃圾收集器可能会有很大差别,这里只讨论基于JDK1.7之后的HotSpot虚拟机。这个虚拟机包括的收集器如下图所示:

这里写图片描述
这里写图片描述

上图展示了7种不同的垃圾收集器,如果两个垃圾收集器中存在连线,就证明这两种垃圾收集器可以搭配使用。它们所处的区域,代表了他们是在新生代还是老年代中使用。

Serial收集器

serial的意思是单线程,这不仅仅意味着它会使用一个cpu或者一条工作线程去完成垃圾收集,更重要的是它在进行垃圾收集时,必须暂停所有其他工作线程,俗称“stop the world”,虽然听起来很酷,但是如果想象以下你每用一小时软件就要卡上个5分钟进行垃圾收集,这并不是什么很好的体验。

这里写图片描述
这里写图片描述

(图画的不好请见谅) 虽然虚拟机的开发团队一直在为了减少因为内存回收而导致的停顿时间,随着一个个越来越优秀的收集器的出现,用户线程的停顿时间在不断缩短,但是无法完全消除。虽然serial收集器看起来鸡肋,但是到现在为止,它仍然是虚拟机运行在客户端情况下的默认收集器。它简单而高效,对单线程的垃圾收集效率极高,而对用户的桌面应用场景下,新生代一般也就100~200mb的样子,这样的垃圾回收导致的停顿可以控制在100ms内,只要不是频繁的发生,这是完全可以接受的。

ParNew收集器

这其实就是serial收集器的多线程版本,除了使用多线程进行垃圾收集之外,其余行为包括serial收集器可用的所有控制参数,收集算法,stop the world,对象分配原则,回收策略等都与serial收集器完全一样,两者公用了相当多的代码。

这里写图片描述
这里写图片描述

ParNew收集器是在新生代唯一可以配合CMS收集器的,ParNew收集器在单cpu的环境中绝对不会有比serial收集器更好的效果,甚至因为存在线程交互的开销,该收集器在通过超线程实现的双核环境下都不能保证一定超过serial收集。当然,随着cpu的数量的增加,它对于提升GC时系统资源的利用率还是有好处的。 tips: 并行:多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态; 并发:指用户线程和垃圾收集线程同时执行(但不一定是并行的,可能是交替执行),用户程序在继续运行,而垃圾收集器运行与另一个CPU上。

Parallel Scavenge收集器

这也是一个新生代收集,使用复制算法,也是并行的多线程收集器,虽然听起来和前者一样,但是关注点和其他的收集器不同。CMS等其他收集器的目的是尽可能的缩短垃圾收集时用户线程的停顿时间,而此收集器的目的是达到一个可控的吞吐量(throughput),又被称为吞吐量优先收集器。吞吐量=运行用户代码时间/(运行用户代码事件+垃圾收集事件)。 停顿时间越短越有利于和用户交互的程序,良好的反应速度可以提升用户体验。而高吞吐量则可以高效率的利用cpu时间,尽快完成程序的运算任务,适合在后台不需要太多交互的任务。 该收集器提供了两个参数用于精确控制吞吐量,分别控制最大垃圾收集停顿时间的-XX:MaxGCPauseMills参数以及直接设置吞吐量大小的-XX:GCTimeRadio参数。 前者可以设置为一个大于0的毫秒数,收集器将尽可能保证垃圾收集事件不超过设定值。不过不要一味调低这个值,因为垃圾收集变得更快是以牺牲吞吐量和新生代空间换取的,把新生代调小一点,比如从500mb变为300mb,垃圾收集事件从每10s一次,每次停顿100ms变成现在每5s一次,每次70ms,虽然垃圾回收事件减少了,但是吞吐量也降下来了。 后者代表的是程序运行时间的比例,可以设置为1~100之间的整数,比如设置为19,那么垃圾收集时间就占1/(1+19) = 5%,如果设置为99,那么垃圾收集时间就占1/(99+1)= 1%。

Serial Old收集器

serial收集器的老年代版本,使用标记-整理算法,这个收集器的主要意义在于给client模式下的虚拟机使用,如果在server环境下,主要用于在JDK1.5以及之前的版本与Parallel scavenge收集器搭配使用,另一用途就是作为CMS收集器的后备方案。

这里写图片描述
这里写图片描述

Parallel Old收集器

Parallel scavenge收集器的老年代版本,使用多线程与标记-整理算法。这个收集器出现的最大作用就是可以与Parallel scavenge搭配使用,解决了原来只可以使用serial old收集器的尴尬,因为serial old收集器在老年代无法充分发挥服务器多cpu的处理能力。在吞吐量优先的环境中,可以考虑使用parallel old + Parallel scavenge 的组合。

这里写图片描述
这里写图片描述

CMS收集器

CMS收集器是一种以获取最短回收停顿时间的收集器,主要用于javaweb中的服务端上。它基于标记-清除算法,它的运作过程相对复杂,分为:

  • 初始标记
  • 并发标记
  • 重新标记
  • 并发清除

其中,初始标记和重新标记仍需要“stop the world”。初始标记仅仅是标记以下GC roots能到达的对象,速度很快;并发标记是进行GC root tracing的过程,而重新标记阶段是为了修正因为用户程序继续运行导致标记变动的那一部分对象的标记记录,这个时间一般比初始标记的时间长,但是远比并发标记的时间短。 由于整个过程中耗时最长的并发标记与并发清除收集器线程可以与用户线程一起工作,所以,总体上来说,CMS的内存回收过程是和用户线程一起并发进行的。

这里写图片描述
这里写图片描述

CMS是一款优秀的收集器,被称为并发多线程收集器,但是,它也存在3个明显的缺点: - CMS收集器对收集器资源非常敏感,CMS默认启动的回收线程数是(cpu数量+3)/4,很明显,cpu越少,收集器线程占用的线程越多,吞吐量变低,应用程序变慢。 - CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败导致另一次full gc的发生。 CMS在并发清理阶段用户线程还在运行着,伴随运行着自然会有垃圾产生,这部分垃圾在产生后,CMS无法在当次垃圾回收中清理它们,只能等待下一次垃圾回收,这被称为“Floating Garbage”。同时,由于垃圾回收阶段用户线程仍然在运行,故CMS收集器不能等到老年代被填满之后才进行收集,需要预留一部分空间提供并发收集的程序运作使用。如果CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”,这是虚拟机将启动后备预案,临时启动serial old收集器进行老年代的收集。 - 最后一个缺点,CMS收集器是一个基于标记-清除算法的收集器,这种算法会导致大量内存碎片的产生,会给大对象的分配造成麻烦,往往老年代还有很多空间,但是无法找到足够大的连续空间。可以采取-XX:+UseCMSCompactAtFullCollection开关参数,在收集器顶不住的时候进行full gc时候进行内存的合并整理,但是耗时边长。

G1收集器

G1是一个面向服务端应用的垃圾回收器,目标是替换CMS收集器,有以下特点: - 并行与并发:充分利用多核,多cpu的性能优势,缩短“stop the word”的运行时间。 - 分代收集:虽然可以不用其他收集器配合就可以独立管理整个java堆,但是采取不同的方式对待新创建的对象和创建已久,熬过多次垃圾收集的旧对象。 - 空间整合:从整体来看基于标记-整理算法,从局部来看基于复制算法,运行时不会产生内存碎片,收集能提供规整的可用内存。 - 可预测的停顿:G1除了追求低停顿外,还建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的的时间片段内,这几乎是实时垃圾收集器的特征了。 G1收集器将整个java堆划分为多个大小相等的独立区域(region),虽然还保留这新生代和老年代的概念,但新生代和来年代不再是物理隔离的,都是一部分region的集合(不需要连续)。G1收集器跟踪每个region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次更具允许的收集时间,优先回收价值最大的region。 G1收集器的运作大致可以分为以下几个步骤: 1. 初始标记 2. 并发标记 3. 最终标记 4. 筛选回收

这里写图片描述
这里写图片描述

内存分配与回收策略

对象的内存分配,往大方向讲,就是在堆上分配(也有可能是在经过JIT编译后拆散为标量类型间接的在栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲(Thread Local Allocation Buffer),线程优先在TLAB上分配,少数情况下直接分配在老年代上,分配规则并不是完全确定的,是具体情况而定。

对象优先在Eden上分配

大多数情况下,对象在新生代eden区分配,当eden区没有足够空间进行分配时,虚拟机进行一次Minor GC。

代码语言:javascript
复制
Minor GC和Full GC有什么不同?
Minor GC:发生在新生代的垃圾回收动作,因为大多数java对象都有存活时间短的特性,所以Minor GC分成频繁,回收速度块。
Full GC:发生在老年代的GC,出现了Full GC,经常伴随着至少一次Minor GC,Full GC的速度通常比前者慢十倍以上。

大对象直接进入老年代

大对象指需要大量连续内存的java对象,典型的大对象是那种很长的字符串与数组,这对于内存分配是一个坏消息,更坏的消息就是遇到一群短命的大对象,编程时尽量避免。大对象容易因为无法安置提前触发 GC。 虚拟机提供了一个-XX:PretenureSizeThreshold参数,大于这个值的对象直接进入老年代分配,避免在eden区发生大量的内存复制操作。

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

为了识别哪些对象应放在新生代,哪些在老年代,虚拟机给每个对象定义了一个Age计数器,如果对象在eden出生并且经过一次MinorGC仍然存活并且可以被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1,对象每熬过一次MinorGC,年龄增加一岁,当age大于某个值,默认为15时,晋升到老年代中。

动态判断对象年龄

如果在Survivor空间中的想通年龄的多有对象大小总和大于Survivor空间的一半,年龄大于等于这个值的对象直接升入老年代,无需满足上一条的要求。

空间分配担保

这里写图片描述
这里写图片描述
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017年03月14日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 垃圾收集器
    • Serial收集器
      • ParNew收集器
        • Parallel Scavenge收集器
          • Serial Old收集器
            • Parallel Old收集器
              • CMS收集器
                • G1收集器
                • 内存分配与回收策略
                  • 对象优先在Eden上分配
                    • 大对象直接进入老年代
                      • 长期存活的对象进入老年代
                        • 动态判断对象年龄
                          • 空间分配担保
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档