图片


分配与回收,是 Java 虚拟机自动管理内存的两个部分。


之前提的垃圾内存回收的三种算法与常见的垃圾收集器,这些都是内存的回收,那 JVM 是如何分配内存呢?


Java 虚拟机规范并没有规定对象的创建和存储的细节,每款收集器都有各自的实现。


我本地环境是:

JDK 1.8、64 位、win7、HotSpot 默认 JVM 配置 

Parallel Scavenge + Parallel Old 组合,会加参数改收集器


1、新对象,一般会优先被分配到新生代 Eden 区,当 Eden 区没有足够空间虚拟机将发起 Minor GC

代码:

package constxiong.jvm.gc;

/**
 * 测试分配对象到 Eden 区
 */

public class AllocateToEden {

    public static void main(String[] args) {
    }

}

参数:

-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails

GC 日志:

图片

说明:

-Xms20M 初试堆内存大小 20M
-Xmx20M 最大堆内存大小 20M
-Xmn10M 新生内存大小 10M
-XX:SurvivorRatio=8 新生代中 Eden:Survivor:Survivor=8:1:1,Eden 占 80%
-XX:+PrintGCDetails 打印 GC 日志详情
这样配,

堆的大小为 20M
新生代:Eden 就 8M = 8192K,两个 Survivor(from、to) 分别为 1M = 1024K
老年代:10M = 10240K


上面代码启动并未分配对象,Eden 消耗了 2314K,元空间 Metaspace 消耗了 3458K


修改代码在 main 方法分配一个 2M 的字节数组

代码:

package constxiong.jvm.gc;

/**
 * 测试分配对象到 Eden 区
 */

public class AllocateToEden {

    public static void main(String[] args{
        byte[] array1 = new byte[2 * 1024 * 1024];//2M 数组
    }

}
GC 日志:

图片

说明:

2M 数组对象,别分配到了新生代的 Eden 区,未分配对象前 Eden 消耗 2314K,加上 2048K,等于 4362K



2、占用连续空间的大对象(超长字符串、数组),可以直接分配到老年代,避免在新生代的两个 Survivor 区来回复制大对象

修改代码,再分配一个大对象 8M 数组:

package constxiong.jvm.gc;

/**
 * 测试分配大对象到老年代
 */

public class AllocateToOldGeneration {

    public static void main(String[] args{
        byte[] array1 = new byte[2 * 1024 * 1024]; //2M 数组
        byte[] array2 = new byte[8 * 1024 * 1024]; //8M 数组
    }

}

参数未变:

-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails

GC 日志:

图片

说明:

新生代已经不够分配 8M 的数组对象,所以直接分配到老年代


-XX:PretenureSizeThreshold 可以指定当对象大小超过这个阀值时,会被分配到老年代;但 Parallel Scavenge 并不支持,所以添加参数 -XX:PretenureSizeThreshold=3145728,指定垃圾收集器为 Serial ,同时修改代码,把 8M 的数组改为 3M

代码:

package constxiong.jvm.gc;

/**
 * 测试分配对象到老年代
 */

public class AllocateToOldGeneration {

    public static void main(String[] args{
        byte[] array1 = new byte[2 * 1024 * 1024]; //2M 数组
        byte[] array2 = new byte[3 * 1024 * 1024]; //3M 数组
    }

}
参数:
-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+UseSerialGC -XX:PretenureSizeThreshold=3145728

GC 日志:

图片



3、存活超过一定年龄的对象会被移到老年代

对象熬过一次 Minor GC 年龄就增加,默认配置年龄超过 15 之后,对象会被移到老年代,可以通过 -XX:MaxTenuringThreshold 进行配置该阀值

代码:

package constxiong.jvm.gc;

/**
 * 测试移动超过一定年龄的对象到老年代
 */

public class AllocateToOldGeneration {

    public static void main(String[] args{
        byte[] array1 = new byte[1024 * 1024 / 10]; //0.1M
        byte[] array2 = new byte[4 * 1024 * 1024]; //4M
        array2 = null;
        array2 = new byte[4 * 1024 * 1024]; //4M
    }

}

参数:

-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=3 -XX:+PrintGCDetails -XX:+UseSerialGC -XX:MaxTenuringThreshold=1

GC 日志:

图片

说明:

-XX:SurvivorRatio=3,eden=6M、from survivor=2M、to survivor=2M


byte[] array2 = new byte[4 * 1024 * 1024]; 这行代码执行的时候触发第一次 GC,此时 array1 和部分其他启动时创建的对象继续保留在新生代且年龄加 1


最后一行代码,array2 = new byte[4 * 1024 * 1024]; 代码执行,触发第二次 GC,由于参数中配置了 -XX:MaxTenuringThreshold=1,年龄大于等于 1 的对象都移动到老年代,此次 GC 后新生代为 0


修改参数,把 -XX:MaxTenuringThreshold=1 改为 15

代码不变,参数:

-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=3 -XX:+PrintGCDetails -XX:+UseSerialGC -XX:MaxTenuringThreshold=15

GC 日志:

图片

说明:

修改 -XX:MaxTenuringThreshold=15,代码未变,array1 对象仍然保留在新生代,此时新生代不为 0


这条规则需要同时结合下一条:相同年龄所有对象占用内存之和,大于 Survivor 区空间一半时,大于等于该年龄的对象会被移到老年代。如果本例中的参数 -XX:SurvivorRatio=3 仍然等于 8 的话,第二次 GC 之后新生代仍然为 0,因为相同年龄的对象和大于 survivor 区 1M 的一半了


4、相同年龄所有对象占用内存之和,大于 Survivor 区空间一半时,大于等于该年龄的对象会被移到老年代

代码:

package constxiong.jvm.gc;

/**
 * 测试相同年龄所有对象占用内存之和,大于 Survivor 区空间一半时,大于等于该年龄的对象会被移到老年代
 */

public class AllocateToOldGeneration {

    public static void main(String[] args{
        byte[] array1 = new byte[1024 * 1024 / 4]; //0.25M
        byte[] array2 = new byte[4 * 1024 * 1024]; //4M
        byte[] array3 = new byte[4 * 1024 * 1024]; //4M,触发第一次 GC
        array3 = null;
        array3 = new byte[4 * 1024 * 1024]; //4M,触发第二次 GC,把所有年轻代的对象移动到老年代
    }

}

参数:

-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+UseSerialGC -XX:MaxTenuringThreshold=15

GC 日志:

图片

说明:

byte[] array3 = new byte[4 * 1024 * 1024]; 这行代码执行的时候触发第一次 GC,此时 array2 被移到老年代,array1 和部分其他启动时创建的对象继续保留在新生代且年龄加 1
最后一行代码,array3 = new byte[4 * 1024 * 1024]; 代码执行,触发第二次 GC,参数中配置了 XX:MaxTenuringThreshold=15,但相同年龄的对象占用内存大于 Survivor 区的一半,所有都移动到老年代,此次 GC 后新生代为 0



5、分配担保

主要解决的是,当老年代的连续可用空间不大于新生代的所有对象总内存时,这次 Minor GC 就是有风险的,根据参数 HandlePromotionFailure 决定是否进行担保分配的逻辑。

JDK 6 Update 24 开始,只要老年代的连续空间大于新生代对象总和或历次晋升的平均值,就会发生 Minor GC;否则进行 Full GC。



补充:不同版本 JDK、不同参数,相同代码 GC 情况不一


更多相关文章

  1. HotSpot VM 中对象的内存分析
  2. 从对象生命周期的经验统计到垃圾回收算法
  3. 什么样的 Java 对象会被当垃圾回收?
  4. Spring Ioc 实例化 Bean 对象有几种方式?
  5. Javascript面向对象入门
  6. 如何在 Java 中构造对象(学习 Java 编程语言 034)
  7. java创建对象的过程(内存角度分析)

随机推荐

  1. Android一些经常涉及到的权限【转】
  2. This text field does not specify an in
  3. android wrapper C调用java api
  4. Android跨进程通信IPC之11——Binder驱动
  5. android上传图片至服务器
  6. Android导出Kml
  7. minSdkVersion,targetSdkVersion,maxSdkVer
  8. Android开发学习---使用Intelij idea 13.
  9. android画图——Path()的使用
  10. 坑爹的android碎片化