为什么这个Java / Groovy代码导致堆内存异常?

时间:2010-06-26 08:30:27

标签: java memory groovy heap

此第三方脚本不断导致堆内存异常:

byte[] forwardMessage(byte[] content) {
    s = new Socket("172.17.0.30", 10001);
    s.withStreams {InputStream input, OutputStream output ->
        output.write content
        return readRtsData(input)
    }
}

byte[] readRtsData(input) {
    def vplEndByte = 0xff

    def inStream = new BufferedInputStream(input)

    def bytes = []
    while (bytes.isEmpty() || bytes.last() != vplEndByte) {
        bytes.add(inStream.read())
    }

    bytes
}

脚本的一部分在收到消息后通过TCP / IP接收消息,导致以下异常:

  

线程“Thread-2”中的异常   org.codehaus.groovy.runtime.InvokerInvocationException:   java.lang.OutOfMemoryError:Java堆   空间       在org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:92)       at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:234)       在org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)       at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:880)       在groovy.lang.Closure.call(Closure.java:279)       在groovy.lang.Closure.call(Closure.java:292)       在org.codehaus.groovy.runtime.DefaultGroovyMethods $ 6.run(DefaultGroovyMethods.java:11563)       在java.lang.Thread.run(Thread.java:636)   引起:java.lang.OutOfMemoryError:   Java堆空间       at java.util.Arrays.copyOf(Arrays.java:2746)       at java.util.ArrayList.ensureCapacity(ArrayList.java:187)       at java.util.ArrayList.add(ArrayList.java:378)       at sun.reflect.GeneratedMethodAccessor9.invoke(未知   资源)       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)       在java.lang.reflect.Method.invoke(Method.java:616)       at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite $ PojoCachedMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:229)       在org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:52)       在org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)       在RTSGatewayServer.readRtsData(RTSGatewayServer.groovy:46)       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native   方法)       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)       在java.lang.reflect.Method.invoke(Method.java:616)       在org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)       at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:234)       在org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:361)       at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:880)       在org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)       在org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:151)       在RTSGatewayServer $ _forwardMessage_closure2.doCall(RTSGatewayServer.groovy:35)       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native   方法)       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)       在java.lang.reflect.Method.invoke(Method.java:616)       在org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)       at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:234)       在org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)       at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:880)       在groovy.lang.Closure.call(Closure.java:279)       at org.codehaus.groovy.runtime.DefaultGroovyMethods.withStreams(DefaultGroovyMethods.java:11462)       在org.codehaus.groovy.runtime.dgm $ 658.invoke(未知   源)

我猜测使用bytes.add(...)会有更好的内存效率吗?

如果有人能够将结果与.NET中发生的事情进行比较,那将会更好,因为我是.NET开发人员。

4 个答案:

答案 0 :(得分:3)

因此脚本会一直读取和存储字节,直到看到0xff。

这似乎是一个有缺陷的设计。无论你如何调整JVM,你最终可能会耗尽memry。如果远程服务选择在0xff之前发送peta字节的详细信息,那么你将耗尽内存。我的看法是你应该总是假设其他参与者可能被打破或反社会。

因此,我会对我准备接受的数据设置一些上限。然后,如果可能的话,处理你已收到的块并返回并获取下一个块,或者如果不能以块的形式处理,则礼貌地发出错误消息并停止。

底线:清理您的输入。允许外部过程耗尽你的记忆是一件坏事。当他们说“不可能发生”时,不要相信他们。

答案 1 :(得分:1)

用 - Xmx调整jvm ......?

答案 2 :(得分:1)

它实际上是来自Socket的数据导致OutofMemory吗?

如果您使用的是Java 6,请使用jconsole连接到服务器并查看堆。

答案 3 :(得分:0)

如果您阅读合同,

InputStream.read():int 
当流结束时,

返回-1。

因此,如果流不具有0xff字符(例如,如果流在接收到0xff之前结束),则此代码将永久地将接收到的最后一个字节(例如-1)附加到该数组! / p>

明确修复:

while (bytes.isEmpty() || bytes.last() != vplEndByte || bytes.last != -1) {

// ---其他问题

虽然您应该在此代码上设置上限以避免OOM,但您遇到的具体问题是

def bytes = [] //[] is shorthand for "new ArrayList()"

这将创建一个ArrayList,一个数组支持的数据结构,它通过分配一个数组开始,并在每次达到前一个容量时将其大小增加一倍。我相信默认大小只有10个元素。

因此,你在这里隐式地创建了大量的数组,这就是堆栈跟踪的混乱所抱怨的。在具有几K到M的适度大小消息的高性能应用程序中,您可能比GC可以更快地分配新阵列。

您应该考虑使用合理的初始大小创建该arraylist,以减少隐式对象实例化并引入合理的最大边界。您也可以将该集合切换到链接列表,但可能会降低性能。