JavaEE应用服务器JVM调优

1为什么要谈Java虚拟机JVM

近期,益体网性能出现问题,不得不关注优化问题,涉及方方面面。JVM的自动垃圾收集(Garbage Collection)使得开发人员无需关注垃圾收集的细节,不过,当内存问题成为系统瓶颈的时候,我们就需要了解一下JVM的垃圾收集机制了。

2 JVM分代

应用程序中生成的对象绝大部分都是临时对象,属于那种生的、快死的,来也匆匆,去也匆匆,当然也有伴随应用程序的生命周期而存在的对象,鉴于对象的生命周期的不同,JVM的内存是分代(Generation)管理的。分代情况如下图所示:

JAVA内存由 Heap Perm 组成,其中堆Heap的描述如下:

Heap ::= Young + Old

Young ::= Eden + Survivor

Survivor ::= from + to

 

Young 是年轻代堆HeapOld 是年老代堆Heap

Eden 是活跃的、第一短期存活对象内存段,Survivor 是活跃的、短期存活对象内存段。

2.1年轻代Young

年轻代(Young Generation)包含伊甸园(Eden)和2段幸存者空间(Survivor Spaces),伊甸园当然是新生对象的乐园(只在乎曾经拥有,不在乎天长地久。也许看作前线战场更为合适:),也有部分大对象直接分配在年老代)。

而幸存者空间是用来保存上次GC之后依然存活的对象,经过战事的洗礼,能够存活下来实属不易,说明你是一个优秀的士兵,特此晋升一下,并且分配给你一间前线作战指挥所。2段空间保持其中一个为空,后面再详细说明缘由和作用。

2.2年老代Old

年老代(Tunured Generation)中存放的是那些从幸存者空间经过多次GC依然存活的对象,即:存放应用程序中生命周期长的内存对象,他们是由年轻的军官经过许多战事的磨砺而来,已经成为老一辈的无产阶级革命家。

2.3永久代Perm

永久代(Permanent Generation)是保存JVM描述对象的结构数据,主要用来放JVM自己的反射对象,如:类定义,方法定义的信息。这里属于战争的大后方。

3垃圾回收

3.1 GC思想

当一个对象不再被引用时,该对象就是一个不可达的对象,dead的了,战死沙场了,就成为垃圾garbage了。GC就是通过一定的算法,判断一个对象可达与否,通常是遍历所有可达的对象,剩下的对象就是认为是garbage,然后销毁这些garbage,释放内存。

 

既然内存是分代管理的,GC也就自然而然是分代收集的。

 

年轻代垃圾回收GC有多种算法,例如:单线程Copyingscavenge的算法,将所有仍然生存的对象搬到另外一块内存后,整块内存就可回收。这种方法有效率,但需要有一定的空闲内存,拷贝也有开销。因为年轻代进进出出的人多而频繁,所以年轻代的GC也就频繁一点,但涉及范围也就年轻代这点弹丸之地内的对象,其特点就是少量,多次,但快速,称之为minor collection

当年轻代的内存使用达到一定的阀值时,minor collection就被触发,Eden及某一Survior Space(即:From space)之内存活的的对象被移到另一个空的Survior space(即:To space)中,然后from spaceto space角色对调。当一个对象在两个survivor space之间移动过一定次数(达到预设的阀值)时,它就足够old了,够资格呆在年老代了。

当然,如果survivor space比较小不足以容下所有live objects时,部分live objects也会直接晋升到年老代。

 

Survior spaces可以看作是Eden和年老代之间的缓冲,通过该缓冲可以检验一个对象生命周期是否足够的长,因为某些对象虽然逃过了一次minor collection,并不能说明其生命周期足够长,说不定在下一次minor collection之前就挂了。给一个士兵晋升是要有一定的考察期的:)。这样一定程度上确保了进入年老代的对象是货真价实的,减少了年老代空间使用的增长速度,也就降低年老代GC的频率。

 

年老代Old和永久代Perm垃圾回收GC有多种算法,例如:mark-compact算法,将活着的对象标记出来,然后搬迁到一起连成大块的内存,其他内存就可以回收了。这种方法不需要占用额外的空间,但速度慢些,但减少内存要求。当年老代或者永久代的内存使用达到一定阀值时,一次基于所有代的GC就触发了,其特定是涉及范围广(量大),耗费的时间相对较长(较慢),但是频率比较低(次数少),称之为major collection(full collection)

通常,首先使用针对年轻代的GC算法进行年轻代的GC,然后使用针对年老代的GC算法对年老代和永久代进行GC

3.2回收器算法

3.2.1 serial collector

针对young generation的串行垃圾收集器,使用stop-the-world(就是暂停整个应用程序的执行)的形式,利用单线程通过复制live objectssurvivor spacetenured generation的方法来进行垃圾收集。

3.2.2 p arallel scavenge collector

针对young generation的并行垃圾收集器,利用多个GC线程来进行垃圾收集,每个线程的GC方法和serial collector一样。

3.2.3 p arallel new collector

针对young generation的增强的并行垃圾收集器,以便可以和CMS一起使用。

3.2.4 serial old collector

针对tenured generation的串行垃圾收集器,使用stop-the-world形式,利用单线程通过mark-sweep-compact的方法进行垃圾收集。

parallel old collector:针对tenured generation的并行垃圾收集器,利用多线程进行垃圾收集,方法和serial old collector一样。

3.2.5 p arallel compacting collector

对于young generation使用和parallel new collector一样的算法,对于tenured generation使用了新的算法(mark-summary-compact),该收集器用来替代parallel new collectorparallel old collector

3.2.6 concurrent mark-sweep collector

对于young generation使用和parallel new collector一样的算法,对于tenured generation使用跟应用程序并发的方式,收集期间也有引起stop-the-world的暂停Mark阶段,也有伴随着应用程序运行的并发Mark和并发Sweep阶段,降低了应用程序暂停的时间。

可以看出这些垃圾收集器分为3种类型:串行,并行,并发;串行的就是单线程的,并行的就是多线程的,串行并行都是stop-the-world的,而并发是多线程的,但不完全是stop-the-world

 

垃圾回收GC分多级,0级为完全(Full)的垃圾回收,会回收OLD段中的垃圾;1级或以上为部分垃圾回收,只会回收NEW中的垃圾,内存溢出通常发生于0级完全回收中OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。

3.3内存泄露OOM

3.3.1 内存申请过程

当一个URL被访问时,例如:输入益体网网址http://www.et5155.com/ 准备购买益体德尔惠体育用品,Java内存申请过程如下:

AJVM会试图为相关Java对象在Eden中初始化一块内存区域;

B. Eden空间足够时,内存申请结束。否则到下一步;

CJVM试图释放在Eden中所有不活跃的对象(这属于1级或更高级的垃圾回收GC, 释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区;

DSurvivor区被用来作为EdenOLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区;

E. OLD区空间不够时,JVM会在OLD区进行完全的垃圾收集(0级)

F. 完全垃圾收集后,若SurvivorOLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误(OOM)”。一种情况是JVM设置过小,另一种常见的情况是JavaEE应用内存泄露。

3.3.2 内存泄露原因

1OLD段溢出:

这种内存溢出是最常见的情况之一,产生的原因可能是:

1) 设置的内存参数过小(ms/mx, NewSize/MaxNewSize)

2) 程序问题:单个程序持续进行消耗内存的处理,如循环几千次的字符串处理,对字符串处理应建议使用StringBuffer。此时不会报内存溢出错,却会使系统持续垃圾收集,无法处理其它请求,相关问题程序可通过Thread Dump获取单个程序所申请内存过大,有的程序会申请几十乃至几百兆内存,此时JVM也会因无法申请到资源而出现内存溢出,对此首先要找到相关功能,然后交予程序员修改,要找到相关程序,须在Apache日志中寻找或是第三方工具,例如:Oracle JRMC套件。

 

Java对象使用完毕后,其所引用的对象却没有销毁,使得JVM认为他还是活跃的对象而不进行回收,这样累计占用了大量内存而无法释放。

 

2. Perm段溢出:

通常由于Perm段装载了大量的Servlet类而导致溢出,目前的解决办法:

1) PermSize扩大,一般 256M 能够满足要求:-XX:PermSize= 256m -XX:MaxPermSize= 256m

2) 若别无选择,则只能将servlet的路径加到CLASSPATH中,但一般不建议这么处理;

 

3. C Heap溢出

系统对C Heap没有限制,故C Heap发生问题时,Java进程所占内存会持续增长,直到占用所有可用系统内存。

4 JVM调优

       -X是非标准参数,-XX是非稳定参数,但在生产环境中很有用,使用之前做好上线测试。

       Xmx = Xms = Young + Old

TotalHeapSize = Xmx + Perm

4.1 ms/mx

定义YOUNG+OLD段的总尺寸,msJVM启动时YOUNG+OLD的内存大小;mx为最大可占用的YOUNG+OLD内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。

       如:-Xms 1024m -Xmx 1024m

4.2 NewSize/MaxNewSize

定义YOUNG段的尺寸,NewSizeJVM启动时YOUNG的内存大小;MaxNewSize为最大可占用的YOUNG内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。

       如:-XX:NewSize= 256m -XX:MaxNewSize= 256m

4.3 PermSize/MaxPermSize

定义Perm段的尺寸,PermSizeJVM启动时Perm的内存大小;MaxPermSize为最大可占用的Perm内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。

4.4 SurvivorRatio

设置Survivor空间和Eden空间的比例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值