扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
1.前言 3.1 分代复制算法
Garbage Collector, 简称GC, 虽然不是Java的首创,但确实经由Java发扬光大. 正是有了GC机制,很多的Java程序员已经不再关心程序的内存问题,也体会不到了C/C++程序员的苦与乐. 但是, 在做一些较低层的模块或是一些较大型的应用时候, GC知识还是必不可少的. 笔者曾经参与的一个大型系统, 上线后系统一直运行良好,随着业务的发展,用户数也直接上升.终于当用户数到达了200万的时候, 一个跟用户数据打交道的子系统出现了重大的问题, 几乎所有的CPU时间都被用来进行垃圾回收, 系统近乎瘫痪. 这个例子说明了内存管理和合理运用GC的重要性.
2.内存模型
要弄清楚GC机制,首先要对Java的内存模型有所了解. 下图是JDK1.4虚拟机规范的内存模型
堆和栈是JVM中最重要的两个内存区域。每一个java 线程都拥有自己的内存栈,用来存放局部变量和返回值,这同传统的C语言十分类似。栈是在线程启动的时候分配的。而所有的线程都共享一个内存堆,所有运行时的内存分配都在堆上进行,换句话说,所有的对象都是在堆上创建的。堆是在JVM启动的时候分配的,它的空间由GC控制.
Java的内存分配有三种:
3. GC Performance Tuning
在我们的实践中, 常常会根据系统的实际情况,选用不同的GC collector,配合不同的参数来进行性能调整,这是一门专门的技术,称为GC Performance Tuning. Sun公司甚至有专门这样的tuning服务提供给一些客户.接下来笔者将结合自己的经验,详细谈谈如何进行tuning.
从JRE1.3开始, GC都采用了分代复制算法,这个算法根据对象的生存期将对象分成两代,新创建的对象在年轻代(Young Generation),当年轻代的内存分完的时候,GC将年轻代中少数尚未死亡的对象复制到另一块年老代(Tenured Generation),然后直接更新年轻代的指针,这个动作称为次要收集(minor collection),一次次要收集的时间取决于年轻代中存活的对象的数目,当年轻代中的对象绝大部分已经死亡的话,次要收集速度很快。当年老代的内存分完的时候,GC会进行一次主要收集(major collection),因为采用了标记收集, 这个动作很慢。所以我们Tuning的策略基本上来说就是尽量减少主要收集的次数。
3.2 Tuning的指标
Tuning的目的是提高性能,但性能指标是有很多种的, 不同的情况下对不同的指标有不同的要求. 因此,在开始Tuning之前,我们要先弄清楚究竟是要提高哪些指标,而不仅仅是一个模糊的概念-“提高性能”.
吞吐量(Throughput)是指没用在GC上的时间比例,中断(Pause)是当GC时,程序没有响应的时间。这是衡量GC性能的两个主要指标,不同的应用对于这两个指标要求是不一样的,所以调优的策略也就不一样。此外还有其它指标,Footprint,影响着可伸缩性(scalability),灵敏度(Promptness),指从一个对象死亡到它占据的内存可用的时间间隔。通常来说,吞吐量跟年轻代的大小成正比,其它指标跟年轻代成反比。一般情况下,一个generation的大小不会影响到另外一个generation的收集频率和中断时间.
3.2 GC Collector
从JRE1.4开始, 虚拟机提供四种collector以供实际选用,除了Default Collector之外,还有throughput Collector, Concurrent Collector和Incremental Collector.
Throughput Collector, 顾名思义,是为了追求最大吞吐量而设计的, 可以通过参数 –XX:+UseParallelGC指定. 它对年轻代采用了并行收集的算法, 使用多个线程来进行minor collection. 实践经验是当只有一个CPU时,使用它反而会比default collector慢,因为有线程同步的开销。当有两个CPU的时候,跟Default Collector差不多。当有三个或以上CPU时,效果会比较明显。当有多个CPU且需要提高throughput的时候我们会尝试使用它.这个collector还有几个相关的参数可以进行tuning. -XX:ParallelGCThreads=<desirednumber>,用于指定线程的数量; -因为每一个线程会保留一部分tenure来进行promotion,因此在每一部分tenure的边界会产生碎片. 使用-XX:+UseAdaptiveSizePolicy可以统计垃圾收集的这些相关信息, -XX:+AggressiveHeap让JVM自动根据机器的内存和CPU数优化各种参数,通常内存会使用机器的最大物理内存。
Concurrent Collector是为了追求最小pause而设计的,通过-XX:+UseConcMarkSweepGC指定。它对年老代使用并发收集的算法,即可能地让收集跟主程序的执行并发。通常,当程序拥有long-lived的对象(意味着tenure大),且在多CPU机器上跑的时候比较适用。这个collector第一次pause的时候,终止所有application的线程,一个线程进行收集。第二次pause的时候,终止所有application的线程,多个线程进行收集。余下的pause,一个线程进行收集,并且并发执行application线程。当机器的CPU空闲较多的时候可以采用,否则它会跟application抢系统资源。同时,因为并发操作中会产生碎片,所以tenure的保留空间要更大(因为没有好的方法去得知Eden和survivor中碎片的大小, 所以要有一个连续的剩余空间大于等于Eden的空间加上非空survivor中对象的空间,否则就有可能发生major collection)。当在并发收集的过程中,如果年老代满了,那么这时JVM会停下application线程,进行一次full GC,这是一个信号提示我们去调整参数。因为收集线程和application线程并发执行,所以可能出现收集线程认为是lived的,马上就会死亡,这种垃圾称为float garbage。这样就会影响灵敏度,粗略的规则是将tenure空间提高20%来避免这种情况。(因为tenure大了,在GC之前就会有更多的对象死亡)。float garbage在下一次GC时会被收集。我们还可以使用-XX:+UseParNewGC和-XX:+CMSParallelRemarkEnabled来进一步降低pause的时间
Incremental Collector,也是为了追求最小pause,通过-Xincgc来指定. 它通过每次minor collection时都对一部分tenure进行收集来达到这个效果,但是这样的整体throughput是最低的。当年老代较大,年轻代较小且只有一个CPU的时候可以考虑使用。当使用Default Collector并调整generation的大小不能满足pause的要求的时候,我们可以考虑它。有时它也会执行一次非递增的major collection来避免out of memory。
3.3 其他GC Performance Tuning的经验
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者