代码之家  ›  专栏  ›  技术社区  ›  TimothyP

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

  •  1
  • TimothyP  · 技术社区  · 14 年前

    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接收消息的部分会导致以下异常:

    空间 在org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod。java:92) 在groovy.lang.MetaMethod.doMethodInvoke(MetaMethod。java:234) 在org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass。java:272) 在groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl。java:880) 在groovy.lang.Closure.call(Closure。java:292) 在org.codehaus.groovy.runtime.DefaultGroovyMethods$6.run(DefaultGroovyMethods。java:11563) 原因:java.lang.OutOfMemoryError: Java堆空间 在java.util.ArrayList.add(ArrayList。java:378) 在sun.reflect.GeneratedMethodAccessor9.invoke(未知 在sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl。java:43) 在java.lang.reflect.Method.invoke(方法。java:616) 在org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite。java:52) 在sun.reflect.NativeMethodAccessorImpl.invoke0(本机 在sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl。java:57) 在java.lang.reflect.Method.invoke(方法。java:616) 在org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass。java:361) 在groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl。java:880) 位于org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite。java:151) 在RTSGatewayServer$\u forwardMessage\u closure2.doCall(RTSGatewayServer。groovy:35) 在sun.reflect.NativeMethodAccessorImpl.invoke0(本机 方法) 在java.lang.reflect.Method.invoke(方法。java:616) 在org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod。java:86) 在org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass。java:272) 在groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl。java:880) 位于org.codehaus.groovy.runtime.DefaultGroovyMethods.withStreams(DefaultGroovyMethods。java:11462) 位于org.codehaus.groovy.runtime.dgm$658.invoke(未知 (来源)

    我猜有一种比使用 bytes.add(...)

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

    4 回复  |  直到 14 年前
        1
  •  3
  •   djna    14 年前

    所以脚本一直读取和存储字节,直到看到0xff。

    这似乎是个有缺陷的设计。无论如何调优JVM,最终都可能耗尽内存。如果远程服务选择在0xff之前发送peta字节的deta,那么您将耗尽内存。我的看法是,你应该总是假设另一个参与者可能是坏的或反社会的。

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

        2
  •  1
  •   Blauohr    14 年前

        3
  •  1
  •   Ravindra    14 年前

    实际上是来自套接字的数据导致了OutofMemory吗?

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

        4
  •  0
  •   Matthew Mark Miller    9 年前

    如果你看了合同,

    InputStream.read():int 
    

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

    如此固定:

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

    //---其他问题

    当然,您应该为这段代码设置一个上限以避免OOM,但您遇到的具体问题是

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

    这将创建一个ArrayList,这是一个以数组为基础的数据结构,它从分配一个数组开始,每次达到以前的容量时,它的大小都会增加一倍。我相信默认大小只有10个元素。

    因此,您在这里隐式地创建了大量数组,这就是堆栈跟踪的混乱所抱怨的。在一个高性能的应用程序中,消息大小适中,为数不多的K到M,分配新数组的速度很可能比GC所能达到的速度还要快。