性能优化的关键并不在于怎么进行优化,而在于怎么找到当前系统的性能瓶颈。
高性能硬件上的程序部署策略
一个web文档服务器使用了一个64位虚拟机运行系统,堆大小设置为12G,默认使用吞吐量优先收集器。访问量不算大,但是每10多分钟就会无响应十几秒,这个时间是绝对无法忍受的。查看日志发现是gc导致的停顿。文档会读入到内存,进入老年代,12G的内存回收起来需要很长的时间。
这里的程序设计首先就有一定的问题,文档对象都是一些大对象,直接在内存中存储本身就不是一个好的方案。32位的虚拟机在性能方面要比64位的好一些,64位JDK如果产生dump文件将会非常大的。
后来改为在一台物理机上运行多个32位虚拟机,前端建立反向代理。并且改用CMS收集器。问题解决了。
堆外内存导致的溢出错误
一个系统运行一会就会经常的报出OOM,设置参数让虚拟机在产生OOM时生成堆转储快照,没反应。只能开着jstat盯着,发现报错时,各区域内存都正常,很平稳。后来发现是机器内存2G,虚拟机堆分配1.6G。而系统大量使用NIO,分配了过多堆外内存导致的。
除了java堆和永久代之外,下面这些区域还会占用较多的内存,这里所有的内存总和会受到操作系统进程最大内存限制:
● Direct Memory : 可通过-XX : MaxDirectMemorySize调整大小,内存不足时拋出OutOfMemoryError或者OutOfMemoryError : Direct buffer memory。
● 线程堆栈:可通过-Xss调整大小,内存不足时拋出*Error (纵向无法分配, 即无法分配新的栈帧)或者OutOfMemoryError : unable to create new native thread (横向无法分配 ,即无法建立新的线程)。
● Socket缓存区:每个Socket连接都Receive和Send两个缓存区,分别占大约37KB和25KB内存,连接多的话这块内存占用也比较可观。如果无法分配,则可能会拋出IOException : Too many open files异常。
● JNI代码 :如果代码中使用JNI调用本地库,那本地库使用的内存也不在堆中。
● 虚拟机和GC:虚拟机、GC的代码执行也要消耗一定的内存。
外部命令导致系统缓慢
一个系统监控时,cpu使用率非常高,但是系统本身的进程cpu占用率很低,这很不正常。后来发现程序很次用户请求都要执行一个外部的shell脚本。利用的是Runtime.getRuntime().exec()方法,频繁的这个操作很消耗cpu和内存资源。Java虚拟机执行这个命令的过程是:首先克隆一个和当前虚拟机拥有一样环境变量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程。如果频繁执 行这个操作,系统的消耗会很大,不仅是CPU, 内存负担也很重。
服务器JVM进程崩溃
一个系统接入了一个外部服务,但是有几个接口不可用,导致很多请求长达3分钟才返回连接断开的错误。为了不被拖累,采用了异步方式访问,就导致了越来越多的请求没有被处理完成,OOM。后来修复了不可用接口,同时改为生产者-消费者模式处理。
不恰当数据结构导致内存占用过大
有一个后台RPC服务器,使用64位虚拟机,平时对外服务的Minor GC时间约在30毫秒以内,完全可以接受。但业务上需要每10分钟加载一个约80MB的数据文件到内存进行数据分析,这些数据会在内存中形成超过100万个HashMap
由Windows虚拟内存导致的长时间停顿
有一个带心跳检测功能的GUI桌面程序,每15秒会发送一次心跳检测信号,如果对方30秒以内都没有信号返回,那就认为和对方程序的连接已经断开。程序上线后发现心跳检测有误报的概率,查询日志发现误报的原因是程序会偶尔出现间隔约一分钟左右的时间完全无日志输出,处于停顿状态。观察到这个GUI程序内存变化的一个特点,当它最小化的时候,资源管理中显示的占用内存大幅度减小,但是虚拟内存则没有变化,因此怀疑程序在最小化时它的工作内存被自动交换到磁盘的页面文件之中了。
在MSDN上查证后确认了这种猜想,因此,在Java的GUI程序中要避免这种现象,可以加入参数“-Dsun.awt.keepWorkingSetOnMinimize=true”来解决。