使用Protocol Buffers时可能导致java.lang.OutOfMemoryError的原因是什么?

时间:2014-03-10 12:08:25

标签: java redis protocol-buffers jedis

运行下面的代码会导致Java堆空间异常。

来自.NET并掌握GC的工作原理我想知道在尝试运行以下内容时,在内存管理方面是否需要考虑一些事项:

public static void main(String[] args) throws NumberFormatException, ParseException, IOException {

    Jedis jedis = new Jedis("<HostName>");

    TimeSeriesPoints retrieved = null;

    while(!finished) {

        try {

            finished = true;

            List<String> keys = getNextFiftyKeys();

            String[] cacheKeys = new String[keys.size()];

            List<String> cacheResults = jedis.mget(keys.toArray(cacheKeys));

            List<TimeSeries> cachedTimeSeries = new ArrayList<TimeSeries>();

            for(String cacheResult : cacheResults){
                try {
                    retrieved = TimeSeriesPoints.parseFrom(cacheResult.getBytes());

                    TimeSeries timeSeries = new TimeSeries(retrieved.getName(), retrieved.getPointsList());

                    cachedTimeSeries.add(timeSeries);
                } 
                catch (InvalidProtocolBufferException e) {
                    e.printStackTrace();
                }
            }

            long pointsCount = 0;
            for(TimeSeries timeSeries : cachedTimeSeries){
                pointsCount += timeSeries.points.length;
            }

            System.out.println("retrieved: " + cachedTimeSeries.size());
            System.out.println("points:" + pointsCount);            
    }
}

使用堆栈跟踪在 TimeSeriesPoints.parseFrom 处抛出异常,如下所示。不确定为什么。

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.ArrayList.ensureCapacity(Unknown Source)
    at java.util.ArrayList.add(Unknown Source)
    at com.wimiro.caching.TimeSeriesProtos$TimeSeriesPoints.<init>(TimeSeriesProtos.java:115)
    at com.wimiro.caching.TimeSeriesProtos$TimeSeriesPoints.<init>(TimeSeriesProtos.java:82)
    at com.wimiro.caching.TimeSeriesProtos$TimeSeriesPoints$1.parsePartialFrom(TimeSeriesProtos.java:151)
    at com.wimiro.caching.TimeSeriesProtos$TimeSeriesPoints$1.parsePartialFrom(TimeSeriesProtos.java:1)
    at com.google.protobuf.AbstractParser.parsePartialFrom(AbstractParser.java:141)
    at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:176)
    at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:188)
    at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:193)
    at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:49)
    at com.wimiro.caching.TimeSeriesProtos$TimeSeriesPoints.parseFrom(TimeSeriesProtos.java:958)
    at program.main(program.java:77)

试图阅读800个时间序列(每个都有~4000个数据点)。由于我在这个例子中一次只处理50个时间序列,所以我不希望内存占用量大幅增长。

在.NET中,我没有遇到任何困难。是时候学习一些Java了。我需要阅读什么?

2 个答案:

答案 0 :(得分:0)

您正在尝试从java String(UTF-16)获取字节数组,这些数组来自jedis(使用SafeEncoder将UTF-16转换为bytearray),该数组调用redis(C char 8位编码)。我认为这是你问题的根源,你的java字符串可能不正确,这会使protobuf失败。

您应该尝试使用jedis中的字节数组签名:

final List<byte[]> mget = jedis.mget(byteArray1, byteArray2, ...);

然后尝试在字节数组上使用protobuf。还要检查如何使用jedis在redis中插入数据,在所有情况下,建议将字节数组签名与二进制数据一起使用。

答案 1 :(得分:0)

不幸的是,Protobuf-Java对重复的基元类型使用了非常低效的编码 - 每个元素都是盒装的。例如。 repeated int32表示为ArrayList<Int>。从理论上讲,这可以在protobuf实现中进行优化,但最后我才知道这一点。我猜你的问题源于此。

如果您还没有,请尝试增加JVM的最大堆大小(例如,-Xmx2g为2GB)。