Extreme memory allocation with VoiceServer Client Output
TotalHamman opened this issue ยท 5 comments
Mekanism-1.10.2-9.2.0.292
During Server Play, noticed high amounts of Memory Allocation and Garbage Collection running at least once a second.
Checking my client process in JVisualVM, I found that VoiceServer Client Output Thread is allocating memory at the rate of over 500 MB/s. Looking at the stacktrace below from the ThreadDump, it appears the main source is line 40 which is the following line (according to github code / lines):
short byteCount = voiceClient.input.readShort();
"VoiceServer Client Output Thread" #121 daemon prio=5 os_prio=0 tid=0x00000000417be800 nid=0x2ae90 runnable [0x0000000078fae000]
java.lang.Thread.State: RUNNABLE
at java.lang.Throwable.fillInStackTrace(Native Method)
at java.lang.Throwable.fillInStackTrace(Throwable.java:783)
- locked <0x00000007b28d9f70> (a java.io.EOFException)
at java.lang.Throwable.(Throwable.java:250)
at java.lang.Exception.(Exception.java:54)
at java.io.IOException.(IOException.java:47)
at java.io.EOFException.(EOFException.java:50)
at java.io.DataInputStream.readShort(DataInputStream.java:315)
at mekanism.client.voice.VoiceOutput.run(VoiceOutput.java:40)
Locked ownable synchronizers:
- None
Attaching screenshots of GC Eden space with B:VoiceServerEnabled set to true and then set to false. Also included screenshot of JVisualVM.
After sleeping on the issue, I think the best way to fix would be to declare the byteCount variable just before the while loop and only attempt to assign it voiceClient.input.readShort(); inside the loop. That should prevent the high memory allocation observed.
i don't think this will change much since the today's HotSpot VM is known for for being able to resolve such situations into extremely performant code (including ram usage)
Declaring the variable outside of the Do/While will reduce overall RAM usage, but it is not the only cause of excessive RAM usage.
There other cause is one that I don't think will be easily handled as it is fundamental to the logic of the loop. The Try/Catch is throwing an exception on every execution. That exception is going to take up a lot more space in memory than the variable itself. Unfortunately the only way that I can see to limit that overhead would be to limit the number of executions of the Do/While per second.
I have no good ideas on how to correct that logic, so I chose to focus only on the variable declaration itself.
For reference, the Java Class I am referring too is this one - https://github.com/aidancbrady/Mekanism/blob/master/src/main/java/mekanism/client/voice/VoiceOutput.java
@aidancbrady @TotalHamman
what about replacing https://github.com/aidancbrady/Mekanism/blob/master/src/main/java/mekanism/client/voice/VoiceOutput.java#L29-L51 with
@Override
public void run()
{
try {
sourceLine = ((SourceDataLine)AudioSystem.getLine(speaker));
sourceLine.open(voiceClient.format, 2200);
sourceLine.start();
byte[] audioData = new byte[4096]; // less allocation/gc (if done outside the loop)
int byteCount;
int length;
while(voiceClient.running)
{
try {
byteCount = voiceClient.input.readUnsignedShort(); //Why would we only read signed shorts? negative amount of waiting data doesn't make sense anyway :D
while(byteCount>0 && voiceClient.running){
length=audioData.length;
if(length>byteCount) length=byteCount;
length = voiceClient.input.read(audioData, 0, length); // That one returns the actual read amount of data (we can begin transferring even if input is waiting/incomplete)
if(length<0) throw new EOFException();
sourceLine.write(audioData, 0, length);
byteCount-=length;
}
} catch(EOFException eof){
Mekanism.logger.error("VoiceServer: Unexpected input EOF Exception occured.");
break; // voiceClient.input will continue throwing EOFs -> no need to go on checking
} catch(Exception e) {
/*Would a debug output be good here?*/
}
}
} catch(Exception e) {
Mekanism.logger.error("VoiceServer: Error while running client output thread.");
e.printStackTrace();
}
}