JVM性能调优

JVM性能调优

高性能硬件上部署程序

策略

通过64位JDK来使用大内存
控制Full GC时间

对于交互性强、对停顿时间敏感的系统,可以给Java虚拟机超大内存的前提是有把握把应用程序的GC频率控制的足够低,譬如十几个小时乃至一天才出现一次Full GC,这样可以在深夜定时任务执行Full GC甚至重启服务器来保持内存空间在一个稳定的水平。

控制Full GC频率

控制Full GC频率关键是应用中绝大多数对象能否符合“朝生夕死”的原则,即大多数对象的生存时间不应太长,尤其是不能有成批量的、长生存时间的大对象产生,这样才能保障老年代空间的稳定。

控制对象的生命周期

在大多数网站形式的应用里,主要对象的生存周期应该是请求级或是页面级的,会话级和全局级的长生命对象相对很少。

面临问题
  • 内存回收导致的长时间停顿
  • 64位JDK的性能测试结果普遍低于32位JDK
  • 需要保证程序足够稳定,如果产生几十G的堆转储快照也无法分析
  • 相同程序在64位JDK内存消耗一般比32位JDK要大,这是由于指针膨胀以及数据对齐补白等因素造成的
使用若干个32位虚拟机建立逻辑集群来利用硬件资源
工作方式

在一台物理机上启动若干个服务器进程,启动不同的端口,在使用负载均衡服务器(根据SessionID分配)使用反向代理来分配请求。

面临问题
  • 尽量避免节点竞争全局的资源,最典型的就是磁盘竞争,如果每个节点同时访问一个磁盘文件(尤其是并发写操作)时,容易导致I/O异常
  • 很难高效的利用某些资源池
  • 每个节点都受32位的内存限制(windows: 2GB, linux: 4GB)
  • 大量使用本地缓存,可以共用一份集中式缓存

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

解决方案

被集群共享的数据可以允许频繁的读操作,但是要避免频繁的写操作。

堆外内存导致的内存溢出

问题表现

服务器不定时抛出OOM异常,服务器不一定每次都会出异常,开大内存也没有用,并且异常会更频繁,新生代和老年代内存也很稳定。查看系统日志:java.nio.DirectByteBuffer报出内存溢出,即直接内存(Direct Memory)内存溢出。Direct Memory不能像新生代和老年代一样,内存不足时通知垃圾收集器进行垃圾回收,他只能等老年代满了后Full GC,然后顺便清理内存的废弃对象。

容易造成该现象的内存区域

  • Direct Memory
    • NIO有大量的操作
  • 线程堆栈
    • 无法分配新的堆栈时抛出OOM
    • 无法建立新的线程时抛出StackOverFlowError
  • Socket 缓冲区
    • Receive缓冲区(约37KB),如果无法分配可能会抛出IOException
    • Send缓冲区(约25KB),如果无法分配可能会抛出IOException
  • JNI代码
    • 如果代码使用JNI调用本地方法,那本地库使用的内存也不在堆中
  • 虚拟机和GC
    • 虚拟机和GC执行的代码也需要消耗一定的内存

外部命令导致系统缓慢

问题表现

做大压力测试时发现请求响应很慢,查出CPU使用率很高,并且占用绝大多数的CPU资源不是应用系统本身,查看最消耗CPU资源的是“fork”系统调用。

导致原因

每个用户的请求都要执行一个shell脚本去获得系统信息,调用脚本是通过java的Runtime.getRuntime.exec()方法来调用的,它在Java虚拟机中是非常消耗资源的操作,即使外部命令很快就能执行完毕,频繁调用时创建进程的开销也非常客观。

虚拟机执行步骤

  • 克隆一个和当前虚拟机拥有一样环境变量的进程
  • 用这个进程去执行外部命令
  • 退出进程

改进方案

直接使用Java的API去掉用

服务器JVM进程崩溃

问题表现

在服务器正常运行一段时间后,发现运行期间频繁出现集群节点的虚拟机进程自动关闭的现象,留下了一个**.log文件后,进程就消失了。每台服务器上的节点都出现过进程崩溃的现象。研究发现,每个崩溃的虚拟机节点在崩溃前不久都抛出了相同的SocketException: Connection reset

导致原因

这是一个远端断开连接的异常,由于工作流要通过Web服务通知OA门户系统代办事项有变化,但是调用后要长达3分钟后才能返回,并且返回都是连接失败,为了不被OA拖累,使用异步的方式调用Web服务,但是由于两边服务速度的完全不对等,导致等待的线程和Socket连接越来越多,最终导致虚拟机进程崩溃。

改进方案

通知OA门户方修复无法使用的集成接口,并将异步调用改为生产者/消费者模式的消息队列模式。

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

问题表现

RPC服务器,使用的ParNew + CMS收集器组合,平时对外服务的Minor约在30ms以内。但业务每十分钟加载一个约80MB的数据文件到内存中进行数据分析,这些数据会在内存中形成100W个HashMap

导致原因

在平时Minor GC后,Eden区和Survivor区都很空闲,但是在数据分析时,800M的Eden很快就被填满然后触发Minor GC,但GC后还有大量的对象存活下来,由于ParNew使用的是复制算法,所以复制到Survivor区并维持这些对象的引用的正确就成了一个沉重的负担,因此GC暂停时间变长。

其实根本原因是HashMap

改进方案

  • 仅从GC调优角度
    • 可以将Survivor区去掉,让新生代第一次Minor GC就进入老年代
  • 更换数据结构

参考资料

周志明. 深入理解JVM虚拟机

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值