Hadoop0.21内存泄漏问题:数据块映射管理的一个bug

我们的HDFS生产环境是Hadoop-0.21,机器规模200台,block在7KW左右. 集群每运行几个月,NameNode就会频繁FGC,最后不得不restart NameNode. 因此怀疑NameNode存在内存泄漏问题,我们dump出了NameNode进程在重启前后的对象统计信息。

07-10重启前:

num     #instances         #bytes  class name

----------------------------------------------

1:      59262275     3613989480  [Ljava.lang.Object;

...

10:       8549361      615553992  org.apache.hadoop.hdfs.server.namenode.BlockInfoUnderConstruction

11:       5941511      427788792  org.apache.hadoop.hdfs.server.namenode.INodeFileUnderConstruction

...

07-10重启后:

num     #instances         #bytes  class name

----------------------------------------------

1:      44188391     2934099616  [Ljava.lang.Object;

...

23:        721763       51966936  org.apache.hadoop.hdfs.server.namenode.BlockInfoUnderConstruction

24:        620028       44642016  org.apache.hadoop.hdfs.server.namenode.INodeFileUnderConstruction

...

从上面的信息可以看出,NameNode节点重启前最占内存的对象是[Ljava.lang.Object、[C、org.apache.hadoop.hdfs.server.namenode.INodeFile、org.apache.hadoop.hdfs.server.namenode.BlockInfo、[B、org.apache.hadoop.hdfs.server.namenode.BlockInfoUnderConstruction$ReplicaUnderConstruction等,它们的引用关系如下:

其中,根据NameNode节点的内部处理逻辑,INodeFileUnderConstruction和BlockInfoUnderConstruction都属于中间状态,当文件的写关闭之后,INodeFileUnderConstruction会变成INodeFile,BlockInfoUnderConstruction会变成BlockInfo,而集群的文件写压力不可能在100W/s级别,因此,NameNode节点可能存在内存泄漏。

文件在close时会调用NameNode的complete方法来关闭,此时BlocksMap的映射就会从BlockInfoUnderConstruction-->BlockInfoUnderConstruction 变成BlockInfo->BlockInfo. (我们暂且描述为oldBlock>oldBlock 替换为newBlock->newBlock) BlocksMap对这一状态转变的处理逻辑是:

 BlockInfo replaceBlock(BlockInfo newBlock) {
    BlockInfo currentBlock = map.get(newBlock);
 
    assert currentBlock != null : "the block if not in blocksMap";
    // replace block in data-node lists
    for(int idx = currentBlock.numNodes()-1; idx >= 0; idx--) {
      DatanodeDescriptor dn = currentBlock.getDatanode(idx);
      Log.info("Replace Block[" + newBlock + "] to Block[" + currentBlock + "] in DataNode[" + dn + "]");
      dn.replaceBlock(currentBlock, newBlock);
    }
    
    // replace block in the map itself
    map.put(newBlock, newBlock);
    return newBlock;
  }

Block重写了hashCode和equals方法,使得newBlock和oldBlock有相同的hashCode,而且newBlock.equals(oldBlock)=true.

上述代码的原意是将map中的Entry(oldBlock,oldBlock)替换成(newBlock,newBlock). 而HashMap在处理put的时候,如果key相同(注意:这里的相同是指(newKey.hashCode==oldKey.hashCode && (oldKey==newKey || oldkey.equals(newKey))). 只会将对应的value替换,导致oldBlock->oldBlock被替换成oldBlock->newBlock, 也就是oldBlock依然没有被释放,也就是所谓的内存泄漏。

请参考HashMap的代码:

    /**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

建议将BlocksMap修复如下,已经提交patch,请见:https://issues.apache.org/jira/browse/HDFS-7592

 BlockInfo replaceBlock(BlockInfo newBlock) {

+   /**
+    * change to fix bug about memory leak of NameNode by huahua.xu  
+    * 2013-08-17 15:20
+    */
 
    BlockInfo currentBlock = map.get(newBlock);
 
    assert currentBlock != null : "the block if not in blocksMap";
    // replace block in data-node lists
    for(int idx = currentBlock.numNodes()-1; idx >= 0; idx--) {
      DatanodeDescriptor dn = currentBlock.getDatanode(idx);
      dn.replaceBlock(currentBlock, newBlock);
    }
    
    // replace block in the map itself

+   BlockInfo currentBlock = map.remove(newBlock);     
    map.put(newBlock, newBlock);
    return newBlock;
  }

截止到目前为止,该patch已经正式更新到线上集群,解决了内存泄漏问题。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

这是公司生产环境中遇到的较为严重的bug,目前已经提交社区:https://issues.apache.org/jira/browse/HDFS-7592 ,在此和大家share一下。如有问题可邮件联系QQ576072986.

时间: 2024-08-30 01:40:48

Hadoop0.21内存泄漏问题:数据块映射管理的一个bug的相关文章

NameNode对数据块的管理

关于块跟副本 hadoop中块是一种逻辑概念而副本才是真正的物理概念,即在DataNode中存储的数据块是以一个叫做的Replica来表示的,而在NameNode中则是以Block来表示.BlockInfo是Block的子类,主要用它来表示一个数据块,这个类中最重要的就是triplets这个数组对象了,假设数据块有i个副本,那么这个数组的长度为3*i,其中下标为3*i的元素记录了第i个副本所在的DataNode信息,而3*i+1以及3*i+2则分别记录了这个DataNode上前一个数据副本以及后

(转)从内存管 理、内存泄漏、内存回收探讨C++内存管理

http://www.cr173.com/html/18898_all.html 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对 C++的痛恨,但内存管理在C++中无处不在,内存泄漏几乎在每个C++程序中都会发生,因此要想成为C++高手,内存管理一关是必须要过的,除非放弃 C++,转到Java或者.NET,他们的内存管理基本是自动的,当然你也放弃了自由和对内存的支配权,还放弃了C++超绝的性能

Android内存泄漏查找和解决

Android内存泄漏查找和解决 目录: 内存泄漏的概念 一个内存泄漏的例子 Java中"失效"的private修饰符 回头看内存泄漏例子泄漏的重点 强引用与弱引用 解决内部类的内存泄漏 Context造成的泄漏 使用LeakCanary工具查找内存泄漏 总结 一.内存泄漏概念 1.什么是内存泄漏? 用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元.直到程序结束.即所谓的内存泄漏. 其实说白了就是该内存空间使用完毕之后未回收 2.内存泄漏会导致的问题 内

HDFS源码分析(三)-----数据块关系基本结构

前言 正如我在前面的文章中曾经写过,在HDFS中存在着两大关系模块,一个是文件与block数据块的关系,简称为第一关系,但是相比于第一个关系清晰的结构关系,HDFS的第二关系就没有这么简单了,第二关系自然是与数据节点相关,就是数据块与数据节点的映射关系,里面的有些过程的确是错综复杂的,这个也很好理解嘛,本身block块就很多,而且还有副本设置,然后一旦集群规模扩大,数据节点的数量也将会变大,如何处理此时的数据块与对应数据节点的映射就必然不是简单的事情了,所以这里有一点是比较特别的,随着系统的运行

一个跨平台的 C++ 内存泄漏检测器

2004 年 3 月 01 日 内存泄漏对于C/C++程序员来说也可以算作是个永恒的话题了吧.在Windows下,MFC的一个很有用的功能就是能在程序运行结束时报告是否发生了内存泄漏.在Linux下,相对来说就没有那么容易使用的解决方案了:像mpatrol之类的现有工具,易用性.附加开销和性能都不是很理想.本文实现一个极易于使用.跨平台的C++内存泄漏检测器.并对相关的技术问题作一下探讨. 基本使用 对于下面这样的一个简单程序test.cpp: int main() { int* p1 = ne

内存泄漏分析工具tMemMonitor (TMM)使用简介

C/C++由于灵活.高效的优点一直以来都是主流的程序设计语言之一,但是其内存的分配与释放均由程序员自己管理,当由于疏忽或错误造成程序未能释放不再使用的内存时就会造成内存泄漏.在大型.复杂的应用程序中,内存泄漏往往是最常见的问题,因而及时解决内存泄漏非常必要.tMemMonitor (TMM)作为一个专业.准确.易用的内存泄漏分析工具,可以帮助C/C++程序员迅速地解决内存泄漏这个令人头疼的问题. TMM下载地址:http://download.csdn.net/download/tmemmoni

内存泄漏与内存溢出是什么?

内存泄漏定义(memory leak): 一个不再被程序使用的对象或变量还在内存中占有存储空间. 一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出.内存溢出 out of memory : 指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出. 二者的关系: 内存泄漏的堆积最终会导致内存溢出 内存溢出就是你要的内存空间超过了系统实际分配给你的空间

使用BBED理解和修改Oracle数据块

1.生成bbed list file文件: SQL> select file#||' '||name||' '||bytes from v$datafile; $ vim dbfile.txt 1 /u01/app/oradata/sydb/system01.dbf 754974720 2 /u01/app/oradata/sydb/sysaux01.dbf 587202560 3 /u01/app/oradata/sydb/undotbs01.dbf 429916160 4 /u01/app/

浅析c#内存泄漏

一直以来都对内存泄露和内存溢出理解的不是很深刻.在网上看到了几篇文章,于是整理了一下自己对内存泄露和内存溢出的理解. 一.概念 内存溢出:指程序在运行的过程中,程序对内存的需求超过了超过了计算机分配给程序的内存,从而造成“Out of memory”之类的错误,使程序不能正常运行. 造成内存溢出有几种情况: 1.计算机本身的内存小,当同时运行多个软件时,计算机得内存不够用从而造成内存溢出.对于这种情况,只能增加计算机内存来解决. 2.软件程序的问题,程序在运行时没能及时释放不用的内存,造成使用的