Icharle | Don't forget your first thoughts - JVM
type: channel
link: https://icharle.com/tag/JVM/
items: JVM内存分配与回收策略
items: JVM垃圾收集器

JVM内存分配与回收策略
type: item
date: 2019-04-19T03:50:00+08:00
description: 新生代GC(Minor GC)：指发生在新生代的垃圾收集动作，因为Java对象大多都具备朝生夕灭的特性，所以Minor GC非常频繁，一般回收速度也比较快。 老年代(Major GC/Full GC)：指发生在老年代的GC，出现了Major GC，经常会伴随至少一次的Minor GC(但非绝对的，在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minior GC慢10倍以上。 内存分配策略 JVM虚拟机提供了-XX：+PrintGCDetails这个收集器日志参数，告诉虚拟机在发生垃圾收集行为时打印内存回收日志，并且在进程退出的时候输出当前的内存各区域分配情况。 对象优先在Eden分配 在大多数情况，对象在新生代Eden区中分配，当Eden区分配没有足够空间进行分配时，虚拟机将发起一次Minor GC。 大对象直接进入老年代 大对象是指，需要大量连续内存空间的Java对象，典型的大对象就是那种很长很长的字符串以及数组。 经常出现大对象容易导致内存还有不少空间就提前触发垃圾收集以获取足够的连续空间来存放大对象。 JVM虚拟机提供了一个-XX：PretenureSizeThreshold参数，令大于这个设置值的对象直接在老年代分配。注意：PretenureSizeThreshold参数只对Serial和ParNew两个收集器有效。 长期存活的对象将进入老年代 JVM虚拟机采用分代收集的思想来管理内存，则内存回收也需要识别何为新生代，何为老年代，因此JVM定义了一对象年龄(Age)计数器。对象在Survior区中每“熬过”一次Minor GC，年龄就增加1岁，默认为15岁即可晋升老年代。 对象晋升老年代阈值，可以通过-XX：MaxTenuringThreshold设置。 动态对象年龄判定 JVM虚拟机并不是永远地要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代，如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半，年龄大于或等于该年龄的对象就可以直接进入老年代，无须等到MaxTenuringthreshold中要求的年龄。 空间分配担保 在发生Minor GC之前，虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间。 如果这个条件成立，那么Minor GC可以确保是安全的。如果不成立，则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。 如果允许，则会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小，如果大于，将尝试着进行一次Minor GC，尽管这次Minor GC是有风险的；如果小于，或者HandlePromotionFailure设置不允许冒险，则改为进行一次Full GC。

JVM垃圾收集器
type: item
date: 2019-04-18T21:51:00+08:00
description: 7种作用于不同分代的收集器，如果两个收集器之间存在连线，就说明可以搭配使用。 Serial收集器 JVM虚拟机运行在Client模式下的默认新生代收集器。 是一个单线程收集器，单线程即说明它只会使用一个CPU或者一条收集线程去完成垃圾收集工作，更重要的是在它进行垃圾收集时，必须暂停其他所有的工作线程，直到它收集结束。 简单而高效(与其他收集器的单线程比)，对于限定单个CPU的环境来说，Serial收集器由于没有线程交互的开销，因此获得最高的单线程收集效率 在Client场景下，分配给虚拟机管理的内存一般来说不会很大，收集几十兆甚至一两百兆的新生代停顿时间完全可以控制在几十毫秒最多一百多毫秒以内。 ParNew收集器 是Serial收集器的多线程版本 运行在Server模式下的虚拟机首选的新生代收集器 除Serial收集器外，目前只有它能与CMS收集器配合工作 由于存在线程交互的开销，ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果。随着CPU数量增加，默认开启的收集器线程数与CPU的数量相同，可以使用-XX：ParallelGCThreads参数来限制垃圾收集的线程数。 Parallel Scavenge收集器 多线程新生代收集器 基于复制算法实现 其它收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间，而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。吞吐量是指：CPU用于运行代码的时间与CPU总消耗时间的比值，即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间) Serial Old收集器 是Serial收集器的老年代版本，是一个单线程收集器 采用“标记-整理”算法 Client模式下的虚拟机使用 在Server模式下，则它主要还有两大用途：一种用途是在JDK1.5以及之前版本中与Parallel Scavenge收集器搭配使用；另外一种用途是作为CMS收集器的后备预案，在并发收集发生Concurrent Mode Failure时使用。 Parallel Old收集器 是Parallel Scavenge收集器的老年代版本，是一个多线程收集器 采用“标记-整理”算法 新生代Parallel Scavenge收集器由于无法与CMS收集器配合工作，只能与Serial Old收集器配合工作，但该收集器在服务端性能差，使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果，由于单线程的老年代收集无法充分利用服务器多CPU的处理能力，在老年代很大而且硬件比较高级的环境还不一定有ParNew+CMS的组合 Parallel Old收集器的出现，在注重吞吐量以及CPU资源敏感的场合，优先考虑Parallel Scacenge + Parallel Old收集器。 CMS收集器 一种以获取最短回收停顿时间为目标的收集器 采用“标记-清除”算法 收集过程分为4步： 初始标记(CMS initial mark)：仅仅只是标记一下GC Roots能直接关联到的对象，速度很快，需要停顿 并发标记(CMS concurrent mark)：进行RootsTracing过程，不需要停顿，耗时长 重新标记(CMS remark)：为了修正并发标记期间因用户重新继续运作而导致标记产生的变动的哪一部分对象的标记记录，需要停顿 并发清除(CMS concurrent sweep)：不需要停顿 由于整个过程中耗时最长的并发标记和并发清除过程收集器线程可以与用户线程一起工作，不需要停顿。 存在的缺点： CMS收集器对CPU资源非常敏感，它虽然不会导致用户线程停顿，但会因为占用一部分线程(CPU资源)而导致应用程序变慢，总吞吐量降低。 无法处理浮动垃圾，可能出现Concurrent Mode Failure失败而导致另一次Full GC的产生。浮动垃圾是指由于CMS并发清理阶段用户线程还在运行着，伴随着程序运行自然会有新的垃圾不断产生，这一部分垃圾出现在标记过程之后，CMS无法在打次收集中处理掉他们，只好留待下一次GC时再清理掉。CMS运行期间预留的内存无法满足程序需要，就会出现一次Concurrent Mode Failure失败，则将启动后备预案：临时请用Serial Old收集器重新进行老年代的垃圾收集，造成停顿时间加长，性能下降。 基于“标记-清除”算法，会有大量的空间碎片产生，往往会出现老年代还有很大空间剩余，但无法找到足够大的连续空间来分配当前对象，不得不提前触发一次Full GC。 G1收集器 面向服务端应用的垃圾收集器 具备特点： 并行与并发：G1能充分利用多CPU、多核环境下的硬件优势，使用多个CPU(CPU或者CPU核心)来缩短停顿时间，部…