对于Java应用程序来说,Java堆是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块区域,在虚拟机启动的时候创建,几乎所有的对象实例都在这块分配。
我们来看一下堆和方法区的内存图,可以看到主要分为年轻代和老年代。
老年代:这块是堆内存区域占用空间最大的一块。用于存储那些不容易消亡的对象。
Eden: 新生代的区域,新建的对象实例大多都分配在这里。
S0/S1: 用于储存新生代垃圾回收所保留下来的对象。
这里需要注意,在Eden区域还有一小块区域叫做TLAB,这块区域是线程私有,因为对象分配并不是线程安全的,需要枷锁,所以这块区域用于提高对象分配效率,但是分配对象大于这块区域或者这块区域已经满了,仍然需要加锁分配对象内存。
所以分配对象的流程图如下:
但是如果出现以下情况则直接分配对象直接进入老年代:
- 大对象直接分配: 当对象的大小超过JVM参数-XX:PretenureSizeThreshold指定的值时,这个对象会直接在老年代分配。这样做是为了避免大对象在年轻代之间来回复制,因为这样做可能会增加GC的负担。
- 长期存活的对象: JVM使用一种称为“分代年龄”(tenuring age)的机制,如果一个对象在多次GC后仍然存活,它的分代年龄会增加。当这个年龄达到JVM参数-XX:MaxTenuringThreshold指定的值时,对象会被提升到老年代。
- 动态对象年龄判定: 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,那么年龄大于或等于该年龄的对象会直接进入老年代,而不会等到MaxTenuringThreshold指定的年龄。
- 老年代担保分配: 如果JVM发现年轻代的空间不足以安置一个新创建的对象,并且这个对象的大小非常大,那么会尝试进行一次Minor GC。如果在进行Minor GC之后,仍然无法在年轻代找到足够的空间来分配这个对象,那么这个对象会直接在老年代分配。
- JVM参数强制指定: 有时,开发者可以通过设置JVM参数如-XX:+AlwaysTenure来强制所有对象都在老年代分配,不过这种做法并不常见,因为它可能会严重影响GC的性能。
提到堆区域,那就不得不提到垃圾回收了。
在JVM中,按照回收区域可以分为以下几种:
下面是JVM堆区域相关参数的设置:
- -Xms: 初始化堆大小,即JVM启动时分配的堆内存大小。
- -Xmx: 最大堆大小,即JVM可以使用的最大堆内存大小。
- -XX:NewRatio: 设置年轻代(Young Generation)与老年代(Old Generation)的大小比例。默认值为2,表示年轻代与老年代的大小比为1:2。
- -XX:NewSize: 设置年轻代初始大小。
- -XX:MaxNewSize: 设置年轻代最大大小。
- -XX:SurvivorRatio: 设置伊甸园(Eden)空间与幸存者空间(Survivor Spaces)的大小比例。默认值为8,表示伊甸园与幸存者空间的大小比为8:1。
- -XX:PretenureSizeThreshold: 设置大对象直接进入老年代的大小阈值。超过这个大小的对象将直接在老年代分配。
- -XX:MaxTenuringThreshold: 设置对象晋升到老年代的年龄阈值。当对象的分代年龄达到这个值时,将会被晋升到老年代。
- -XX:+UseTLAB: 启用线程本地分配缓冲区(Thread Local Allocation Buffer),默认是启用的。
- -XX:TLABSize: 设置TLAB的初始大小。
- -XX:MinHeapFreeRatio: 设置堆空间最小空闲比率,当堆空间的空闲内存低于这个值时,JVM会尝试增加堆空间大小。
- -XX:MaxHeapFreeRatio: 设置堆空间最大空闲比率,当堆空间的空闲内存高于这个值时,JVM会尝试减少堆空间大小。
- -XX:HeapDumpOnOutOfMemoryError: 当JVM遇到OutOfMemoryError时,自动生成堆转储(Heap Dump)文件,用于分析内存溢出问题。
- -XX:HeapDumpPath: 设置堆转储文件(Heap Dump File)的路径。