最近在项目中开展重构活动,对Map端内存尽量要省一些,当前的系统中Map端内存最高占用大概3G左右(设置成2G时会导致Java Heap OOM)。虽然个人觉得占用不算多,但是显然这样的结果想要试图去说服一些对内存占用非常挑剔的C++程序员们理由还是不够,于是便通过一定的方式对内存的占用进行了分析,刨根问底。
 
关于运行时内存占用可以参考文章:http://brandnewuser.iteye.com/blog/2113828, 这里采用的是简单的方式,通过反射将内存MemoryCounter的方式计算的内存占用。
 

分析后的内存占用

分析后的内存占用峰值,即在我们的报表数据dump之前,就是属于内存占用的峰值。由于报表对象占用的数据非常大,大概1G左右,而且Map的输出中介结果Value格式是BytesWritable,是通过Java序列化方式dump出来的,因此当时的byte[]非常大(甚至可能超出1G)。于是,便采用了拆分报表输出的方式,这样便可以节省一定的内存空间。
 
在分析内存的占用过程中,尽量查找一些不太合理的内存占用,于是我们便查找到其中的一个类,在初始化后,占用500M的内存,这就是其中使用到的org.apache.hadoop.fs.FSDataInputStream,为什么这个类的对象会占用500M内存?
 
代码中初始化org.apache.hadoop.fs.FSDataInputStream后,就立刻占用500M的内存,那么是否这个类我们使用的方式不对?经过我们单独写了一个应用程序的测试(读取的HDFS与上面的环境一致),其内存占用并没有达到哪怕是1M。
 
org.apache.hadoop.fs.FSDataInputStream接收一个InputStream参数,经过打印其具体实现类型,发现为:org.apache.hadoop.hdfs.DFSInputStream,初始化这个类时,内存占用就已经上去了。
     
于是在MemoryCounter中加入日志,将对象size大于100M的对象打印出来,发现有一个byte[]长度为536870912占用内存!
Exceed 100M object[Array]:
byte Exceed 100M object[Array]:
[B Array length: 536870912
 
初步计算了一下byte[]长度为536870912,估计为2的29次幂,根据条件打印计算出来的结果,发现:
Start estimate: org.apache.hadoop.mapred.MapTask$MapOutputBuffer:
Start estimate: org.apache.hadoop.util.QuickSort:
Start estimate: [B:
Start estimate: [B:
Exceed 100M object[Array]: [B
 
于是恍然大悟,是MapOutputBuffer,然后查看了任务配置的参数,果然
mapreduce.task.io.sort.mb=512
缓冲区设置占用了512M,也就是2的29次幂,这是需要占用Map端的Java Heap内存,下面就是对这部分的一个学习和回顾。
 

Map Collect 过程分析

待每次map函数处理完一对key/value,并产生新的key/value后,就会调用OutputCollector.collect函数输出结果,函数内部首先会调用Partitioner.getPartition()获取纪录的分区号,将<key, value, partition>传递给MapOutputBuffer.collect函数作进一步的处理。
 
MapOutputBuffer内部使用了一个缓冲区暂时存储用户输出数据,当缓冲区的数据达到一定阈值的时候,再将缓冲区的数据写到磁盘上。缓冲区采用的是环形内存缓冲区保存数据(大小为mapreduce.task.io.sort.mb),当达到一定数值(mapreduce.map.sort.spill.percent)后,由线程SpillThread将数据写到一个临时文件中,当所有的数据都处理完成后,对所有的临时文件进行一次合并生成一个最终文件。环形缓冲区使得Map Task的Collect阶段和Spill阶段可以并行地进行。
 
MapOutputBuffer采用了两级索引结构,涉及到3个环形内存缓冲区,三个缓冲区占用内存的总空间为mapreduce.task.io.sort.mb。

 
 
  1. kvoffsets, 偏移量索引数组,用于保存key/value信息在位置索引kvindices中的偏移量,一对key/value需要占用kvoffsets的1个int大小,数组kvindices的3个int大小
  2. kvindices, 位置索引数组,用于保存key/value值在数据缓冲区kvbuffer中的起始位置
  3. kvbuffer,数据缓冲区,用于保存实际的key/value值,默认情况下可以最多使用整个缓冲区的95%
 
以上几个缓冲区读写均采用了典型的生产者消费者模型,MapOutputBuffer的collect方法和MapOutputBuffer的write方法是生产者,spillThread线程是消费者,通过可重入互斥锁的条件变量来完成的。
 
Spill过程是由SpillThread线程完成的,SpillThread线程实际上是缓冲区kvbuffer的消费者,调用sortAndSpill方法将环形缓冲区kvbuffer中的数据写到磁盘上,函数的工作原理如下:
 
  1. 利用快速排序算法对缓冲区kvbuffer的数据进行排序,先按照partition进行排序,然后按照key进行排序。经过排序后,数据以分区为单位聚集在一起,同一分区内的所有数据按照key有序;
  2. 按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件output/spillN.out(N为spill的次数),如果用户设置了Combiner,写入文件之前可能会对每个分区的数据进行一次数据聚集操作;
  3. 将分区数据的元信息写到内存索引数据结构SpillRecord中,每个分区的元信息包括在临时文件的偏移量,压缩前数据大小和压缩后数据大小,如果内存中的索引大小超过1M,将内存索引写到索引文件中output/spillN.out.index中。
 
当所有数据处理完成之后,Map Task会将所有的临时文件合并成一个大的文件,保存到output/file.out.index中。在进行文件合并的过程中,以分区为单位,对于某个分区,采用多轮递归合并的方式:每轮合并io.sort.factor个文件,并将产生的文件重新加入待合并列表中,重复以上过程以最终得到一个大文件。
 
 
 
 
 
 
 
 

项目中Map端内存占用的分析的更多相关文章

  1. 项目中Map端数据处理不均匀性分析

    Map任务的不均匀性 最近发现Map端数据越来越不均匀,而处理输入的数据,写到本地磁盘的数据量都差不多,我们随便拿出来两个attempt任务(当前map数量为64个),33和45,33的counter ...

  2. map的内存分配机制分析

    该程序演示了map在形成的时候对内存的操作和分配. 因为自己对平衡二叉树的创建细节理解不够,还不太明白程序所显示的日志.等我明白了,再来修改这个文档. /* 功能说明: map的内存分配机制分析. 代 ...

  3. [转帖]Linux中buff/cache内存占用过高解决办法

    Linux中buff/cache内存占用过高解决办法 https://www.cnblogs.com/rocky-AGE-24/p/7629500.html /proc/sys/vm/drop_cac ...

  4. sqoop关系型数据迁移原理以及map端内存为何不会爆掉窥探

    序:map客户端使用jdbc向数据库发送查询语句,将会拿到所有数据到map的客户端,安装jdbc的原理,数据全部缓存在内存中,但是内存没有出现爆掉情况,这是因为1.3以后,对jdbc进行了优化,改进j ...

  5. iOS中引用计数内存管理机制分析

    在 iOS 中引用计数是内存的管理方式,虽然在 iOS5 版本中,已经支持了自动引用计数管理模式,但理解它的运行方式有助于我们了解程序的运行原理,有助于 debug 程序. 操作系统的内存管理分成堆和 ...

  6. 解决webpack项目中打包时候内存溢出的bug JavaScript heap out of memory

    vue 项目 npm run dev 的时候一直卡住不动:后来找到报错是 Ineffective mark-compacts near heap limit Allocation failed - J ...

  7. MapReduce项目中的一个JVM错误问题分析和解决

    最近一周都在查项目的各种问题,由于对原有的一个MapReduce分析数据的项目进行重构,减少了运行时的使用资源,但是重构完成后,在Reduce端总是不定时地抛出JVM的相关错误,非常随机,没有发现有什 ...

  8. C#中XmlSerializer的内存占用问题

    被XmlSerializer掉坑里了,爬了一晚上才出来. 本来实现一个功能,从数据库中查出一堆数据(比较多,几十万,不过,是分批查出来的),查出来的数据包含了一个XML字符串,代码中对其进行序列化,一 ...

  9. 优化 UWP 中图片的内存占用

    跟图片打交道的 UWP 应用或多或少都会遇到图片带来的性能问题,就算不主要处理图片,做个论坛做个新闻客户端都涉及到大量图片.一个帖子.一篇文章里多半都是些高清大图,这些图片一张即可占用程序 1~2M ...

随机推荐

  1. C++中几个值得分析的小问题(2)

    下面有3个小问题,作为C++ Beginner你一定要知道错在哪里了. 1.派生类到基类的引用或指针转换一定“完美”存在? 一般情况,你很可能会认为:派生类对象的引用或指针转换为基类对象的引用或指针是 ...

  2. chrome浏览器Uncaught TypeError: object is not a function问题解决

    今天测试多浏览器的时候,chrome浏览器出现Uncaught TypeError: object is not a function: 解决办法:(不知道为啥子)改一下js的方法名字就可以了

  3. 剑指offer--44.两个链表的第一个公共结点

    @selfboot 牛逼的代码,长度相同,一遍出结果, 长度不同,短的点跑完,变成长的,当长的跑完变成短的链表的时候,较长的链表已经走过了多的结点. ------------------------- ...

  4. Vim技能修炼教程(5) - 操作符实务

    操作符实务 操作符复习 上次我们讲了操作符与文本对象的组合这样一个vim的强大功能.但是上节的知识点过于密集,可读性可能差了一点.不过没关系,重要的知识点在后面用到的时候我们可以先复习一下. 我们还是 ...

  5. arduino 编程基础

    0. setup() 与 loop() 函数 void setup(): // put your setup code here, to run once: 仅执行一次: void loop(): / ...

  6. learn go random

    package main // 参考文档: // go 基本类型和运算符 // https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/e ...

  7. erl_0021 erlang和java的内存模型比较(引用)

    原文  http://deepinmind.iteye.com/blog/2030390 我读到一篇相当相当有趣的关于Erlang VM内存管理策略的文章.它是Jesper Wilhelmsson写的 ...

  8. erl_0020 《面对软件错误构建可靠的分布式系统》读书笔记001 “面向并发COPL”

    在现实世界中,顺序化的(sequential)活动非常罕见.当我们走在大街上的时候,如果只看到一件事情发生的话我们一定会感到不可思议,我们期望碰到许多同时进行的活动. 如果我们不能对同时发生的众多事件 ...

  9. HihoCoder1049 后序遍历 分治水题

    水题,是为了给难题(树形DP)做铺垫 描述 在参与过了美食节之后,小Hi和小Ho在别的地方又玩耍了一阵子,在这个过程中,小Ho得到了一个非常有意思的玩具——一棵由小球和木棍连接起来的二叉树! 小Ho对 ...

  10. Java并发--线程间协作的两种方式:wait、notify、notifyAll和Condition

    在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界 ...