[TOC]
1 理论
1.4 垃圾收集算法
1.5 垃圾收集器
2 工具
3 实战
3.1 大内存硬件上的程序部署策略
如果实际内存有16G,将-Xmx
和-Xms
参数将Java 堆大小固定为12G,程序运行一段时间后,会常常不定期出现长时间失去响应,程序中涉及将磁盘中的文档提取到内存中,这些大对象在分配时直接进入了老年代,没有在 Minor GC 中被清理掉,很快就造成了内存不够的现象导致程序每隔几分钟出现十几秒的停顿。
解决办法是堆内存重新缩小到1.5G或者2G。
每一款Java虚拟机中的每一款垃圾收集器都有自己的应用目标与最适合的应用场景,如果在特定
场景中选择了不恰当的配置和部署方式,自然会事倍功半。目前单体应用在较大内存的硬件上主要的
部署方式有两种:
通过一个单独的Java虚拟机实例来管理大量的Java堆内存。
同时使用若干个Java虚拟机,建立逻辑集群来利用硬件资源。
控制Full GC频率的关键是老年代的相对稳定,这主要取决于应用中绝大多数对象能否符合“朝生夕灭”的原则,即大多数对象的生存时间不应当太长,尤其是不能有成批量的、长生存时间的大对象产 生,这样才能保障老年代空间的稳定。
如果要使用单个Java实例来管理大内存,还需考虑下面可能面临的问题:
回收大块堆内存而导致的长时间停顿,自从G1收集器的出现,增量回收得到比较好的应用,
这个问题有所缓解,但要到ZGC和Shenandoah收集器成熟之后才得到相对彻底地解决。
大内存必须有64位Java虚拟机的支持,但由于压缩指针、处理器缓存行容量(Cache Line)等因素,64位虚拟机的性能测试结果普遍略低于相同版本的32位虚拟机。
必须保证应用程序足够稳定,因为这种大型单体应用要是发生了堆内存溢出,几乎无法产生堆转储快照(要产生十几GB乃至更大的快照文件),哪怕成功生成了快照也难以进行分析;如果确实出了问题要进行诊断,可能就必须应用JMC这种能够在生产环境中进行的运维工具。
相同的程序在64位虚拟机中消耗的内存一般比32位虚拟机要大,这是由于指针膨胀,以及数据类型对齐补白等因素导致的,可以开启(默认即开启)压缩指针功能来缓解。
3.2 集群间同步导致的内存溢出
被集群共享的数据要使用类似JBossCache这种非集中式的集群缓存来同步的话,可以允许读操作频繁,因为数据在本地内存有一份副本,读取的动作不会耗费多少资源,但不应当有过于频繁 的写操作,会带来很大的网络同步的开销。
3.3 堆外内存导致的溢出错误
1 | [org.eclipse.jetty.util.log] handle failed java.lang.OutOfMemoryError: null |
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁的使用,而且也可能导致OutOfMemory异常出现。
在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器寻址空间的限制,一般服务 器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,但经常忽略掉直接内存,使得 各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现 OutOfMemoryError异常。