JVM运行时数据区-堆

版权声明:非商业目的可自由转载,转载请标明出处 https://blog.csdn.net/u010013573/article/details/83900682

基本概念

    堆,jvm进程启动时创建,为jvm进程的所有线程共享的区域,所有Java对象和数组(jdk8+也包含字符串常量)均在堆上分配内存,所以堆的大小决定了能存放多少Java对象,当堆满或者无法容纳新创建的对象时,则需要通过垃圾收集器进行垃圾回收,在回收过程中会造成应用停顿,影响应用性能。所以在JVM性能调优中,需要在堆的大小,应用需要创建的对象的多少和大小,可容忍应用停顿时间以及目标吞吐量之间找到一个平衡点。

堆的内存分布

  • 堆根据对象的存活的时间,分为新生代Young和老年代Old。而新生代由进一步分为Eden区和两个Survivor区。而进行这种分代的原因主要是优化GC性能,因为对象大部分生命周期都很短,如果不进行分代,则在进行GC时,则要扫描整个堆,导致应用长时间停顿;而采用分代后,则规定:新创建的对象都在新生代的Eden中分配内存(如果是大对象则直接在老年代分配,具体由参数-XX:PretenureSizeThreshold指定,大于),当Eden无法容纳新对象时,则发送一次MG。
  • 初始时,两块Survivor区都是空的,分别可叫做From S和To S,其中To始终是空的,用于存放在MG时,在Eden和From S中存活的对象,这样做的目的主要是保证存活对象在连续的地址空间,避免产生内存碎片。第一次MG时,选择其中一块作为From S,将Eden中存活的对象移动过来;而在之后的MG中,则将Eden和From S中存活的对象移动到To S,然后清空Eden和From S,此时To变成From,From变成To。每进行一次MG,存活对象年龄加一,当达到MaxTenringThresHold的值(默认为15),则晋升移动到老年代。
  • 除了达到MaxTenringThresHold的大小的对象晋升到老年代这种固定情况外,还有两种动态情况:
  1. Survivor中存活的对象中,相同年龄的对象的大小的和大于Survivor空间(为From S,如From,To均为1M,则大于512K)的一半,则Surviror中大于等于这个年龄的对象全部晋升到老年代;
  2. 在发生MG时,To无法存放Eden和From中移动过来的对象时,溢出对象直接晋升到老年代,此时在gc日志可看到"premature promotion"(过早提升)。
  • 当从新生代晋升存活对象到老年代时,如果老年代没有那么多空间来容纳这些对象,则老年代发生FGC来获取空间。除了这种情况会触发FGC外,还包括:
  1. 直接在老年代分配大对象,老年代容纳不下时触发FGC,FGC后如果还是存放不下,则会出现OOM:java heap space的错误;
  2. 统计得到MG晋升到老年代的对象平均大小大于老年代可用空间时,也会触发FGC;
  3. 在CMS垃圾收集器中,出现promotion failed(从suvivor过来的对象太大,老年代存放不下)和concurrent mode failed(在老年代进行CMS时,MG有对象从suvivor晋升到老年代)时也会出现FGC;
  4. 除此之外还有Perm区空间慢,显示调用System.gc()(如使用RMI进行RPC时,默认每一小时进行一次FGC,System.gc()可用通过参数DisableExplicitGC来禁止)
  • 注意在老年代垃圾收集器中,如CMS执行时,并不是FGC,CMS是跟用户线程一起执行的,是在当老年代内存占用达到某个比例时,CMS开始工作。而FGC是在老年代无法容纳晋升的对象时触发。而MG和FGC均会发生STW造成应用停顿,只是MG时间很短可忽略。具体分代情况如图:

JVM参数解析

  • 堆大小相关:-Xms:jvm堆初始大小,默认物理内存的1/64,-Xmx:jvm堆最大分配大小,默认物理内存的1/4;动态调整默认为当当前已分配堆中,空闲少于40%则调大直到Xmx,空闲超过70%则调小直到Xms;为了性能考虑,通常将Xms于Xmx设置为一样大,从而避免每次GC后调整堆的大小。
  • 新生代:堆=新生代+老年代,新生代大小可以通过:-XX:NewSize/MaxNewSize  》 -Xmn 》-XX:NewRatio,推荐使用-Xmn,相当一次设置了NewSize/MaxNewSize。NewRatio为old和young的比例,默认值为2,-server默认为2:1,如果young分得太少,则会造成大量对象直接进入old,影响性能。经验性能优化采用3/8原则,即新生代和老年代的比值为3/5。
  • Eden与Survivor:-XX:SurvivorRatio,默认为8,即eden和survivor的比例为8:1,即from和to各占新生代的1/10。一般保持默认值,对性能影响不大。
  • 堆大小调整与GC的关系:新生代和老年代:对新生代自身而言,增大新生代MG时间增加,频率降低;减小则MG时间减少,频率增大。对老生代而言,新生代越大,则老年代越小,增大FGC次数,降低每次FGC的时间。

最佳实践

  • 查看gc日志每次MC,FGC后,整体堆大小,新生代大小,老年代大小,从而确定程序运行实际需要多大的堆,新生代多大,老年代多大。或者根据gc日志分析,如使用网站:http://gceasy.io/index.jsp#banner,分析日志,得出young,old分配大小,峰值大小等数据,从而设置合理的堆大小Xms,Xmx和Xmn。基于实际值后,调大适当的倍数再观察调整,直到最优倍数。具体来说为确定一个对象活跃数,即程序稳定运行后长期存活在堆中对象所占空间大小,这个可以通过FGC后查看老年代对象的大小,或者更精确可以在程序稳定后,多次获取GC数据来计算一个平均值,最终以这个活跃数为参考,适当增大以下倍数:

            

  • 服务器内存和应用对吞吐量和响应时间的考虑:服务器内存足够,则增大新生代的大小;不够,则适当增大Survivor的大小,而Survivor的具体大小,则需要根据MC之后,存活的对象的大小多次调整,计算公式:

       Survivor 空间计算公式: survivor 空间大小 = -Xmn[value] / (-XX:SurvivorRatio=<ratio> + 2)  

  • 吞吐量优先的应用,则尽可能调大新生代,垃圾回收使用并行;响应时间优先的应用,则尽可能设大新生代,直到接近应用最低响应时间限制,因为新生代越大,则发送MC的频率越大,同时减少到老年代的对象(此时老年代较小,可能FGC会增多)。

猜你喜欢

转载自blog.csdn.net/u010013573/article/details/83900682