Jade Dungeon

常见问题与调优

常见问题

高端硬件利用

大对象直接进入老年代,这样即使一次性的也不会马上被销毁掉。等老年代Full GC的时候 才干掉。如果是64位JVM加大内存,Full GC要花很长时间……

64位系统问题:

  • 内存回收时间长
  • 性能低于32位
  • 内存大,产的转储文件也大,十几个G的转储文件几乎无法分析
  • 指针膨胀与数据类型对齐导致占用内存大很多

改用多个32位的JDK也会有些问题:

  • 对全局资源如磁盘的竞争、每个要有自己的连接池与缓存
  • 限制内存最多4G

集群同步引起内存溢出

带着-XX:+HeapDumpOnOutOfMemoryError等溢出后看dump文件。发现JBossCache全局缓存 为了同步保存缓存内容以备重发,在不同步情况下缓存太多内容。

堆外内存导致溢出错误

带着-XX:+HeapDumpOnOutOfMemoryError也没有dump文件,GC不频繁,Eden、Survivor、 老年代、永久代都占不多。所以堆外内存导致的溢出看不出来。

操作系统给每个进程的空间有限,如Windows最大2G,如果Java堆点了1.6GB那堆外只有剩下 的400M可用了。而且堆外内存不会自动回收,只在老年代Full GC后才会调用回收。而且在 打开了-XX:+DisableExplicitGC的情况下还不会回收。

注意会占用较大内存的区域:

Direct Memory:通过-XX:MaxDirectMemorySize调整,不足时抛出OutOfMemoryErrorOutOfMemoryError:Direct buffer memory

线程堆栈:通过-Xss调整大小,内存不足时抛出StackOverflowError(栈深度)或 OutOfMemoryError: unable to create new native thread(无法建立新线程)

Socket缓存区:每个Socket连接都有收(37KB)与发(25KB)两个缓存区,无法分配会抛出 IOException: Too many open files

JNI代码:本地库在调用内存不在堆中。

虚拟机和GC:别忘记这两个也是要点内存的。

外部命令调用导致系统缓慢

Runtime.getRuntime().exec()调用本地命令非常消耗资源:先克隆一个和虚拟机环境 变量一样的进程,用它调用外部命令,然后再退出。

这种情况下用mpstat看到操作系统调用很高,Solaris的Dtrace看到占用CPU最大的是 fork系统调用。

与其他系统的IO连接太多

通过Socket连接其他系统的调用,对方系统慢,Socket连接越来越多,JVM崩溃。

常用的加速手段

字节码验证

对于已经有很多人在用的程序,可以在加载阶段关闭字节码验证:-Xverify:none

虚拟机JIT编译器

频繁调用的代码判定为热代码,交由JIT(Just In Time)编译器产生本地代码。根据环境用 的优化手段可能比较激进,本地代码可能比C/C++更加快,在优化条件不成立的时候再逆 优化回来。但这会占用运行的时间,被称为「编译时间」。为了省下编译时间可以用-Xinit 禁止编译,但这得不偿失,没有优化成本地代码全都是字节码运行会很慢。

-client模式的C1编译器,-server模式下C2编译器优化更好。运行过程中即时编译的 时间更多但优化速度更快。对长时间运行的程序是个好选择。

内存空间设置

对于每个区域来说,满了以后除了调用GC还要看看有没有到最大内存限制,没有到还要扩大 。所以会有其实只要扩空间不用调GC的情况,可以把开始空间大小和最大空间设一样大。

  • 新生代没有初始大小和最大设置,只有一个大小设置:-Xmn
  • 老年代:-Xms-Xmx
  • 永久代:-XX:permSizeMax-XX:permSize

屏蔽代码中的GC调用

工具命令jstat -gccause 进程号可看到最近一次GC调用的原因,如果是显示由于代码的 显式调用System.gc()比较多,可以考虑-XX:+DisableExplicitGC屏蔽掉程序里的显式 调用。

换垃圾收集器

对于收集器调用时程序停止而且CPU占用又不高的情况,可以考虑换并发的CMS收集器(参数 -XX:+UseConcMarkSweepGC)配合ParNew(参数-XX:+UseParNewGC

由于CMS默认老年代使用68%时触发,如果这觉得这个太频繁可以调整一下,比如改成85%: -XX:+CMSInitialtingOccupancyFraction=85