2

I've got a little network game I'm making to learn networking in Java, and I'm in need of a little insight into what my program is having issues with. My server program maxes out the heap and burns 100% of the CPU. I'm sure I've got major newbie gotchas in the code, and I'm wondering if anyone would be so kind as to point them out to me, and perhaps detail why it is such a horrible practice.

Basically, the Server class's job is to wait in socket.accept() to deal with new clients. Each client from there gets its own ConnectionThread (which deals with input) and an attached OutputStream (which handles output). I know this may be wasteful for large applications, but with the server running along with just three clients (which are set to skip rendering and only send data through the socket every ~20ms for both input and output) it cooks the CPU and the server overflows the stack.

I've got a Packet class which converts the data into a string for sending, and the receiver decodes it back into a Packet. I suspect I have some Packets laying around too long, but I don't see where. If it isn't the packets, I'm fairly certain I have SOME sort of uncontrolled exponential object growth.

Here are some snippets of relevant code. I'm happy to provide more if the problem is elsewhere.

Just for reference, here is the full code: https://github.com/taylorrobert/ProjectM2O

Server:

public Server() {
    network = new NetworkManager(this);
    network.setConnectionCounter(0);
    entityManager = new EntityManager(this);
    setListenState(true);

    try {
        serverSocket = new ServerSocket(PORT);

    } catch (IOException e) {
        System.out.println("Error in server constructor.");
        System.exit(1);
    }
}

public void listen() {

        System.out.println("Current connectionCounter: " + network.getConnectionCounter()); 
        while (shouldListen) {
            ConnectionThread conn = null;

            try {
                conn = new ConnectionThread(serverSocket, this);
            }
            catch (Exception e) {
                System.out.println("____Error constructing ConnectionThread. Could there be another instance of the server running?");
                e.printStackTrace();
                System.exit(1);
            }

            (new Thread(conn)).start();


            System.out.println("Connection count: " + network.getConnectionCounter());
        }
    }

ConnectionThread:

public ConnectionThread(ServerSocket s, Server ser) {
    resetTimer();
    setActiveState(false);
    server = ser;

    //This UUID becomes the client's controllable player ID
    //and the ID of this ConnectionThread.
    connectionID = String.valueOf(UUID.randomUUID());

    try {
        socket = s.accept();
        System.out.println("Socket ID " + connectionID + " established on: " + socket);
    } catch (IOException e) {
        System.out.println("Error in ConnectionThread. Is there a server already running on this port?");
    }
    init();
}

public void init() {
        try {
            out = new PrintWriter(socket.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        }
        catch (IOException e) {
            System.out.println("Error in intializing I/O streams.");
            System.exit(1);
        }

        //Create the output thread
        OutputStream outputHandler = new OutputStream(this);
        new Thread(outputHandler).start();


        //Get the client up to date on all relevant data
        server.getEntityManager().addPlayerEntity(getConnectionID());
        server.getNetwork().pushClientInitState(getConnectionID(), this);
        server.getNetwork().addConnection(this);
        server.getNetwork().notifyClientsAboutNewPlayer(getConnectionID());
        int s = server.getNetwork().getConnections().size();
        server.getNetwork().sendConsoleMessage("Players online: " + s, this);

    }

public void run() {
    setActiveState(true);
    System.out.println("Running ConnectionThread...");
    while (isActive()) {


        //System.out.println("Entity size: " + server.getEntityManager().getEntities().size());
            String op = readInputStream();

            if (op.equals("")) continue;
            Packet packet = Packet.populateNewPacketFromString(op);
            try {
                incomingOpQueue.put(packet);
            } catch (InterruptedException e) {
                System.out.println("Server failed to add packet to outgoing queue!");
            }
            //Take all packets off the incoming queue and execute them in order
            while (incomingOpQueue.size() > 0) {
                Packet p = incomingOpQueue.poll();
                PacketExecutor.executePacket(server, p, this);


            }
    }
}

public String readInputStream() {
    String msg = "";

    try {
        msg = in.readLine();
        msg = msg.replace("\n", "");
        msg = msg.trim();
    } catch (IOException e) {
        return "";
    }
    return msg;

}

OutputStream:

public void output() {
    while (parentCT.isActive()) {
        UnitTester.updateAllEntityLocationsToAllClients(parentCT, parentCT.server.getEntityManager().getEntities());
        while (parentCT.getOutgoingOpQueue().size() > 0) {
            String packet = (parentCT.getOutgoingOpQueue().poll().getString());
            if (packet.equals("")) continue;
            //System.out.println("Sending " + packet + " to " + parentCT.getConnectionID());
            parentCT.getOutput().println(packet);

        }

    }
}
Taylor Hill
  • 1,053
  • 1
  • 14
  • 24
  • No that's not possible, you can't memory leak in java! /fanboyism – Cruncher Nov 13 '13 at 20:11
  • A 100% CPU would indicate that there's a major spinlock going on somewhere. Possibly the output() method. I'd stick a Thread.sleep(10); in there and see if it calms the CPU a bit. – Kayaman Nov 13 '13 at 20:19
  • Putting a sleep(10) in there actually did make quite a huge difference in the CPU. It's down from 90-100% to ~50%. As far as the heap goes, it also made a huge difference, but after a few minutes the heap size starts to pile up linearly. I have no idea where the memory leak could be, but I will keep looking. – Taylor Hill Nov 13 '13 at 20:28
  • use jvisualvm to find out what's wrong. – ZhongYu Nov 13 '13 at 20:29
  • That's what I'm using at the moment, though I just started learning the art of profiling! – Taylor Hill Nov 13 '13 at 20:30
  • Maybe GC thrashing, try increasing the heap size – Steve Kuo Nov 13 '13 at 20:57
  • @Cruncher It can be done, it just required a master programer. [How to leak memory in java](http://stackoverflow.com/questions/6470651/creating-a-memory-leak-with-java) – Richard Tingle Nov 13 '13 at 22:36
  • @RichardTingle Oh I'm aware. I was satirizing the java philosophy :) – Cruncher Nov 14 '13 at 13:31

1 Answers1

2

You probably need to make shouldListen volatile. Otherwise, there's every chance that its value will be cached, and setting it to false in some other thread will make no difference. You are setting it to false right, so that the main loop just doesn't make lots and lots of threads until it maxes out the heap and burns up the CPU?

Dawood ibn Kareem
  • 77,785
  • 15
  • 98
  • 110
  • 1
    I don't see anything in his snippet that sets `shouldListen` to false. – Thorn G Nov 13 '13 at 20:14
  • Indeed. It'll block on the serverSocket.accept() anyways, so that's a limiting factor. – Kayaman Nov 13 '13 at 20:15
  • As Kayaman said, inside the ConnectionThread constructor is the ServerSocket.accept() method, which blocks the creation of new threads until it can be paired with a client. – Taylor Hill Nov 13 '13 at 20:18
  • But I just checked--good catch on the shouldListen being volatile. I missed that one. Thanks. – Taylor Hill Nov 13 '13 at 20:29