JAVA对象内存layout

       迟到的文章,4年前就有所研究,但是总是用的时候现看,补上这篇blog,方便自己查询记忆。 通常情况上来说,Java程序员不需要了解对象在内存中的情况,这是一件非常幸福的事情,不需要malloc,free等等,更不需要管垃圾回收。但是,在一些情况下,比如内存占用非常大的应用(像hadoop namenode),就需要对内存占用十分敏感,比如我之前的blog曾经计算过,如果大规模设置ACLs后对hadoop集群内存的压力。 这种情况就需要仔细的计算java类的内存使用情况,一点点的内存节省,当面临成千万的对象的时候效果就会很明显。

       JAVA对象内存计算分为shallow size和deep size。shallow size就是这个对象自己占用的内存空间,包括有基本数据类型占用的内存,其它reference对象的reference 占用的内存。和deep size就是这个除了这个对象自己占用的内存空间,再加上reference对象的占用内存综合。我更感兴趣的是deep size。 一下所有计算都是按照64 bit进行的,32 bit请自行查询。默认情况下java6都是开启-XX:+UseCompressedOops,会对object 的header计算有影响。

        64 bit object的header压缩的占用12byte,不压缩的占用16byte,包括有对象类型的指针,锁信息,以及hash等等。 基本的类型内存占用如下: mem1

       对象的reference64位不压缩的占用8 bytes, 开启压缩占用4 bytes。

       内存layout规则如下:

       1、对象在内存中都是8字节对齐的,即内存计算后不能被8整除,要补充到8字节对齐

       2、基本数据类型也需要对齐,根据类型占用字节数对齐,比如long、double8字节对齐, int,float 4字节对齐

       3、在多数情况下,由于对齐的原因,JVM会按照类型占用的字节多少排列顺序,占用多的排在前面,占用少的排在后面,对象的reference排在最后。比如:

class MyClass {
   byte a;
   int c;
   boolean d; 
   long e;
   Object f; 
}

       如果按照定义的顺序,那么内存使用如下,总共有14 bytes的padding。

[HEADER: 16 bytes] 16 
[a: 1 byte ] 17
[padding: 3 bytes] 20 
[c: 4 bytes] 24 
[d: 1 byte ] 25 
[padding: 7 bytes] 32 
[e: 8 bytes] 40 
[f: 4 bytes] 44 
[padding: 4 bytes] 48

       如果按照大小排列,也就是2中的规则排列,少使用了6 bytes。

[HEADER: 16 bytes] 16 
[e: 8 bytes] 24 
[c: 4 bytes] 28 
[a: 1 byte ] 29 
[d: 1 byte ] 30 
[padding: 2 bytes] 32 
[f: 4 bytes] 36 
[padding: 4 bytes] 40

       4、class field之间不会混在一起,如果class B extends class A,那么父类所有field先去排列,接下来是子类,同时子类起始位置需要对齐4 byte。

       5、对于数组来说,header除了上面说的以外,还需要加入8 bytes,标识array 长度

DataNode BlockReport bug解决(二)

三、bug复现

       测试环境生成一个5M的测试文件,文件路径为/test/missdisk/part-m-0000,fsck查看了一下,该文件只有一个block,blockid为blk_1074399823_659259,该block的三个副本分别分布在5.42、5.26和5.27上面。

为了重现问题,选择5.26和5.27停掉DN,同时在5.42上面将将存放块blk_1074399823的盘data6下线。

       使用命令 echo “scsi remove-single-device 00 02 06 00″ > /proc/scsi/scsi

       

       查看5.42的DN日志,日志显示FsDatasetImpl移除data6盘,同时从FsDatasetImpl的数据结构中移除data6上面的所有副本信息,5.42会errorReport 将data6盘掉盘信息汇报给NN,NN记录下了。但是NN并没有将该盘上面的块信息从NN的数据结构blockmap中移除,由于5.26和5.27的DN停掉了,所以这个时候NN会从5.42上面仅存的副本(实际上已经坏了)repicate到其他两个节点上面去。当从5.42拷贝数据副本到其他节点时,实际上DN认为该副本其实已经损坏,并将块损坏了不能拷贝到其他节点上报给NN,最终副本拷贝失败。等待一段时间之后执行fsck,发现nn显示42节点副本是好的,但是get文件时报错。50070页面显示没有missing block,但是在Live Nodes页面对应的DN又有Failed Volumes。该问题成功重现。


四、解决方案

https://issues.apache.org/jira/browse/HDFS-7208?jql=project%20%3D%20HDFS%20AND%20text%20~%20errorReport

社区提出了解决这个问题的四种方案:

1、DN通过DatanodeProtocol.reportBadBlocks将坏盘中对应的数据块信息通知NN,让NN从数据结构中移除这些块信息。这种方式比较慢,因为每次只能上报一个block。

2、DN通过 DatanodeProtocol.errorReport将坏盘的storageId告诉给NN,从而让NN移除storageId下面所要的块信息,这种方式需要修改DatanodeProtocol.errorReport接口,增加storageId参数,同时DN端代码需要大量调整,比较麻烦。 

3、DN通过blockReport的方式告诉NN有坏盘,并从NN中移除坏盘中得数据块信息,由于blockReport每6小时才会执行一次block的全量汇报,这种方式会有很大的延迟。

4、DN通过心跳的方式告诉NN有坏盘,并从NN中移除坏盘中得数据块信息,社区提供的patch就是基于这种方式。这种方式不用只用修改NN端的代码。