我想实现网络传输大量图片,于是用ObjectOutputStream和ObjectInputStream来传送(压缩后)转化为byte数组的图片,程序一开始运行正常,但等过了五分钟左右,接收图片端会出现java.lang.OutOfMemoryError。于是我用eclipse的插件MemoryAnalyzer检查了出问题时的堆栈状态,发现有一个ObjectInputStream的实例对象(注:我的程序中只建了一个ObjectInputStream的实例,用来读取byte数组,这个实例在我的程序运行过程中没有close过,接收所有的byte数组都始终在用这一个实例)竟占了整个Java虚拟机内存的90.15%!进一步往下看我发现(如下图)似乎发送过的byte数组全被保存到了一个与ObjectInputStream有关的Object数组中了(有606个之多)。这个问题感觉上不是我自己写的代码导致的问题,好象是ObjectInputStream本身的问题,而我也看了一下ObjectInputStream的方法,似乎没有与这个有关的。实在是没办法了,请各位高手帮忙解决啊!

解决方案 »

  1.   

    呵呵..首先,这肯定不是ObjectInputStream的问题,
    以前也遇到过, 这是你程序写的有问题,当你在发送端每发送完一个对象后,就要调用相应的flush()方法,把对象给发到另一端去.不然对象多了就会出这种问题
    希望有用.
      

  2.   

    记住:在流中传输大量文件时,flush()是很重要的.
      

  3.   

    楼主未必有用BufferedInputStream 怎么能flush呢
    是不是一下子传的图片过多 对方一个一个字节得没来得及接受 你这儿一直在上传 就内存溢出了。。
      

  4.   

    是不是楼主把byte数组设的太大了
    另外,建议使用有buffer的流楼主可以把byte数组设的小点,然后循环读取
    这样每次循环会把byte数组清空另外,说得再多,方便的话,上代码吧
      

  5.   

    首先,我觉得楼主选择ObjectInputStream读图片就不对。
    ObjectInputStream和ObjectOutputStream搭配使用,用来实现java对象的序列化存储和读入。
    而你要传输大量的图片,我觉得选择带缓冲的普通字节流更合适。另外,在我的理解里,ObjectInputStream只负责读数据,而不负责保存数据。
    例如,javadoc中有如下的例子:        FileInputStream fis = new FileInputStream("t.tmp");
            ObjectInputStream ois = new ObjectInputStream(fis);        int i = ois.readInt();
            String today = (String) ois.readObject();
            Date date = (Date) ois.readObject();读出来的数据都是保存在变量中的。就比如,用抽水机把池塘里的水抽到田里。
    ObjectInputStream只是一个抽水机。所以说,问题不在ObjectInputStream里。
      

  6.   

    LZ说“于是用ObjectOutputStream和ObjectInputStream来传送(压缩后)转化为byte数组的图片”,就是你说的“ObjectInputStream和ObjectOutputStream搭配使用”方法了。但是却不行。
      

  7.   

    您说的应该不是问题所在,因为我在发送端每次发送完一个byte数组后确实都调用过flush方法,而且,错误是出现在接收端,非发送端。
      

  8.   

    至少“ObjectInputStream和ObjectOutputStream搭配使用”这句话看懂了,似乎在说LZ没有搭配使用。
      

  9.   

    to:yingle2000
    ObjectInputStream和ObjectOutputStream搭配使用,用来实现java对象的序列化存储和读入。 
    我这句话的重心在后面。
      

  10.   

    谢谢大家,既然ObjectInput/OutputStream不合适,我就用基本的流吧。其实我一开始也想用基本流来传送数据,但有一个问题:若用read()或readByte()的话,没有可用的结束标记来区分前一幅图和后一幅图,因为我用的流是用socket.getInput/OutputStream()得来的,若发完一张图的数据后close此流,接收端是会得知一张图片的数据发送完了,但相应的socket也会断开,我还需要重新连,这显然是不可行的。总之,我需要一直用一个不断的流来传送数据,而且还需要接收端能够判断出前一张图片数据段的结束(即后一张图片数据段的开始)。
      

  11.   

    事实上,有没有问题必须自己亲自试过才能下结论,ObjectIn/OutputStream确实有点问题,最基本的用法如下所示,就会出现OutOfMemory(当然,不是不能解决,所以就要想办法)://St.java
    import java.io.*;public class St { public static void main(String[] args) throws Exception
    {
    ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream("st.serial"));
    for(int i = 1; i < 0x40000000; i++)
    {
    oout.writeObject(new MySerialCls(i, String.valueOf(i)));
    oout.flush();
    //System.gc();
    System.out.println(i);
    }
    oout.close();
    ObjectInputStream oin = new ObjectInputStream(new FileInputStream("st.serial"));
    while(true)
    {
    try
    {
    Object o = oin.readObject();
    if(o == null)
    break;
    System.out.println(o);
    //System.gc();
    }
    catch(EOFException e)
    {
    break;
    }
    }
    oin.close();
    }}//MySerialCls.java
    import java.io.Serializable;public class MySerialCls implements Serializable
    {
    private int a;
    private String b;
    public MySerialCls(int a, String b)
    {
    this.a = a;
    this.b = b;
    }
    public int getA()
    {
    return a;
    }
    public void setA(int a)
    {
    this.a = a;
    }
    public String getB()
    {
    return b;
    }
    public void setB(String b)
    {
    this.b = b;
    }
    public String toString()
    {
    return "MySerialCls[a=" + a + ",b=" + b + "]";
    }
    }
      

  12.   


    Java不是有个视频聊天组件么.. 而且不用一张张读JPG 而是定时去读视频播放流吧 LZ思维够灵活 运用了最原始的动画手段 佩服 佩服..
      

  13.   


    谢谢指导,您说的那个组件应该是JMF中的吧,是个不错的选择,我先研究研究,有了问题再请教大家。不过,我最开始提出的问题还是没有解决,虽然我打算改用JFM,但我还是很想知道为什么会出现内存溢出,我读取数据是在一个方法中进行的,得到的数据是存在该方法中定义的局部变量中,所以每次方法结束都会释放,但不知为啥,根据内存状态监测,莫名其妙的出现了一个Objct数组,里面似乎存着所有我读过的数据,把内存占完了,这是不是就是传说中的"内存泄漏"?
    希望有高手能仔细看看我发的图,告知我图中那个Object数组的来历(图中显示的应该是包含关系,"java.io.ObjectInputStream @ 0x2492df20"里包含"java.io.ObjectInputStream$HandleTable@0x2492e0e8"而"java.io.ObjectInputStream$HandleTable @ 0x2492e0e8"里包含"java.lang.Object[703] @ 0x26802958",这一切似乎真的与那个ObjectInputStream的对象有关。
      

  14.   

    这是接收端的代码,就是这边出了问题。public class ScreenViewer extends JLabel implements Runnable {    private Socket socket;
        private BufferedImage image;
        private ObjectOutputStream out;
        private ObjectInputStream in; /**
         *
         * @param Socket 用于进行操控的服务端Socket
         * @param input 即new ObjectIntputStream(socket.getInputStream())
         * @param output 即new ObjectOutputStream(socet.getOutputStream())
         */
        public ScreenViewer(Socket Socket, ObjectInputStream input, ObjectOutputStream output) {
            super();
            this.socket = Socket;
            image = null;
            in = input;
            out = output;
        }
    /**
         * 获取服务端传来的图像的线程
         */
        @Override
        public void run() {        while (true) {
                getImage();
            }    } /**
         * 从服务端获取的输入流中读取图像对象,并用JPEG进行解码
         * 显示在本类的对象(JLabel)中(用paint()方法drawImage)
         */
        private void getImage() {
            try {
                Object bufIn = in.readObject();
                byte[] buffer = (byte[]) bufIn;
                ByteArrayInputStream byteIn = new ByteArrayInputStream(buffer);            JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(byteIn);
                BufferedImage image = decoder.decodeAsBufferedImage();
                this.image = image;
                this.setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
                this.updateUI();            bufIn = null;
                buffer = null;
                byteIn.close();
                byteIn = null;
                decoder = null;
                image = null;
               
            } catch (Exception e) {
                e.printStackTrace();
            }
        }     /**
         * 覆盖父类paint()方法,用于绘制从服务端传来的图像
         */
        public void paint(Graphics g) {
            super.paint(g);
            if (image != null) {
                g.drawImage(image, 0, 0, this);
            }    }
        public static void main(String[] args){
        Socket socket=new Socket("127.0.0.1",8888);
        ScreenViewer client=new ScreenViewer(socket,
                         socket.getInputStream(),socket.getOutputStream());
        new Thread(client).start();
        }}
      

  15.   

    搞定了,怕硬盘会炸掉运行到170万条就停了网上搜的方法,ObjectOutputStream 会保留之前的对象方便引用,还不是很明白……
    间隔一工作量后重置就可以了
    for(int i = 1; i < 0x40000000; i++)
            {
                oout.writeObject(new MySerialCls(i, String.valueOf(i)));
                oout.flush();
                if(i%100000==0)
                 oout.reset();
                System.out.println(i);
            }
      

  16.   

    bayougeng 说的很明白了 这个最适合序列化操作 把对象保存成文件
      

  17.   

    刚才用自己的Socket试了一下,发送50条相同的对象,550byte 的流量 (之前用字符流是 2000byte)然后修改代码,在每次发送后都使用reset(),结果有 6000byte 的流量说明 ObjectOutputStream 和 ObjectInputStream 之间会相互协调,通过一个特定"缓存"来提高效率猜测:发送端发现有重复的对象,就询问接收端是否还缓存着该对象,如果接收端有的话就直接引用
      

  18.   

    这句话说到了关键点,Java 发送对象因为涉及到循环引用,比如 parent.getChild(0).getParent().getChild(0). 一个 child 引用  parent 而 parent 又引用 child. 这种情况下, 一次发送会话,必须确保所有引用的相关对象全部发送完发送端才能清空缓存,而接收到没有收到发送端的"已经发送完" 的通知也不会清空缓存,如果提前清空的话,在接收端收到了循环引用的对象就会再建立另外一个状态完全相同但内部引用标志确不同的两个对象实例,这样就出问题了,因为实际上它应该只有一个实例存在,解决问题的办法就是楼主听说的 ObjectOutputStream.reset()它通道接收方这次发送会话结束双方可以清空缓存了。“网上搜的方法,ObjectOutputStream 会保留之前的对象方便引用,还不是很明白…… 
    间隔一工作量后重置就可以了 ”
      

  19.   

    在没有 ObjectOutputStream.reset() 之前。
    如果我们 A a = new A();a.setName("1");outputStream.writeObject(a);a.setName("2");outputStream.writeObject(a);两次连续调用你会发现接收端收到的对象的 getName() 返回相同的值,就跟你没调用第二次的 writeObject 一样的效果,具体是发送端没有发第二次还是接收方发现收到的对象标志在已接收对象缓存中已经存在而丢弃不尝试构造对象的内存镜像这个就需要我们用网络抓包程序抓包再证实网络上是否有数据流量存在并且流量的大小是否对应着一个对象的估计的字节数,这个我没试过。有兴趣的试试。
      

  20.   

    楼上的你那一口气可真够长啊连说100个字一个标点符号也没有看着真是很吃力我就没这么大本事你看我这就要换气了。有些知识一到中等难度就很少能搜到相关资源了,jdk里解释得又很含糊,这用起来还真是容易碰钉子