java stackoverflowerror thrown in infinite loop

2019-03-03 16:36发布

问题:

I have the following function that starts a jsvc daemon for receiving UDP messages:

 @Override
public void start() throws Exception {
    byte[] buf = new byte[1000];

    DatagramPacket dgp = new DatagramPacket(buf, buf.length);
    DatagramSocket sk;

    sk = new DatagramSocket(1000);
    sk.setSoTimeout(0);

    byte[] rcvMsg = null;


    run(sk, dgp, rcvMsg);


}

With a timeout of 0, the socket blocks until a another message comes in. This is what triggers the continuous run through the following while loop:

 MessageConstructor tmc =null;
Message message = null;

public void run(DatagramSocket sk, DatagramPacket dgp, byte[] rcvMsg){
    while(true){
        try {
            sk.receive(dgp);
        } catch (IOException e) {
            e.printStackTrace();
        }
        rcvMsg = dgp.getData();

         tmc = new MessageConstructor();
         message = tmc.constructMessageFromBinary(rcvMsg);

        tmc =null;
        message = null;
     }


}

The only new objects created are the MessageConstructor below:

And inside of the constructTagMessageFromBinary function a Message that is populated from a ByteArrayInputStream which converts the received UDP message to an int.

 public Message constructTagMessageFromBinary(byte[] rcvMsg) {

Message message = new Message();
ByteArrayInputStream bais = new ByteArrayInputStream(rcvMsg);
DataInput input = new DataInputStream(bais);

    try {

        int MsgType = 0;
        MsgType = input.readShort();

        message.setType(MsgType);

        return message;

    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    return null;
}

Lastly, the message is a pojo.

public class Message {

private int type;
 //getters and setters omitted

}

I have narrowed the memory leak down to the lines:

 tmc = new MessageConstructor();
 message = tmc.constructMessageFromBinary(rcvMsg);

If I comment them out, the memory never grows and stays consistent for as long as the daemon runs.

What am I doing wrong within the MessageConstructor class to receive the following stackoverflowerror:

Service exit with a return value of 143
java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:616)
        at org.apache.commons.daemon.support.DaemonLoader.start(DaemonLoader.java:243)
Caused by: java.lang.NullPointerException
        at MainDaemon.start(MainDaemon.java:116)
        ... 5 more
Cannot start daemon
Service exit with a return value of 5
java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:616)
        at org.apache.commons.daemon.support.DaemonLoader.start(DaemonLoader.java:243)
Caused by: java.lang.NullPointerException
        at MainDaemon.start(MainDaemon.java:117)
        ... 5 more
Cannot start daemon
Service exit with a return value of 5
Service exit with a return value of 143
Service exit with a return value of 143
Service exit with a return value of 143
Service exit with a return value of 143
Service exit with a return value of 143
Service exit with a return value of 143
Service exit with a return value of 143
Service exit with a return value of 143
java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:616)
        at org.apache.commons.daemon.support.DaemonLoader.start(DaemonLoader.java:243)
Caused by: java.lang.StackOverflowError

回答1:

    public void run() {             
        while(!stopped){

            byte[] rcvMsg = incomingBinaryMessage;

            MessageCreator tmc = new MessageCreator();
            Message message = null;
            try {
                message = tmc.createMessage(rcvMsg);
            System.out.println(message);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

This code does not appear to do any I/O. incomingBinaryMessage is not a method invocation, it is an object reference to an existing byte[].

The loop runs repeatedly creating the same message over and over.

Normally GC should keep up with you since you're discarding the messages and MessageCreator instance on every loop. However, the one piece of code you haven't shown, the constructor for Message could be saving a reference to the messages (i.e. adding them to a map?) and preventing them from being GC'ed.



回答2:

I'm not 100% sure on this, so I'm going to offer more than one suggestion in the hope that some combination of them fixes your problem. I'll also be "typing out loud" so forgive me if you are an advanced user and some of this is really obvious.

You have an essentially infinite loop. This loop creates a byte array object that has data assigned to it. You create a MessageCreator reference and assign an object to it. You create a null Message reference, enter a try block, and then assign an object (value) to that reference. That assignment, or specifically the method creating the assignment, is the problem, so let's look at that.

Your createMessage method takes in a byte array which is a copy of the value of the original byte array (rcvMsg). It is its own reference, but points to the same object on the heap. Inside the actual method, you create a Message reference and object. The BAIS reference is created and is pointed to the corresponding BAIS object, which takes in the byte array reference. This means the BAIS object is pointing to the ORIGINAL byte array values. Then you have the DIS, which essentially locally encapsulates the BAIS.

The try block creates an int reference to a value of 0, which you then immediately reset to be the value of input.readShort(). This reads in the value from the ORIGINAL byte array. You set the message type to be that value, and return the message.

When you exit the method, the reference to the message object you created is destroyed. Fortunately, you transfer that reference via the return method. So that object is safe. Unfortunately, your input stream is left open! The jvm might leave this alone since it is still open, I'm not sure. If it does, the more important information information is that it still has a reference to the byte array values, thereby keeping them alive. The next time your loop executes, it discards the old rcvMsg reference, but NOT THE OBJECT IT POINTS TO because that object still has a reference. With each iteration, you have more and more byte arrays sitting on the heap, until you run out of memory.

When you comment out the method call, you never get to open (and leave open) the data stream, and so the underlying object (the byte array) never has a persistant reference, and so is destroyed by the garbage collector.

TLDR: Close your stream. Add input.close(); before return message; in your createMessage method (may require an updated try/catch).

Again, not sure about this, but to my warped sense of logic, it makes sense.



回答3:

the problem was that I open a database connection in the loop without closing it.