深入理解JVM(五)——JVM调优 Eclipse调优

来源:互联网 发布:phpcms 模板使用php 编辑:程序博客网 时间:2024/06/07 07:37

在开发,测试环境,我们可以通过JConsole或者VisualVM去监控Java程序的运行时,但是生产环境是不会给你安装这些应用的。JDK1.6之后,JMX管理默认都是开启的,所以你也可以通过JMX管理达到监控和调优的目的。这也是我下一阶段的工作一部分。

大体的需求包括:

  1. 显示虚拟机进程以及进程的配置,环境信息(jps,jinfo)
  2. 监视应用程序的CPU,GC,堆,方法区以及线程信息(jstat,jstack)
  3. dump以及分析堆转储快照(jmap,jhat)
  4. 方法级的程序运行分析,找出被调用最多的,运行时间最长的方法
  5. 离线程序快照:收集程序的运行时配置,线程dump,内存dump等信息建立一个快照,可以将离线发给开发者进行BUG反馈

下面介绍JVM优化案例

高性能硬件上的程序部署策略

背景:日PV量15万左右的在线文档类型网站,硬件升级前使用32位系统1.5GB的堆,用户刚到网站比较缓慢,不会停顿。

现状:最近更换硬件,新的硬件为4个CPU,16GB物理内存,操作系统64位的JDK1.5,Resin作为Web服务器。设置-Xmx和-Xms参数将Java堆固定在12GB。

问题:使用一段时间后并不理想,网站经常出现不定期长时间失去响应的情况。

原因:监控服务器运行情况发现网站失去响应是由GC停顿导致的,虚拟机运行在Server模式,默认吞吐量优先收集器,回收12GB的堆,一次Full GC的停顿时间高达14秒。由于程序设计是文档访问,很多大的对象都进入老年代,即使有12G的堆,也会被很快耗尽。

方案:选择多个32位虚拟机建立逻辑集群来利用硬件资源(部署多个应用服务进程,在前端搭建一个负载均衡器,以反向代理的方式来分配请求)

实施:建立了5个32位JDK的逻辑集群,每个进程按照2GB内存计算(堆固定在1.5GB),占用了10GB内存。建立Apache服务器作为前端均衡代理访问门户。考虑用户对响应速度关心,并且文档服务器压力集中在磁盘和内存访问,CPU资源敏感较低,改为CMS收集器。

使用64为管理大内存有以下问题:

  • 内存回收导致的长时间停顿
  • 现阶段,64位JDK的性能测试结果普遍低于32位JDK
  • 需要保证程序足够稳定,因为这种应用堆溢出几乎无法产生的堆转储快照,因为太大了(十几GB甚至更大的Dump文件)
  • 相同程序在64位消耗的内存比32位大,由于指针膨胀,以及数据类型对齐补白等

集群间同步导致的内存溢出

背景:基于B/S的MIS系统,硬件为两台2个CPU,8GB内存的HP小型机,服务器为WebLogic9.2,每台机器有3个实例。节点之间没有session同步,但有需求需要实现部分数据在节点之间共享。

现状:采用JBoosCache构建了一个全局缓存,全局缓存启动后,服务器正常使用了一段时间,但最近不定期出现内存溢出的问题。

问题:内存溢出

原因:加上参数-XX:+HeapDumpOnOutOfMemoryError运行一段时间,最近一次溢出,分析heapdump文件,发现jgroups异常。JBoosCache是通过jgroups进行集群间通讯的,使用协议栈的方式收发数据包,信息在传输失败需要重发,在收到确认消息之前,所有的消息必须在内存中保留。而MIS中有一个全局的Filter,每次收到请求会更新最后操作时间,并且同步到每个节点,导致集群之间交互频繁,重发数据在内存中不断堆积。

方案:JBoosCache官方讨论过内存溢出的问题并在后续版本中改进。MIS在同步最后操作时间,实现方案上的缺陷改进。

堆外内存导致的溢出错误

背景:学校的小型项目,基于B/S的电子考试系统,实现客户端能从服务端接受数据,逆向Ajax技术,Jetty7.1.4 普通PC机i5 CPU 4GB内存 32位Windows操作系统。

现状:服务器不定时抛出内存溢出异常

问题:内存溢出

原因:加上参数-XX:+HeapDumpOnOutOfMemoryError运行一段时间,没有任何反应,GC也并不频繁,Eden,Survivor,老年代都很稳定。内存溢出后,从系统日志找到堆栈。
操作系统对每个进程管理的内存时有限制的,32位的系统,最大使用的内存时2GB,JVM使用了1.6GB,剩下的只有0.4GB。虽然收集器也会堆Direct Memory区域进行回收,但是只是在Full GC后顺便回收。而且Direct Memory也不会通知收集器,直到异常。
本系统恰好有大量的NIO操作使用到Direct Memory内存

外部命令导致系统缓慢

背景:一个数字校园应用系统,大并发测试时发现请求响应比较慢。

问题:请求响应比较慢

原因:通过mpstat工具发现CPU使用率很高,并且绝大数资源消耗并不是系统本身。DTrace运行后发现消耗CPU最多的时fork系统调用,而fork时Linux用来产生新进程的,JVM只有线程,不应该有进程。
系统设计时每个用户请求都需要执行一个外部shell脚本,通过Java的Runtime.getRuntime().exec()方法调用,这种方法能达到目的,但是非常消耗资源。

Runtime.getRuntime().exec()是首先克隆一个和当前虚拟机拥有一样环境标量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程。不仅CPU资源消耗大,内存负担也很重

服务器JVM进程崩溃

等待的线程和Socket连接数太多,超过虚拟机的承受能力,导致虚拟机崩溃。

不恰当的数据结构导致内存占用过大

背景:一台RPC的服务器,平时地外服务,GC在30毫秒左右,完全可以接受。

问题:业务上需要每10分钟加载一个约80MB的数据文件到内存中,会有超过100万个HashMap,键值对为Long,Long,这段时间GC时间会超过500ms.

原因:复制算法对在对付朝生夕灭的对象时很高效。但是在分析800MB数据期间,Eden空间很容易就满了,而对象还都存活,复制大量的对象到Survivor并且更新引用,导致GC暂停明显。

方案:从 GC优化的角度,将Survivor空间去掉。(-XX:SurvivorRatio-65535,-XX:MaxTenuringThreshold=0或者-XX:AlawaysTenure);从程序设计的角度,只有HashMap存放的key和value有效,不需要使用装箱类。本来只有2*8Byte的,装箱之后多了8Byte的MarkWord,8Byte的Class指针,再加上8Byte的数据。组装成Entry,有多了16Byte的头,8Byte的next,4Byte的hash字段,4Byte的空白填充。空间利用率是16Byte/88Byte=18%,实在太低了。

由Windows虚拟内存导致的长时间停顿

问题:Java的GUI程序在程序最小化时,它的工作内存被自动交换到磁盘的页面文件中了,这样GC时就因为恢复页面文件的操作而导致不正常GC停顿

方案:Java的GUI程序要避免这种情况发生,可以加入参数“-Dsun.awt.keepWorkingSetOnMinimize=true”,这个参数很多桌面程序都会用到,如VisualVM,用来保证程序在恢复最小化时能够立即响应。

Eclipse调优

JVM调优不仅限于服务端应用,也可以自己调优测试,如Eclipse应用。

运行平台32位Windows系统,i5 CPU,4GB内存。

初始配置文件在eclipse.ini中,作者改了JDK路径,设置最大堆512MB,开启了JMX管理,其它未做改动。

由于安装了很多插件,启动较慢。

eclipse代码规模较大,增加永久代内存,默认时64MB
-Dcom.sun.management.jmxremote
-Dosgi.requiredJavaVersion=1.5
-Xmx512MB
-XX:MaxPermSize=256MB
去掉类加载时的字节码验证,如果觉得eclipse编译是的字节码是可靠的话
-Xverify:none
通过VisualVM发现启动eclipse时,频繁GC,由于新生代内存分配不足导致的

新增的配置如下:
-Xverify:none
-Xmx512m
-Xms512m
-Xmn128m
-XX:PermSize=96m
-XX:MaxPermSize=96m

最后还可以按照之前的文章,为eclipse配置垃圾收集器,为了减少响应时间,可以选择CMS收集器,当然优化的时候可以配合VisualVM工具,来观察优化的结构,进而调整优化参数

最终新增的配置如下:
-Xverify:none
-Xmx512m
-Xms512m
-Xmn128m
-XX:PermSize=96m
-XX:MaxPermSize=96m
-XX:+DisableExplicitGC
-Xnoclassgc
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=85