编辑一个相当大的措辞编辑,并在整个过程中添加更多细节。
一些想法:
当一段代码的执行次数明显多于其他部分(这是程序的热点)时,热点就会启动。从那时起,这使得那段代码(对于正常路径)明显更快。热点编译后的调用率并不重要,所以我认为这不会导致您提到的效果。
效果是真的吗?用统计数据欺骗自己很容易。不是说你是,但要确保你所有的运行都包含在结果中,并且所有其他效果(如其他程序、活动和你的监控程序在所有情况下都是相同的。我有不止一个有我的监控程序,例如 top,会导致行为不同)。有一次,当数据库上的缓存预热时,应用程序的性能显着提高- 同一数据库实例上的其他应用程序存在内存压力。
很可能涉及操作系统和/或 CPU。当主程序从主要运行变为主要等待 I/O 时,操作系统和 CPU 都会主动和被动地做一些事情来提高主程序的响应能力,反之亦然,包括:
- 操作系统在不使用时将内存分页到磁盘,并在程序运行时将内存分页到 RAM
- 操作系统将缓存经常使用的磁盘块,这又可以提高应用程序的性能
- CPU 指令和内存缓存填充活动程序的指令和数据
Java 应用程序对内存分页效果特别敏感,因为:
- 典型的 Java 应用程序服务器会将几乎所有空闲内存预分配给 Java。大内存使应用程序天生对内存效应更敏感
- 用于管理 Java 内存的分代垃圾收集器最终会在大量页面上创建新对象,因此对应用程序的每个请求都需要比其他语言更多的页面请求。(这主要适用于没有经过多次垃圾回收的“新”对象。提升到永久代的对象实际上存储得非常紧凑)
- 由于系统上分配了大部分可用的物理内存,因此内存总是存在压力,并且最大、最近运行最少的应用程序是页面输出的完美候选者。
考虑到这些因素,与内存需求较小的环境相比,页面丢失的可能性更大,因此性能受到影响。这些在 Java 闲置一段时间后尤其明显。
如果您使用 Solaris 或 Mac,出色的dTrace可以跟踪特定于应用程序的内存和磁盘分页。JVM 有许多 dTrace 挂钩,可用作启动和停止页面监控的触发器。
在 Solaris 上,您可以使用大内存页面(甚至超过 1GB 大小)并将它们固定到 RAM,这样它们就永远不会被分页。这应该可以消除上述内存页面问题。请记住为磁盘缓存和其他系统/维护/备份/管理应用程序留出大量可用内存。我确信其他操作系统也支持类似的功能。
TL/DR:现代操作系统中当前正在运行的程序在几秒钟后会显得运行得更快,因为操作系统会将程序和数据页面从磁盘带回,将经常使用的磁盘页面放在磁盘缓存中,并且操作系统指令和数据缓存将对主程序来说往往是“温暖的”。这种效果并不是 JVM 独有的,但由于典型 Java 应用程序的内存需求和垃圾收集内存模型,这种效果更加明显。