c4rt1y

jvm原理和调优

0x01 JVM简介

JVM是独立于Java语言的一套规范以及一个Class解释执行的平台软件,以其性能精良、稳定高效,赢得了大型应用的亲睐。这里我们需要学习的是关于JVM在应用的上进行调优处理。

0x02 JVM概念和优化思路

Jvm内存分配
  栈内存分配
    保存参数、局部变量、中间计算过程和其他数据。退出方法时,修改栈顶指针就可以把栈帧中的内容销毁。
    栈的优点:存取速度比较快,仅次于寄存器,栈数据可以共享
    栈的缺点:存在栈中大小、生存期是编译时确定的,导致缺乏灵活性
  堆内存分配
    堆的优点:动态分配内存空间,生存期是运行期动态分配,垃圾回收期会自动收走不再使用的空间区域
    堆的缺点:运行时动他分配内存,分配和销毁都需要占用时间,因此效率低下

jvm堆配置参数
  1、 -Xms初始堆大小
  2、 -Xmx最大堆大小
  3、一般建议设置 -Xms = -Xmx
  4、 整个堆大小=年轻代大小+年老代大小+持久代大小

new-centos

jvm新生代
  1、新生代=1个eden区+2个Survivor区
  2、-Xmn年轻代大小(1.4 or lator)
    -XX:NewSize,-XX:MaxNewSize (设置年轻代大小(for 1.3/1.4))
    默认大小为整个堆的3/8
  3、-XX:NewRatio
    年轻代(包括eden区+2个Survivor区)与年老代的比值(去除年老代)
    Xms=Xmx并且设置Xmn的情况下,该参数不需要进行设置
  4、-XX.SurvivorRatio
    Eden区与Survivor区的大小比值,设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年期待的1/10
  5、用来存放JVM刚分配的Java对象

java老年代(Tenured generation)
  1、老年代=整个堆-年轻代大小-持久代大小
  2、年轻代中经过垃圾回收没有回收掉的对象被复制到年老代
  3、年老代存储对象比年轻代年龄大的多,不乏大对象
  4、新建对象也可能直接带入老年代
    4.1、大对象,可以通过启动参数-XX:PretenureSizeThreshold=1024(单位字节,默认0)来代表超过多少时不在新生代分配,直接在老年代分配
    4.2、大数组对象,切数组中无引用的外部对象
  5、老年代大小无配置参数

java持久代(perm generation)
  1、持久代=整个堆-年轻代大小-老年代大小
  2、-XX:PermSize -XX:MaxPermSize
    设置持久代的大小,一般情况下推荐把-XX:PermSize和-XX:MaxPermSize的值设置为相同的值,因为持久代大小调整会导致堆呢村需要出发fgc
  3、存放Class、Method元信息、其大小与项目的规模、类、方法的数量有关。一般设置为128,足够,设置原则是预留30%的空间
  4、持久代的回收方式
    4.1、常量、无用的类信息
    4.2、对于无用类回收,必须保证以下3点:
      类的所有实例都已经被回收
      加载ClassLoader已经被回收
      类对象的Class对象没有被引用(即没有通过反射引用该类的地方)

JVM垃圾收集算法
  1、引用计算算法
    每个对象都有一个引用计数属性和新增一个引用时计数加1,引用设防时计数减1,计数为0时可以进行回收,该方法简单,无法解决对象相互循环引用的问题,无法精准计算计数次数。
  2、根搜索算法
    从GC Roots开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相链时,则证明该对象不可用,不可达对象
    Java中,GC Roots包括:
      虚拟机栈中引用的对象
      方法区中类静态熟悉实体引用对象
      方法区中的常量引用对象
      本地方法栈JNI引用的对象

JVM垃圾回收算法
  1、复制算法(Copying)
  2、标记清除算法(Mark-Sweep)
  3、标记整理压缩算法(Mark-Compac)

名词解释
  1、串行回收
    GCC单线程内存回收,会暂停所有用户线程
  2、并发回收
    收集是指多个GC线程并行工作,但此时用户线程是暂停的,所以Serial是串行的,Parallel收集器是并行的,而CMS收集器是并发的
  3、并发回收
    用户线程和GC线程同时执行不一定是并发,也可能是交替,但是总体上可以同时执行,不需要停顿用户线程(其实CMS中用户线程需要停顿,只是非常短,GC线程在另一个CPU上执行)

Serial回收器(串行回收器)
  1、一个单线程的收集器,只能使用一个CPU或一条县城去完成垃圾收集;在进行垃圾收集时,必须暂停所有其他工作线程,直到收集完成
  2、缺点:Stop-The-World
  3、优点:简单,对于CPU情况,由于没有多线程交互开销,反而可以更高效,是一种Client模式下的默认新生代收集器

新生代Serial回收器
  1、-XX:UseSerialGC开启
    Serial New+ Serial Old 收集器组合进行内存回收
  2、使用复制算法
  3、独占式垃圾回收
    一个线程进行GC,串行。其他工作线程暂停


老年代Serial回收器
  1、-XX:UseSerialGC开启
    Serial New+ Serial Old 收集器组合进行内存回收
  2、使用标记压缩算法
  3、串行,独占式垃圾回收
    因为内存较大原因,回收速度比新生代慢


ParNew回收期(并行回收器)
  并行回收器是一种独占式回收器,在收集过程中,应用程序会全部暂停,但是由于并行回收器使用多线程进行垃圾回收,从而在并发比较强的CPU上,他产生的挺短时间短语串行回收期,但是在单个CPU或者并发能力弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力,他的事迹表现可能比串行回收器差

  新生代Parallel Scavenge 回收器
  1、吞吐量优化回收器
    关注CPU吞吐量,即运行用户代码时间/总时间,比如JVM运行100分钟,其中运行用户代码99分钟,垃圾回收1分钟,则吞吐量是99%,这种收集器能最高效率的利用CPU适合运行后台计算
  2、-XX:+UseParallelGC开启
    使用Parallel Scavenge+Serial Old收集器组合回收垃圾,这种也是Server模式下的默认值
  3、-XX:GCTimeRatio
    设置用户执行时间占总时间的比列,默认为99,即1分钟时间处理垃圾回收
  4、-XX:+MaxGCPauseMillis
    设置GC最大停顿时间
  5、使用复制算法


CMS(并发标记清除)回收器
  运作过程分为4个阶段
    初始标记(CMS initial mark):标记GC Roots能直接关联到的对象
    并发标记(CMS concurrent mark):进行GC RootsTracing的过程
    重新标记(CMS remark):修正并发标记期间因用户程序继续运行导致标记发生改变那一部分对象的标记
    并发清除(CMS concurrent sweep)
  其中标记和重新标记两个阶段需要Stop-The-World,整个过程小号最长的时间是并发标记和并发清楚过程中收集器,都可以与用户线程同时工作

  1、标记-清除算法
    同时他是一个使用多线程并发回收的垃圾收集器
  2、-XX:ParallelCMSThreads
    手工设定CMS的线程数量,CMS默认开启的线程数量是(ParallelCMSThreads+3)/4
  3、-XX:UseCONCmARKSweepGC开启
    使用ParNew+CMS+Serial Old收集器组合进行内存回收,Serial Old作为CMS出现"Concurrent Mode Failure"失败后的后备收集器使用
  4、-XX:CMSInitiatingOccupancyFraction
    设置CMS收集器在年老戴空间被使用多少后触发垃圾收集,默认是68%,仅在CMS收集器时有效,-XX:CMSInitiatingOccupancyFraction=70
  5、-XX:+UseCMSCompacAtFullCollection
    由于CMS收集器会产生碎片,此参数设置在垃圾收集器后是否需要一次内存碎片整理过程,仅CMS收集器有效
  6、-XX:+CMSFullGCBeforeCompaction
    设置CMS收集器在进行若干次垃圾收集后进行内存碎片整理过程,通常与UseCMSCompactAtFullCollection参数一起使用
  7、-XX:CMSInitiatingPermOccupancyFraction
    设置Perm Gen使用达到多少比率出发,默认是92%

GC性能指标
  吞吐量 应用花在非GC上的时间百分比
  GC负荷 与吞吐量相反,指应用华仔GC上的时间百分比
  暂停时间 应用华仔GC Stop-The-World的时间
  GC频率 
  反应速度 从一个对象变为垃圾,这个对象的被回收时间
  一个交互式的应用要求暂停时间越少越好,然后,一个非交互的应用,GC的负荷越低越好
  一个实时系统对暂停时间和GC负荷的要求,都是越低越好
  一个嵌入式系统的Footprint越小越好

内存容量配置原则
  1、年轻代大小选择
    响应时间优先的引用,尽量设置大,直到接近系统的最低响应时间限制,在这样的情况下,年轻代收集发生频率也会最小,减少了达到老年代的对象
    吞吐量优先的应用,尽量设置大,可以达到Gbit的程度,因为对响应时间没有要求,垃圾收集可以并发进行,一般是和8CPU以上应用
    避免出现设置过小,导致YGC次数频繁,YGC对象直接进入老年代,甚至老年代满了,则会触发FGC
  2、年老代大小选择
    响应时间优先应用:年老戴使用并发收集器,所以其大小需要设置,一般要考虑并发会话率和会话持续时间等一些参数;如果堆设置小了,可能再乘内存碎片,高回收频率以及应用暂停而是用传统的标记清除方式;如果堆设置大了,收集的时间会比较漫长
    响应时间优先,并发垃圾收集信息,持久代并发收集次数,传统GC信息,注意年轻代和年老代回收的时间比例
    吞吐量优化,一般吞吐量余华引用都是一个很大的年轻代和一个很小的年老代,原因是,尽可能回收大部分的短期独享,减少中期对象,而年老代尽量存放长期存活对象。

0x03 个人感觉

针对于一个JVM调优,关联内容还是比较多的
1、系统选择,32位本身只有4G,但是如果转为jvm最多2-3G,而64位系统怎不会出现这样的问题。
2、CPU的大小也可以决定性能的好坏,建议8个CPU以上。
3、应用本身的功能性要求不同
4、开发人员的代码逻辑存在逻辑炸弹
5、关于各种JVM参数的优化,这个方向上,偏差不是特别大,不过就是某些参数取值的时候需要不断的去测试。

0x04 资料来源

老男孩第12期
http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
http://www.cnblogs.com/redcreen/archive/2011/05/05/2038331.html
GoTop