java内存分配策略

1. 对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够的空间时,虚拟机将发起一次Minor GC。在如下的测试代码中,尝试分配3个2MB大小和1个4MB大小的对象,在运行时通过参数-Xmx20M,-Xms20M,-Xmn10M这三个参数限制了java堆大小为20MB,不可扩展,其中10MB分配给新生代,剩下的非配给老年代。-XX:SurvivorRatio=8决定了新生代中Eden区与一个Survivor区的比例为8:1,即
Eden: from Survivor:to Survivor = 8:1:1,即8MB:1MB:1MB,新生代的可用空间为9MB。

package test1;

class TestGc {

	private static final int _1MB = 1024 * 1024;

	/**
 -verbose:gc
-Xms20m
-Xmx20m
-Xmn10m
-XX:+UseSerialGC
-XX:+PrintGCDetails
-XX:SurvivorRatio=8

	 * @param args
	 */
	public static void main(String[] args) {
		byte[] allocation1, allocation2, allocation3, allocation4;
		 allocation1  = new byte[2 * _1MB];
		 allocation2  = new byte[2 * _1MB];
		 allocation3  = new byte[2 * _1MB];
		 allocation4  = new byte[4 * _1MB];//出现一次Monor GC
	}

}

对上面的一段代码简单分析:

Eden: from Survivor:to Survivor = 8MB:1MB:1MB;

分配allocation1、allocation2、allocation三个对象到Eden区,占6MB空间;分配allocation4时 发现Eden剩余空间2MB不够分配,因此发生Minor GC,GC期间又发现已有的3个2MB的对象都无法放入Survivor空间(1MB),所以通过担保机制提前转移到老年代区(3个2MB的对象),此时Eden区恢复到8MB空间,然后将allocation4分配到Eden空间。

运行结果:

[GC [DefNew: 6487K->160K(9216K), 0.0155080 secs] 6487K->6305K(19456K), 0.0155918 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]

Heap

def new generation   total 9216K, used 4584K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)

eden space 8192K,  54% used [0x00000000f9a00000, 0x00000000f9e51f98, 0x00000000fa200000)

from space 1024K,  15% used [0x00000000fa300000, 0x00000000fa3283d0, 0x00000000fa400000)

to   space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)

tenured generation   total 10240K, used 6144K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)

the space 10240K,  60% used [0x00000000fa400000, 0x00000000faa00030, 0x00000000faa00200, 0x00000000fae00000)

compacting perm gen  total 21248K, used 2981K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)

the space 21248K,  14% used [0x00000000fae00000, 0x00000000fb0e96a0, 0x00000000fb0e9800, 0x00000000fc2c0000)

No shared spaces configured.

下面的结果是上面这段代码去掉-XX:+UseSerialGC参数后的(我的环境默认采用的是Parallel Scavenge收集器),没有发生Minor GC,为什么呢?这是因为不同的收集器收集策略不同(可以看下下面的Minor GC和 Full GC的差别就明别了)。

Heap

PSYoungGen      total 9216K, used 6651K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)

eden space 8192K, 81% used [0x00000000ff600000,0x00000000ffc7eec8,0x00000000ffe00000)

from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)

to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)

PSOldGen        total 10240K, used 4096K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)

object space 10240K, 40% used [0x00000000fec00000,0x00000000ff000010,0x00000000ff600000)

PSPermGen       total 21248K, used 2972K [0x00000000f9a00000, 0x00000000faec0000, 0x00000000fec00000)

object space 21248K, 13% used [0x00000000f9a00000,0x00000000f9ce71e0,0x00000000faec0000)

Minor GC和 Full GC:

新生代GC(Minor GC):只发生在新生代的垃圾收集动作,因为java对象大多数都具备朝生夕灭的特性,所有Minor GC非常频繁,一般回收速度也比较快。

老年代GC(Major GC/Full GC):只发生在老年代的GC,出现了Full GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge 收集器的收集策略就有直接进行Full GC的策略选择过程)。Full GC的速度一般会比Minor GC慢10倍以上。

2.大对象直接进入老年代

所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。大对象对虚拟机的对象分配来说是一个坏消息,经常出现大对象容易导致内存还有不少空间就提前触发垃圾收集器以获得足够的连续空间来安置它们。

	/**
-verbose:gc
-Xms20m
-Xmx20m
-Xmn10m
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
-XX:+UseSerialGC
-XX:PretenureSizeThreshold=3145728(3MB)
	 */
	public static void test2(){
		byte[] allocation1;
		 allocation1  = new byte[4 * _1MB];

	}

运行结果:

Heap

def new generation   total 9216K, used 507K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)

eden space 8192K,   6% used [0x00000000f9a00000, 0x00000000f9a7ee98, 0x00000000fa200000)

from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)

to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)

tenured generation   total 10240K, used 4096K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)

 the space 10240K,  40% used [0x00000000fa400000, 0x00000000fa800010, 0x00000000fa800200, 0x00000000fae00000)

compacting perm gen  total 21248K, used 2973K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)

the space 21248K,  13% used [0x00000000fae00000, 0x00000000fb0e7428, 0x00000000fb0e7600, 0x00000000fc2c0000)

No shared spaces configured.

3.长期存活的对象进入老年代

既然虚拟机采用了分代收集的思想来管理内存,那么内存回收是就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象如果对象在Eden出生并经历了一次Minor GC 后仍然存活,并且能被Survivor容纳的话,将被移到Survivor空间中,并且年龄设为1.对象在Survivor中每熬过一次 Minor GC,对象的年龄加1岁,年龄加到一定的程度(默认是15),就会被晋升到老年代中。

对象老年代的阈值可以可以通过参数-XX:MaxTenuringThreshold设置。

4.动态对象年龄判断

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

5. 空间分配担保

在发生MInor GC 之前,虚拟机会首先检查老年代最大可用的连续空间是否大于新生代所有对象的总和,如过这个条件成立,那么Minor GC 可以确保是安全的。如果不成立,则虚拟机会查看HandelPromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代连续的可用的空间大小是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试一次Minor GC ,尽管这次Minor GC 是有风险的;如果小于或者HandelPromotionFailure设置不允许冒险,那这是也要进行一次Full
GC。

-XX:-HandelPromotionFailure=false。

时间: 2024-10-31 11:51:00

java内存分配策略的相关文章

Java 内存管理机制:04 Java 内存分配策略

Java 内存分配策略 Java 内存分配策略 优先在 Eden 区分配 大对象直接进入老年代 长期存活的对象将进入老年代 空间分配担保 新生代和老年代的 GC 操作 新生代 GC 操作:Minor GC 发生的非常频繁,速度较块. 老年代 GC 操作:Full GC / Major GC 经常伴随着至少一次的 Minor GC: 速度一般比 Minor GC 慢上 10 倍以上. 优先在 Eden 区分配 Eden 空间不够将会触发一次 Minor GC: 虚拟机参数: -Xmx:Java 堆

Java深入 - Java 内存分配和回收机制-转

Java的GC机制是自动进行的,和c语言有些区别需要程序员自己保证内存的使用和回收. Java的内存分配和回收也主要在Java的堆上进行的,Java的堆中存储了大量的对象实例,所以Java的堆也叫GC堆. Java在垃圾收集的过程中,主要用到了分代收集算法,我会先讲一下常用垃圾收集算法. 常用垃圾收集算法 1. 标记-清除算法 这种垃圾收集算法思路非常简单,主要是首先标记出所有需要回收的对象,然后回收所有需要回收的对象. 但是有一个明显的缺点,采用这种算法之后会发现内存块回收之后就不连续了,这就

Java虚拟机垃圾收集器与内存分配策略

Java虚拟机垃圾收集器与内存分配策略 概述 那些内存需要回收,什么时候回收,如何回收是GC需要完成的3件事情. 程序计数器,虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性,内存随着方法结束或者线程结束就回收了. java堆与方法区在运行期才知道创建那些对象,这部分内存分配是动态的,本章笔记中分配与回收的内存指的就是:java堆与方法区. 判断对象已经死了 引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它,计数器+1;引用失败,计数器-1.计数器为0则改判

java中内存分配策略及堆和栈的比较

2.1 内存分配策略 按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的. 静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求. 栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区

java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项技术当做Java语言的伴生产物.事实上GC的历史远远比Java来得久远,在1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言.当Lisp还在胚胎时期,人们就在思考GC需要完成的3件事情:哪些内存需要回收?什么时候回收?怎么样回收? 经过半个世纪的发展,目前的内存分配策略

【java虚拟机序列】java中的垃圾回收与内存分配策略

在[java虚拟机系列]java虚拟机系列之JVM总述中我们已经详细讲解过java中的内存模型,了解了关于JVM中内存管理的基本知识,接下来本博客将带领大家了解java中的垃圾回收与内存分配策略. 垃圾回收(Garbage Collection,GC)是java语言的一大特色,在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理.而在C/C++中是需要程序员主动释放的,而在java中则交给JVM自动完成,既然是交给程序自动执行,那么这里就必须完成以下几件事:

深入理解java虚拟机----->垃圾收集器与内存分配策略(下)

1.  前言 内存分配与回收策略 JVM堆的结构分析(新生代.老年代.永久代) 对象优先在Eden分配 大对象直接进入老年代 长期存活的对象将进入老年代 动态对象年龄判定 空间分配担保  2.  垃圾收集器与内存分配策略 Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决两个问题: 给对象分配内存; 回收分配给对象的内存. 对象的内存分配,往大方向上讲就是在堆上的分配,对象主要分配在新生代的Eden区上.少数也可能分配在老年代,取决于哪一种垃圾收集器组合,还有虚拟机中的相关内存的参

垃圾收集器与内存分配策略(深入理解Java虚拟机)

3.1 概述 垃圾收集器要解决哪些问题? 哪些内存需要回收 什么时候回收 如何回收 引用计数算法:当有一个地方引用,+1,引用失效,-1.     缺点:对象之间相互循环引用的问题. 可达性分析算法: 思路:通过一系列的成为"Gc Roots"的对象作为起始点,从这些节点开始向下探索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到Gc Roots没有任何引用链相连,则则很难革命此对象是不可用的. Java语言中GC Roots的对象包括下面几种: 1.虚拟机

垃圾收集器与内存分配策略

①对于java虚拟机来说,垃圾收集器主要关注的内存区域是 堆和方法区. ②垃圾收集器就是要收集那些已经“死了”的对象.如果判断一个对象是否存活? 对象引用计数法 对象引用增加一个,那么相应的计数器加1,否则,减1. 优点:实现简单 缺点:不能处理对象间的循环引用.a引用b,b同时引用a. 可达性分析 如果节点到root节点可达,则证明是存活的:否则,已死.所以对于下图的o5,o6,o7虽然他们是循环引用的,但是到root节点无可达,所以已死可清除. ③垃圾回收器对于不同类型引用的回收规则 强引用