内存分配与回收策略

本文探讨了JVM中对象的内存分配策略,包括对象优先在Eden区分配、大对象直接进入老年代、长期存活对象晋升至老年代以及动态对象年龄判定等核心概念。通过实验演示了内存分配的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

     对象的内存分配笼统地讲,就是在堆上分配。对象主要分配在Eden区上,如果考虑线程安全问题,启动本地线程分配缓冲区,将按线程优先在TLAB上分配。少数情况下也可能直接回分配在老年代中,分配的规则并不是百分之百固定的,其细节取决于当前使用的哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数。以HotSpot虚拟机为例,内存分配和回收策略如下:

一、对象优先在Eden区分配

     在HotSpot虚拟机中,将内存区域主要分为新生代和老年代两大块区域,新生代区域又被详细分为 Eden区和两个Survivor区,也就是GC中打印出来的eden space、from space和to space三个空间。在前面垃圾回收算法中的复制算法我们讲过,虚拟机一般创建对象时只在Eden和一个Survivor上分配空间,而另一个Survivor区和Old区分别作为存活对象复制的仓库和分配担保。

     我们做以下实验,尝试分配3个2MB大的对象,通过-Xms20M、-Xmx20M、-xmn10M这三个参数限制Java堆的大小为20M且不可扩展,限制新生代内存大小为10M,则老年代内存为10M。代码清单及结果如下:

public class GCEden {
	private static final int _1MB=1024*1024;
	public static void Eden() {
		byte[] bs1,bs2,bs3;
		bs1=new byte[2*_1MB];
		bs2=new byte[2*_1MB];
		bs3=new byte[2*_1MB];
	}
	public static void main(String[] args) {
		GCEden.Eden();
	}
}
Heap
 PSYoungGen      total 9216K, used 7292K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 89% used [0x00000000ff600000,0x00000000ffd1f1a8,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 0K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000)
 Metaspace       used 2682K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 295K, capacity 386K, committed 512K, reserved 1048576K

     由以上结果我们可以看出来,JVM使用的新生代垃圾回收器为Parallel Scavenge,虽然我们给新生代分配的内存为10M,但我们知道,Parallel Scavenge采用的是“复制算法”,实际上用于新生成的对象的内存空间按比例算为9M,也就是Eden区加上一个Survivor区。从GC日志我们可以看出来,创建对象时,优先使用的是Eden空间,而from Space和to space以及Parallel Old区空间均未使用,也就是新生代中的两个Survivor和老年代未被使用。我们都知道,新生代采用的“复制算法”是将一次存活的对象复制到另一个Survivor空间中去,然后整体回收使用的Eden区和Survivo区,那么当我们遇见的对象比较大时,触发GC的频率也就大大增加,同时复制算法的复制操作也会相对增加,这也就反而降低了虚拟机的性能,所以才有了下一步的策略规划。

二、大对象直接进入老年代

     所谓大对象,就是需要大量连续内存空间的Java对象,最典型的就是那种很长的字符串以及数组对象。虚拟机提供了一个—XX:PretenureSizeThreshold参数来设置多大以上的对象直接进入老年代,避免了内存复制操作所带来的性能消耗。但该参数只适用于Serial和ParNew两款收集器,Parallel Scavenge收集器一般不需要设置,如果遇到必须要使用这个参数的场合,一般考虑使用ParNew和CMS两种收集器组合。

三、长期存活的对象将进入老年代

     虚拟机采用了分代收集的思想来管理内存,那么回收时就必须能识别哪些对象应方在新生代,哪些对象应该方在老年代中。为了做到这一点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor区中每“熬过”一次GC,年龄就增加1岁,当它的年龄增加到一定的程度(默认为15岁),就会直接被移到老年代中。 当然,这样较为“刻板”的设置肯定会影响虚拟机的性能,一般我们不建议单独采用,所以虚拟机设置了如下的动态年龄判定结合年龄设置来决定哪些对象直接进入老年代。

四、动态对象年龄判定

     为了更好的适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了设定的阈值才能晋升到老年代中,如果Survivor空间中相同年龄所有对象大小总和大于了Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须再等到设定的阈值在进入老年代。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值