JVM学习之:内存的分配以及回收
对于操作系统而言,内存的管理主要包含两个部分,分配和回收,因为JVM分担了程序员的内存管理细节,所以虚拟机也帮助程序员完成了这两件事情。一般情况下提到的内存分配主要是指在堆中的Eden区中的分配,少数情况下可能会直接将对象分配在老年区,但是分配规则主要依赖于具体的环境以及对垃圾回收器的配置,下面将介绍几种常见的内存分配以及回收的策略,并结合具体的测试程序以及输出情况进行讲分配策略
对于操作系统而言,内存的管理主要包含两个部分,分配和回收,因为JVM分担了程序员的内存管理细节,所以虚拟机也帮助程序员完成了这两件事情。
一般情况下提到的内存分配主要是指在堆中的Eden区中的分配,少数情况下可能会直接将对象分配在老年区,但是分配规则主要依赖于具体的环境以及对垃圾回收器的配置,下面将介绍几种常见的内存分配以及回收的策略,并结合具体的测试程序以及输出情况进行讲
分配策略
1:对象优先在Eden区上分配
public class FirstUseEdenSpace {
/**验证Eden为默认的对象分配区
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
* -XX:+PrintGCDetails
*/
public static final int _1M = 1024 << 10;
public static void main(String[] args) {
testAllolaction();
}
public static void testAllolaction() {
byte[] all1, all2, all3, all4;
all1 = new byte[_1M << 2];
all2 = new byte[_1M << 2];
all3 = new byte[_1M << 2];
all4 = new byte[_1M << 1];
}
}
运行这个程序时通过-XX:+PrintGCDetails参数来打印收集器的日志参数,下面为具体的日志Heap
eden space 8192K, 81% used [0x000000000b470000,0x000000000baeef10,0x000000000bc70000)
from space 1024K, 0% used [0x000000000bd70000,0x000000000bd70000,0x000000000be70000)
to space 1024K, 0% used [0x000000000bc70000,0x000000000bc70000,0x000000000bd70000)
PSOldGen total 10240K, used 8192K [0x000000000aa70000, 0x000000000b470000, 0x000000000b470000)
object space 10240K, 80% used [0x000000000aa70000,0x000000000b270030,0x000000000b470000)
PSPermGen total 21248K, used 3001K [0x0000000005670000, 0x0000000006b30000, 0x000000000aa70000)
object space 21248K, 14% used [0x0000000005670000,0x000000000595e448,0x0000000006b30000)
首先通过 -Xms20M -Xmx20M -Xmn10M 来设定堆的最大值为20M,新生代为10M,老年代也为10M(20M-10M),当执行testAllolaction方法时,all1以及all2一共大小是8M没有超过10M所以不会引发GC操作,可是当为all3分配堆空间时,由于新生代中只剩下2M的空间,不能存放all3,所以会触发年轻代的GC操作,又因为8M的空间大于Survivor的1M空间,所以被扔进了老年区,导致老年区的空间被用掉了10240*80%=8M,接下来的all3以及all4加起来是6M没有超过8M,所以直接扔到了eden区,而from和to区任然是0
但是实验的过程中,如果把all4改为_1M<<2则会出现下面的问题
[GC-- [PSYoungGen: 4309K->4309K(9216K)] 12501K->12501K(19456K), 0.0012655 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 4309K->4287K(9216K)] [PSOldGen: 8192K->8192K(10240K)] 12501K->12479K(19456K) [PSPermGen: 2992K->2983K(21248K)], 0.0084098 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Exception in thread "main" Heap
PSYoungGen total 9216K, used 4539K [0x000000000b450000, 0x000000000be50000, 0x000000000be50000)
eden space 8192K, 55% used [0x000000000b450000,0x000000000b8bed48,0x000000000bc50000)
from space 1024K, 0% used [0x000000000bd50000,0x000000000bd50000,0x000000000be50000)
to space 1024K, 0% used [0x000000000bc50000,0x000000000bc50000,0x000000000bd50000)
PSOldGen total 10240K, used 8192K [0x000000000aa50000, 0x000000000b450000, 0x000000000b450000)
object space 10240K, 80% used [0x000000000aa50000,0x000000000b250030,0x000000000b450000)
PSPermGen total 21248K, used 3002K [0x0000000005650000, 0x0000000006b10000, 0x000000000aa50000)
object space 21248K, 14% used [0x0000000005650000,0x000000000593eab0,0x0000000006b10000)
java.lang.OutOfMemoryError: Java heap space
因为在分配all4前, eden的空间只剩下45%的空间可以利用,而老年去也被占用了80%,此时如果把all4的大小改为4M,那么年轻代和老年代的空间够不可以存放4M,而且all1,all2,all3都是存活的对象,所以他们都不可以回收,最后只能抛出java.lang.OutOfMemoryError: Java heap space,如果在分配all4之前有一个all1=null;操作的话,则all4就可以正常的分配了
有个问题希望大虾门可以指点:第二个GC detail中 eden space 8192K, 55% used [0x000000000b450000,0x000000000b8bed48,0x000000000bc50000),因为它有8M的空间,所以当分配完all3(4M)后应该是用50%,可是为什么是55%了?
2:大对象直接分配在老年区
所谓的大对象,简单的理解就是可能会占用堆空间很大的对象实例,比如说一个很大的字符串数组,因为虚拟机默认是将对象实例分配在年轻代的Eden区,因为年轻代的垃圾收集器主要采用的复制算法,所以如果这个大对象的存活周期比较长的话,意味着他们会被多次的复制,而且最要命的是,因为当年轻代没有足够的空间来存放大对象时,会引发年轻区的垃圾回收,如果大对象的数量比较多,意味着年轻区会发生频繁的垃圾回收,为了防止这种情况的发生,虚拟机中增加了一个XX:PretenureSizeThreshold参数来指定直接放入老年区的实例大小,下面是个简单的例子
public class AlloctionLargeObejct {
/**验证大对象直接进入老年去
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+UseSerialGC
* -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails
* -XX:PretenureSizeThreshold=1048576
*/
public static final int _1M = 1024 << 10;
public static void main(String[] args) {
testAllolaction();
}
public static void testAllolaction() {
byte[] all1, all2, all3, all4;
all1 = new byte[_1M << 2];
}
Heap
def new generation total 9216K, used 507K [0x00000000055e0000, 0x0000000005fe0000, 0x0000000005fe0000)
eden space 8192K, 6% used [0x00000000055e0000, 0x000000000565eee0, 0x0000000005de0000)
from space 1024K, 0% used [0x0000000005de0000, 0x0000000005de0000, 0x0000000005ee0000)
to space 1024K, 0% used [0x0000000005ee0000, 0x0000000005ee0000, 0x0000000005fe0000)
tenured generation total 10240K, used 1024K [0x0000000005fe0000, 0x00000000069e0000, 0x00000000069e0000)
the space 10240K, 10% used [0x0000000005fe0000, 0x00000000060e0018, 0x00000000060e0200, 0x00000000069e0000)
compacting perm gen total 21248K, used 3000K [0x00000000069e0000, 0x0000000007ea0000, 0x000000000bde0000)
the space 21248K, 14% used [0x00000000069e0000, 0x0000000006cce3b0, 0x0000000006cce400, 0x0000000007ea0000)
No shared spaces configured.
关于上面程序有两点需要注意
(1) -XX:PretenureSizeThreshold 参数不支持M单位的使用,所以只能使用1048576来表示1M
(2)PretenureSizeThreshold-XX:+UseSerialGC来指定Serial作为垃圾收集器参数只支持Serial以及ParNew两种垃圾收集器,所以运行参数使用
3:长期存活的对象直接进入老年区
因为虚拟机分为年轻代和老年代,那么虚拟机就应该知道哪些对象应该放在老年代,哪些对象应该放在年轻代,这里就引入了对象年龄的概念,虚拟机规范规定,当对象在Eden区中经过一次年轻代的回收成功转入Survivor之后,它的年龄就为一,从此没发生一次年轻代的垃圾回收,只要他还在,那么它的年龄就一直加1,知道达到虚拟机的一个最大值(默认为15),为了可以灵活的控制这个年龄,虚拟机提供了一个参数 -XXMaxTenuringThreshold参数来控制这个年龄,验证过程通前面两种相同,读者可以自行进行验证
回收测率
1:Survivor 区中的对象不满足一定年龄也可以进入老年区
虽然说虚拟机通过对象的年龄来区分Young Space 以及Tenuring Space,但是为了更好的适应不同程序的内存情况,虚拟机并不总是要求只有到达一定年龄的对象才可以进入Tenuring Space,如果在Surivivor中相同年龄的所有对象大小总和大于Survivor空间的一半,就可以直接Tenuring Space,而无需等到规定的年龄
2:空间分配担保
在发生Young GC时,虚拟机会检测之前每次晋升到Tenuring Space的平均大小是否大于老年代的剩余空间大小,如果大于,则改为进行一次Full Gc,如果小于,则查看HandlePromotionFailure是否设置为真,因为该参数是设定是否允许Tenuring Space对Young Space放不下的对象实力进行担保存放,如果是真,则会把放不下的实例放到Tenuring Space,然后进行一次Young GC,否则只能进行一次Full GC,虽然虚拟机提供了这种机制,但是有的时候,Tenuring Space的剩余空间也不足以存放Young Space中需要转移出去的对象,这个时候只能进行一次Full GC来在Tenuring Space中腾出更多的空间来了,毕竟这种情况比较少见,总的而言,HandlePromotionFailure还是可以减少Full GC的次数的
更多推荐
所有评论(0)