迁移 64 位虚拟机未必性能更好
业务量上升以后,需要使用的内存随之增加,而在通常 32 位系统上,单个进程占用的最大内存通常是 2GB,且考虑到堆外内存的使用,32 位机器可能无法满足内存要求,一种常见的应对方式就是换用 64 位服务器。而对于 Java,由于指针膨胀和字节对齐,同一个程序在 64 位虚拟机上占用的内存会多于 32 位虚拟机。开发者换用 64 位虚拟机后,很可能会增加虚拟机的堆大小,而这将导致 Full GC 垃圾回收的时间大大增加,导出堆快照也变得困难。
因此,使用 64 位虚拟机增加内存时,需要特别注意对内存的使用,尽量不要触发 Full GC 导致长时间停顿。另一种方法是建立 32 位虚拟机的集群来提高性能。
堆外内存溢出
Java 本地方法调用,如调用 C++ 实现的本地模块、NIO 的 DirectByteBuffer,都会占用大量内存。而在开发中,开发者往往重点关注了堆内存的大小,在内存溢出时也倾向于增加堆内存,而忽视了堆外内存的使用。堆外内存并不会像堆内存一样不足马上通知 GC 进行垃圾回收,堆外内存只能等待老年代空间不足进行 Full GC 时顺便回收内存,否则堆外内存只能等到空间不足时抛出内存溢出异常,然后请求 GC 进行回收。
因此,配置虚拟机,除考虑常规的堆大小外,优化时还需要考虑 Direct Memory、线程栈、socket 缓冲区、JNI 代码、虚拟机、GC 占用的内存大小。
外部命令的时间消耗
Java 开发中如果需要执行 shell 脚本,可以使用 Runtime.getRuntime().exec 方法,还能从返回的 Process 对象中读取标准输出、错误输出、等待执行结束。根据方法注释,该方法首先复制当前进程产生一个子进程,在子进程中执行命令,结束后退出子进程。
进程的复制比较消耗 CPU 和内存,应尽量通过 Java 程序本身去完成相关功能。
多线程使用线程池
Java 虚拟机中没有给用户用的多进程方法,并行处理更多地使用多线程方式。默认情况下,Linux 限制用户的线程数量上限为 1024,当然包括了系统中运行的所有线程。通常情况下,线程资源不会被耗尽,但多线程程序如果频繁创建新线程也会遇到线程资源不足的情况。一方面,可以调整系统设置,提高线程数上限,另一方面,应尽量避免频繁创建线程。线程虽小,创建时一样要消耗时间和内存。
多线程程序应尽量采用 Java 的线程池,这样线程的个数总体可控,使用时可以避免创建线程的时间消耗。Java 提供了多种功能强大的线程池类型,基于线程池可以对任务进行缓存、按照一定的时间频率执行任务、返回执行结果、分叉与合并等。
每周 3 篇学习笔记或技术总结,面向有一定基础的 Java 程序员,内容涉及 Java 进阶、虚拟机、MySQL、NoSQL、分布式计算、开源框架等多个领域。关注作者或微信公众号 backend-develop 第一时间获取最新内容。