大家好! 我们希望将今天的资料翻译与
Java Developer课程中新线程的发布同时进行,该课程将于明天开始。 好吧,让我们开始吧。
JVM可能是一个复杂的野兽。 幸运的是,这种复杂性大部分都隐藏在内部,我们作为应用程序开发人员和负责部署的人,通常不必为此担心很多。 尽管由于用于在容器中部署应用程序的技术的日益普及,但是值得关注JVM中的内存分配。
两种记忆JVM将内存分为两大类:堆和非堆。 堆是开发人员最熟悉的JVM内存。 应用程序创建的对象存储在此处。 它们会一直保留在那里,直到被垃圾收集器移除为止。 通常,应用程序使用的堆大小取决于当前负载。
堆外内存分为几个区域。 在HotSpot中,您可以使用本
机内存跟踪(NMT)机制来探索此内存的区域。 请注意,尽管NMT不会跟踪所有本机内存的使用(
例如,它不会跟踪第三方代码对本机内存的分配 ),但其功能对于大多数典型的Spring应用程序来说已经足够。 要使用NMT,请使用
-XX:NativeMemoryTracking=summary
运行该应用程序
-XX:NativeMemoryTracking=summary
并使用
jcmd VM.native_memory摘要请参阅有关已用内存的信息。
让我们以老朋友Petclinic的NMT为例 。 下图显示了以最大堆大小为48 MB(
-Xmx48M
)启动Petclinic时,根据NMT数据(减去其自身的NMT开销)使用的JVM内存:

如您所见,堆之外的内存占了使用的大多数JVM内存,并且堆内存仅占总数的六分之一。 在这种情况下,大约为44 MB(其中33 MB在垃圾回收后立即使用)。 堆外内存使用总计223 MB。
本机内存区域压缩的类空间 :用于存储有关已加载类的信息。 限于
MaxMetaspaceSize
参数。 一个已加载的类数的函数。
译者注由于某种原因,作者写的是压缩类空间,而不是整个类区域。 压缩的类空间区域是“类”区域的一部分,并且MaxMetaspaceSize
参数限制整个“类”区域的大小,而不仅限于压缩的类空间。 为了限制“压缩类空间”,使用了CompressedClassSpaceSize
参数。
从这里 :
如果打开UseCompressedClassesPointers
并使用UseCompressedClassesPointers
,则将本机内存的两个逻辑上不同的区域用于类元数据...
为这些压缩的类指针分配了一个区域(32位偏移量)。 可以使用CompressedClassSpaceSize
设置区域的大小,默认情况下为1 GB。
MaxMetaspaceSize
适用于已提交的压缩类空间和其他类元数据的空间的总和
如果UseCompressedOops
参数并使用UseCompressedOops
,则将本机内存的两个逻辑上不同的区域用于类元数据...
对于压缩的指针,分配了一个内存区域(32位偏移量)。 可以通过CompressedClassSpaceSize
设置此区域的大小,默认情况下为1 GB ...
MaxMetaspaceSize
参数引用压缩的指针区域与其他类元数据的区域之和。
- 线程:JVM中的线程使用的内存。 正在运行的线程数的函数。
- 代码缓存:JIT用于运行它的内存。 一个已加载的类数的函数。 限于
ReservedCodeCacheSize
。 您可以减少JIT的设置,例如,通过禁用分层编译。 - GC(垃圾收集器):存储垃圾收集器使用的数据。 取决于使用的垃圾收集器。
- 符号:存储诸如字段名称,方法签名和插入字符串之类的字符。 过度使用字符存储器可能表示行太固定了。
- 内部:存储其他区域中未包含的其他内部数据。
差异性与堆相比,堆外内存在负载下的变化较小。 一旦应用程序加载了将要使用的所有类,并且JIT被完全预热,一切将进入稳定状态。 要减少对
Compressed类空间的使用,必须由垃圾收集器删除加载类的类加载器。 在过去,当将应用程序部署在servlet容器或应用程序服务器中时(在从应用程序服务器中删除应用程序时,垃圾收集器会删除应用程序类加载器),这是很常见的,但是在现代的应用程序部署方法中很少发生这种情况。
配置JVM配置JVM以有效使用可用的RAM并不容易。 如果使用
-Xmx16M
参数运行JVM,并且希望使用的内存不超过16 MB,那么您将得到一个不愉快的惊喜。
JVM内存的一个有趣区域是JIT代码缓存。 默认情况下,HotSpot JVM最多使用240 MB。 如果代码缓存太小,则JIT可能没有足够的空间来存储其数据,结果将降低性能。 如果缓存太大,则可能会浪费内存。 确定高速缓存的大小时,重要的是要考虑其对内存使用率和性能的影响。
现在 ,在Docker容器中运行时,最新版本的Java
知道容器的内存限制,并正在尝试相应地调整JVM内存的大小。 不幸的是,经常在堆外部分配大量内存,而在堆中分配的内存不足。 假设您有一个运行在带有2个处理器和512 MB可用内存的容器中的应用程序。 您想要处理更多的工作量,并将处理器数量增加到4,内存增加到1 GB。 正如我们上面所讨论的,堆大小通常随负载而变化,并且堆外的内存变化少得多。 因此,我们希望将额外的512 MB中的大多数分配给堆以处理增加的负载。 不幸的是,默认情况下,JVM不会执行此操作,而是会在堆上和堆外的内存之间或多或少地平均分配其他内存。
幸运的是,CloudFoundry团队对JVM中的内存分配有广泛的了解。 如果您将应用程序下载到CloudFoundry,则构建包将自动将这些知识应用于您。 如果您不使用CloudFoudry或想了解有关如何配置JVM的更多信息,建议阅读
Java buildpack的第三版
内存计算器的描述 。
这对Spring意味着什么Spring团队花费大量时间来考虑性能和内存使用,同时考虑在堆上和堆外使用内存的可能性。 限制堆外内存使用的一种方法是使框架的各个部分尽可能通用。 这样的一个示例是使用Reflection创建依赖关系并将其注入到应用程序的Bean中。 通过使用反射,无论您的应用程序中有多少个Bean,您使用的框架代码的数量都保持不变。 为了优化启动时间,我们使用堆上的缓存,在启动完成后清除该缓存。 垃圾回收器可以轻松清理堆内存,以为应用程序提供更多可用内存。
传统上,我们等待您对材料的评论。