JVM 年青代内存分配一点解疑

JVM 年青代内存分配

引入

先看一段代码:

Test
	public void testJVM() {
		System.out.println("maxMemory=");
		System.out.println(Runtime.getRuntime().maxMemory()+" bytes");
		System.out.println("free mem=");
		System.out.println(Runtime.getRuntime().freeMemory()+" bytes");
		System.out.println("total mem=");
		System.out.println(Runtime.getRuntime().totalMemory()+" bytes");
		
		byte[] b = new byte[1*1024*1024];
		System.out.println("分配了1M空间给数组");
		
		System.out.println("maxMemory=");
		System.out.println(Runtime.getRuntime().maxMemory()+" bytes");
		System.out.println("free mem=");
		System.out.println(Runtime.getRuntime().freeMemory()+" bytes");
		System.out.println("total mem=");
		System.out.println(Runtime.getRuntime().totalMemory()+" bytes");
		
		b = new byte[4*1024*1024];
		System.out.println("分配了4M空间给数组");

		System.out.println("maxMemory=");
		System.out.println(Runtime.getRuntime().maxMemory()+" bytes");
		System.out.println("free mem=");
		System.out.println(Runtime.getRuntime().freeMemory()+" bytes");
		System.out.println("total mem=");
		System.out.println(Runtime.getRuntime().totalMemory()+" bytes");
	}

运行时配置JVM参数如下:

-Xmx20m -Xms20m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC  -XX:+PrintTenuringDistribution

运行后日志如下:

-XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 
[GC [DefNew
Desired survivor size 327680 bytes, new threshold 1 (max 15)
- age   1:     655360 bytes,     655360 total
: 5504K->640K(6144K), 0.0036274 secs] 5504K->861K(19840K), 0.0036505 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
maxMemory=
20316160 bytes
free mem=
18542976 bytes
total mem=
20316160 bytes
分配了1M空间给数组
maxMemory=
20316160 bytes
free mem=
17494384 bytes
total mem=
20316160 bytes
[GC [DefNew
Desired survivor size 327680 bytes, new threshold 15 (max 15)
- age   1:      19488 bytes,      19488 total
: 2534K->19K(6144K), 0.0025142 secs] 2755K->1902K(19840K), 0.0025677 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
分配了4M空间给数组
maxMemory=
20316160 bytes
free mem=
14063464 bytes
total mem=
20316160 bytes
Heap
 def new generation   total 6144K, used 4279K [0x00000000f9a00000, 0x00000000fa0a0000, 0x00000000fa0a0000)
  eden space 5504K,  77% used [0x00000000f9a00000, 0x00000000f9e29130, 0x00000000f9f60000)
  from space 640K,   2% used [0x00000000f9f60000, 0x00000000f9f64c20, 0x00000000fa000000)
  to   space 640K,   0% used [0x00000000fa000000, 0x00000000fa000000, 0x00000000fa0a0000)
 tenured generation   total 13696K, used 1882K [0x00000000fa0a0000, 0x00000000fae00000, 0x00000000fae00000)
   the space 13696K,  13% used [0x00000000fa0a0000, 0x00000000fa276bf0, 0x00000000fa276c00, 0x00000000fae00000)
 compacting perm gen  total 21248K, used 5483K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
   the space 21248K,  25% used [0x00000000fae00000, 0x00000000fb35ae18, 0x00000000fb35b000, 0x00000000fc2c0000)
No shared spaces configured.

分析

可以看到,当前最大内存为由-XX:MaxHeapSize = 20971520 我们根据分配了20M 20*1024*1024=20971520字节。刚好显示的与配置的是对应的,但Runtime.getRuntime().maxMemory() = 20316160字节,比设定的为何少了呢?这是因为分配给堆的内存空间和实际可用空间并非一个概念。我们知道垃圾回收在年青代的so s1(from/to)空间的回收算法一般采用的是复制算法,该算法是以空间换时间的策略,他的算法特点以后会展开说,总的来说,就是该算法会使两个相同大小的空间,在同一时刻只会有一个空间在使用。因此,实际最大可用内存为-Xmx的值减去from的空间大小.默认情况下,我们知道eden/from = 8且从堆打印上可以算出from的大小from space 640K, 2% used [0x00000000f9f60000, 0x00000000f9f64c20, 0x00000000fa000000) 其中[0x00000000f9f60000, 0x00000000f9f64c20, 0x00000000fa000000)这三个分别表示该空间的下界,当前上界,上界,用上界减去下界就是大小了:0x00000000fa000000-0x00000000f9f60000=0xA0000=A*16^4=655360字节 用20971520 -655360=20316160刚好为可用的内存,但有一个很好玩的问题

我们知道以下根据我们的JVM配置以下参数的值如下:

NewRatio=2,NewRatio=Old/Yong=Old/(Eden+Survivor*2)
SurvivorRatio=8,SurvivorRatio=Eden/Survivor
TargetSurvivorRatio=0.5 

我们可以计算出From应该为:20*1024*1024/3/10=699050 但实现中却只有655360 为何呢?

// 根据配置参数SurvivorRatio来确定年青代中Eden/From/To三个内存区的大小  
  size_t compute_survivor_size(size_t gen_size, size_t alignment) const {  
    size_t n = gen_size / (SurvivorRatio + 2);  
    return n > alignment ? align_size_down(n, alignment) : alignment;  
  }  

具体的详细内容可以看这个联连内存代管理器DefNewGeneration的对象内存分配
再看align_size_down(n, alignment)

   #define align_size_down(size, alignment) (size & (~(alignment-1)) )

可见虚拟机分配from/to时进行内存对齐操作。
上面代码 alignment 在非ARM平台上为 2^16 gen_size表示新生代的总数 n =gen_size / (SurvivorRatio + 2)=6990500/10=699050而(size & (~(alignment-1)) = 699050&(~(2^16-1))=655360 刚好为实际分配的 655360

对齐方法

这个对齐方法,也是linux中常用的对齐函数。他是怎么做到对齐的呢,下面我们分开看

  1. ~(alignment-1) 先看这一部分
    假设alignment是8 对应的二进制为:0000 1000
    (a-1)为:0000 0111
    ~(a-1)为:1111 1000
    任何一个数与~(a-1)按位与 都可以把后面的位置为0 也就是把这个数置为alignment的倍数
    其实如果我们要求某数以alignment为倍数的界数 就让这个数(要计算的这个数)表示成二进制时,最后几位为0就可以达到这个目标如本例中的8 只要下面这个数与待计算的数进行"与运算"就可以了:
    11111111 11111111 11111111 11111000
    而这个值就是 ~(alignment-1)

计算size以alignment为倍数的上下界数:
#define alignment_down(size, alignment) (size & (~(alignment-1)) )
#define alignment_up(size, alignment) ((size+alignment-1) & (~ (alignment-1)))
注: 上界数的计算方法,如果要求出比size大的是不是需要加上8就可以了?可是如果size本身就是8的倍数,这样加8不就错了吗,所以在size基础上加上(alignment- 1), 然后与alignment的对齐码进行与运算.
例如:
size=0, alignment=8, 则alignment_down(size,alignment)=0, alignment_up(size,alignment)=0.
size=6, alignment=8, 则alignment_down(size,alignment)=0, alignment_up(size,alignment)=8.
size=8, alignment=8, 则alignment_down(size,alignment)=8, alignment_up(size,alignment)=8.
size=14, alignment=8,则alignment_down(size,alignment)=8, alignment_up(size,alignment)=16.
注:alignment应当为2的n次方, 即2, 4, 8, 16, 32, 64, 128, 256, 1024, 2048, 4096 ...

参考:
内存对齐
原码, 反码, 补码 详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值