我有一个序列文件,包含~ 12,000个键/值对。最大的值是143MB,其他值小于1MB。"mapred.child.java.opts
"设置为"-Xmx500m
"。当处理这个文件时,我得到一个Error: Java heap space
。这是我的Mapper:
public class MyMapper extends Mapper<Text, BytesWritable, Text, Text> {
@Override
protected void setup(Context context) throws IOException, InterruptedException {
super.setup(context);
}
@Override
protected void map(Text key, BytesWritable value, Context context) throws IOException, InterruptedException {
Configuration conf = context.getConfiguration();
String content = new String(value.getBytes());
// remove HTML tags (content is a HTML document)
String content_no_html = Jsoup.parse(content).text();
// try to find a regex match
}
}
}
我认为这可能是因为我的Map类正在将值从BytesWritable转换为String,然后处理该字符串。但是我试图只处理映射器中的键/值对,如果value.getLength() < 8388608
(8 MB),仍然内存不足。
我可以增加-Xmx500m
,但我担心这不会解决我的问题,因为我有几千个序列文件,我需要处理,我不知道最大的键/值对有多大(我对大键/值对不感兴趣,因此上面的8MB限制)。为了记录,我尝试了一些序列文件,导致~2500个映射步骤,并且在第一个1990左右成功后发生错误,这使我认为单个键/值对大小是这里的问题。
是否有一种方法只传递键/值对到映射器的大小限制?为什么我一开始就得到Error: Java heap space
?
编辑
我尝试按照建议创建自定义SequenceFileInputFormat,并发现在调用映射函数之前发生堆空间错误。确切地说,SequenceFileRecordReaders nextKeyValue()
是导致错误的原因。我再次检查了地图功能,但它什么都没做,还是崩溃了。我找不到解决错误的方法,所以我想增加内存是我唯一的机会。
8 MB的HTML还是太大了!
作为一个例子,让我们考虑您的8 MB html文本是一个包含80字节长的元素的ul列表,您将有1,000,000个元素:
<ul>
<li class="item" data-index="000000">This is the <b>first</b> line.</li>
<li class="item" data-index="000001">This is the <b>second</b> line.</li>
[...]
<li class="item" data-index="999999">This is the <b>last</b> line.</li>
</ul>
每个li元素的Jsoup Element
类(它是Node
的子类)具有以下结构,从中我们可以估计(理论上和近似地)我的示例html列表的内存占用:- 元素。tag:对tag对象的引用(其中1字符串+ 8布尔值)=> 64字节
- 元素。classNames: Set of Class names => 56 bytes
- 节点。parentNode: Reference to Node => 8 bytes
- 节点。attributes:对attributes对象的引用=> 152字节
- 包含一个
LinkedHashMap<String, Attribute>
和一个条目(48字节)- 属性包含2个字符串(~56*2=112字节)
- 包含一个
- 节点。childNodes:节点列表
- 3 TextNodes(因为b标签)=>大约。每个100字节(文本内容+节点字段,这是内存开始!)
所以列表中单个元素所使用的内存大约是580字节。
这1,000,000个元素使用的总内存约为580mb。
同样,主ul元素包含了一个包含1,000,000个引用的列表,这些引用来自于li元素在它的childrenNodes字段中,所以它占用了8mb的内存。
最后,当调用text()
时,如果删除标记后留下的文本对应于5MB的序列化字符,那么您需要一个500万char
s的String
,占用5,000,000*2 = 10 MB。
如果你的html就像我展示的列表,那么你需要在你的堆中存储,在调用map()
函数的时候:
-
new String(value.getBytes())
=> ~ 16mb -
Jsoup.parse(content)
=> ~ 590mb -
.text()
=> ~ 10mb
难怪你得到一个java堆空间错误,堆只有500 MB !
使用JSoup解析文件时会发生"内存爆炸",因为它必须创建大量对象,而java对象,特别是字符串,非常重。
这完全取决于你的html看起来像什么。如果你的html有大约相同的html标签/属性的浓度作为我的例子,我认为你可以定义你的阈值约2 MB是安全的。
您可以在value.getLength() < 2_000_000
开始时退出映射函数,就像您所做的那样。或者,如果不浪费数据对您来说很重要,您可以做一些更花哨的事情,比如运行一个regex来计算content
字符串中的标记或属性的数量。如果你有20mb的文本,但里面只有一个标签,它仍然适合内存,不需要跳过它