Walt You - 行是知之始

《深入理解Java虚拟机:JVM高级特性与最佳实践--第二版》学习日志(二)Part 4:调优案例分析与实战

2018-06-04
 

“纸上得来终觉浅”,来从几个实例来实战一下之前学到的知识吧。


学习资料主要参考: 《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)》,作者:周志明



案例分析

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

过多的大对象存放在老年代中,会导致过于频繁的full gc,对应用程序会造成无法忍受的停顿。

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

那么对于高性能硬件上部署程序,目前主要有两种方式:

  • 通过64位JDK来使用大内存
  • 使用若干个32位虚拟机建立逻辑集群来利用硬件资源

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

构建全局缓存的集群,如果不注意使用方式,会产生内存溢出问题。

这时,可以使用 -XX:+HeapDumpOnOutOfMemoryError参数允许,可以查看heapdump文件。

发现是网络堵塞时,重发数据阻塞在内存中所导致的。

被集群共享的这类数据,可以允许读操作频繁,但是不应当有过于频繁的写操作,这样子会带来很大的网络同步的开销。

3. 堆外内存导致的溢出错误

当GC并不频繁,各个区域都正常工作时,我们就要考虑是不是堆外内存导致了溢出。

垃圾收集时,虚拟机对直接内存的回收,只发生在对老年代时进行full gc时,这时虚拟机会顺便清理直接内存。如果等不到full gc,直接内存抛出内存溢出异常,然后再catch块中调用System.gc。但是如果开启了“-XX:+DisableExplicitGC”开关,就只能抛出异常了。

除了Java堆和永久代之外,还有以下区域会占用较多的内存:

  • Direct Memory: 可以通过 -XX:MaxDirectMemorySize 调整大小。内存不足时,抛出OutOfMemoryError 或者 OutOfMemoryError: Direct buffer memory。
  • 线程堆栈:可以通过-Xss调整大小,内存不足时,抛出StackOverflowError,或者OutOfMemoryError:unable to create new native thread。
  • Socket 缓存区: 每个Socket链接都Receive和Send两个缓存区,分别占用37KB和25KB内存,连接多的话,内存占用也很多。如果无法分配内存,会抛出IOException: Too many open files异常。
  • JNI代码:如果代码中使用JNI调用本地库,那本地库使用的内存也不在堆中
  • 虚拟机和GC:虚拟机、GC的代码执行也要消耗一定的内存。

4. 外部命令导致系统缓慢

频繁调用外部命令,是个很耗费资源的操作。

当调用外部命令时,首先克隆一个和当前虚拟机拥有一样环境变量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程。如果频繁的执行这个操作,系统的消耗会很大,不仅是CPU,内存负担也很重。

5. 服务器JVM进程崩溃

当使用异步请求调用服务时,要当心两边服务速度相差过大,导致一方会随着时间增长,积压越来越多的Socket连接。

这时候可以考虑,使用生产者/消费者模式的消息队列来实现。

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

HashMap<Long, Long> 结构中,空间利用效率很低,只有18% = 16 / 88。

总空间:(Long(24B) * 2) + ENtry(32B)+ HashMap Ref(8B)= 88B 有效空间:8 * 2 = 16B

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

在Windows上工作的应用,被最小化时,它的工作内存被自动交换到磁盘的页面文件中了,这样发生GC时,就有可能因为恢复页面文件的操作而导致不正常的GC停顿。

可以加入参数“-Dsun.awt.keepWorkingSetOnMinimize=true”来解决。


上一篇 Hive UDF

Content