背景
在使用Elasticsearch的时候,经常会碰到很多建议,比如标题中写的。在官方建议中,Elasticsearch内存最多只需要占用服务器内存的一半,而且建议最多不能超过32G,比如31G。那么这么建议的原因是什么呢?
首先,Elasticsearch是一个Java应用,那么它肯定是基于JVM的。而建议31G的原因在于JVM的设计。
基础知识
这个问题的原因是Java对象的内存布局决定的,那么什么是Java对象内存布局呢?简单点说,就是指Java对象的内存分配,主要描述在内存中它是怎么存储的,占用多大的空间。这里引用一张图片,具体原出处我也忘记了。
主要分为三部分,对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。
对象头
1. Mark Word
用于存储对象自身的运行时数据,例如HashCode
、GC分代年龄
等信息。在32位和64位的JVM中,这部分数据分别为4bytes和8bytes。
2. Mark Word
用于存储对象引用的指向类元数据的指针,标识属于哪个类的实例。在32位和64位的JVM中,这部分数据分别为4bytes和8bytes。64位JVM中启用压缩时,占用大小为4bytes。
3. 数组长度
用于数组对象的数组长度,这里存储的是一个int类型的对象引用。在32位和64位的JVM中,这部分数据分别为4bytes和8bytes。64位JVM中启用压缩时,占用大小为4bytes。
实例数据
实例数据,即对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是子类中定义的,都需要记录起来。
这部分的存储顺序会受到虚拟机分配策略参数和字段在Java源码中定义顺序的影响。
对齐填充
对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。
比如HotSpot VM的自动内存管理系统要求对象必须是8bytes的整数倍,如果对象实际占用大小不是8的整数倍,就需要通过对齐填充来补全。
Compressed Oops(压缩普通对象指针)
OOP = “ordinary object pointer” 普通对象指针。
启用CompressOops后,会压缩的对象:
• 每个Class的属性指针(静态成员变量)
• 每个对象的属性指针
• 普通对象数组的每个元素指针
当然,压缩也不是万能的,针对一些特殊类型的指针,JVM是不会优化的。
比如指向PermGen的Class对象指针,本地变量,堆栈元素,入参,返回值,NULL指针不会被压缩。
在启动java时,加 -XX:+UseCompressedOops 即可开启。默认在32G内存的时候也会开启。
分析
经过上述基础知识,我们对于Java对象的内存的大小分配也有了一定的了解。每个对象都有固定的最小的空间分配,即对象头的空间大小,一般来说,压缩下的对象会比未压缩的要小一些,正常是20-30%,那么对应的,当JVM的内存设置为32GB以上时,实际使用37-40GB才能达到与31GB堆大小的差不多的内存效果。
总结
实际上原理是相通的,其他的任意Java应用也是这样。每天进步一点点,加油。