Java OutOfMemory when sending images via socket - java

I created an application that basically uses robot to get and image in the client and sends to the server every few seconds, so I can watch what's going on on another PC. The problem seems to be that it keeps saving the image in an array or something, because after a few seconds, it crashs. I just recieve the image and write it on the screen when I do. Yet, after a while, it gives me an OutOfMemory. Does anyone have a hint about what may be causing it?
Here are the code snippets that were requested:
server:
private class Conexao extends Thread {
public static final int PORTA = 12000;
public ObjectOutputStream out;
public ObjectInputStream in;
public Image image;
private boolean fim;
public Conexao(String ip) throws IOException {
try {
Socket socket = new Socket(ip, Conexao.PORTA);
this.out = new ObjectOutputStream(socket.getOutputStream());
this.in = new ObjectInputStream(socket.getInputStream());
} catch (IOException e) {
throw e;
}
}
public void encerrar() {
this.fim = true;
}
#Override
public void run() {
this.fim = false;
while (!this.fim) {
Mensagem mensagem = null;
try {
mensagem = ((Mensagem) in.readObject());
} catch (IOException | ClassNotFoundException e) {
}
if (mensagem != null) {
this.image = mensagem.getImage();
Cliente.this.painel.repaint();
}
}
}
}
client:
private static class Conexao extends Thread {
private static Image CURSOR;
static {
try {
CURSOR = ImageIO.read(new File("images\\mouse.png"));
} catch (IOException e) {
CURSOR = null;
}
}
private ObjectOutputStream out;
private ObjectInputStream in;
public Conexao() throws IOException {
try {
ServerSocket serverSocket = new ServerSocket(Servidor.PORTA, 1);
Socket socket = serverSocket.accept();
this.out = new ObjectOutputStream(socket.getOutputStream());
this.in = new ObjectInputStream(socket.getInputStream());
} catch (IOException e) {
throw e;
}
}
#Override
public void run() {
try {
Robot robot = new Robot();
for (;;)
try {
Thread.sleep(10);
Point p = MouseInfo.getPointerInfo().getLocation();
BufferedImage img = robot.createScreenCapture(new Rectangle(0, 0, Toolkit.getDefaultToolkit().getScreenSize().width, Toolkit.getDefaultToolkit().getScreenSize().height));
if (Conexao.CURSOR != null) {
img.getGraphics().drawImage(CURSOR, p.x, p.y, null);
} else {
Graphics2D g = (Graphics2D) img.getGraphics();
g.setColor(Color.WHITE);
g.fillOval(p.x - 5, p.y - 5, 10, 10);
g.setStroke(new BasicStroke(2));
g.setColor(Color.BLACK);
g.drawOval(p.x - 5, p.y - 5, 10, 10);
g.dispose();
}
this.out.writeObject(new Mensagem(img, p));
this.out.flush();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
} catch (AWTException e) {
}
}
}

The only concrete hint I can offer without seeing your code is to use a memory profiler, such as VisualVM or YourKit.
Something somewhere is keeping references to objects that it probably shouldn't.

Try calling this.out.reset(); after this.out.flush();.
This seems to be an issue with ObjectOutputStream, which (according to the Serialization FAQ and this site) keeps a cache of all objects written to it so that repeated objects can be optimized into cache references. This can also cause problems when a value in the object changes before the object is resent, but the cached object retains the old value. In both cases, calling reset() will fix the issue. Unfortunately, none of this is explained in the class documentation.

The OutOfMemory is caused by insufficient heap space.
Some actions you can try:
Make sure you have a good size of heap space available when you run the application. (-Xmx128M on the java command line where 128 is replaced by the number of megabytes you want to asign)
Release all references (by letting the variables go out of scope or by explicitly setting object variables to null) to objects you no longer need after sending a picture.
If that doesn't help try to reuse objects rather than creating new ones.

Without seeing any code, you are most likely populating a byte[] or ByteArrayOutputStream that is class or instance scoped. You should be writing the content received to a file and then accessing it as necessary. This would mean removing the reference to the content (by having it go out of scope) whenever you switch to the new image.
If you could provide more detail as to how your application works, others should be able to pinpoint the issue.

Related

Android Library thread safe

I've a small android library which handles a serial port, it has basic functionality like open, read, write and close.
I have made an applications that uses this library to write on the serial port and read the responses, within this application there is a thread that periodically opens the serial port asks for the status get the response and close the serial port.
I want to protect the serial communication in a way that if the main thread opens the communication the secondary thread that only checks the status can not open it and wait for the main thread to finish.
class SerialChannel extends Channel
{
private SerialPortUtility serialPortUtility;
private static final String SERIAL_FILE = "/dev/ttyMT2";
private static final String CONTROL_FILE = "/sys/devices/platform/file";
private static final String UNKNOWN_COMMAND = "UNKNOWN COMMAND";
private FileOutputStream fileOutputStream;
private FileInputStream fileInputStream;
#Override
public void open() throws CommunicationException
{
try
{
if (isSerialOpened() != SerialStatus.Open)
{
toggleSerial(SerialStatus.Open.getStatus());
Thread.sleep(100);
}
serialPortUtility = getSerialPortUtility();
fileInputStream = (FileInputStream) serialPortUtility.getInputStream();
fileOutputStream = (FileOutputStream) serialPortUtility.getOutputStream();
currentProcess = Optional.of(Thread.currentThread().getId());
Thread.sleep(500);
}
catch (IOException | InterruptedException e)
{
throw new CommunicationException(e.getMessage());
}
}
#Override
public void close() throws CommunicationException
{
if (serialPortUtility == null)
{
throw new CommunicationException("SerialPort is null");
}
try
{
toggleSerial(SerialStatus.Close.getStatus());
fileOutputStream.close();
fileInputStream.close();
serialPortUtility.close();
fileInputStream = null;
fileOutputStream = null;
serialPortUtility = null;
}
catch (IOException e)
{
throw new CommunicationException(e.getMessage());
}
}
#Override
public void send(byte[] buffer, int timeout, int length) throws CommunicationException
{
if (fileOutputStream == null)
{
throw new CommunicationException("Problem while sending data!");
}
try
{
fileOutputStream.write(buffer);
fileOutputStream.flush();
}
catch (IOException e)
{
throw new CommunicationException(e.getMessage());
}
}
#Override
public byte[] receive(int length, int timeout) throws CommunicationException
{
StringBuilder stringBuilder = new StringBuilder();
byte[] buffer = new byte[length];
int ret;
int totalSize = 0;
if (fileInputStream == null)
{
throw new CommunicationException("FileInputStream is null!");
}
try
{
long millisStart = Calendar.getInstance().getTimeInMillis();
boolean timeoutReached;
while (true)
{
timeoutReached = (Calendar.getInstance().getTimeInMillis() - millisStart > timeout * 1000);
if (fileInputStream.available() <= 0 && timeoutReached)
{
expectingResult = false;
throw new CommunicationException("Error");
}
else if (fileInputStream.available() > 0)
{
break;
}
}
millisStart = Calendar.getInstance().getTimeInMillis();
while (totalSize != length && (ret = fileInputStream.read(buffer)) != -1)
{
String received = new String(buffer);
stringBuilder.append(received);
if(buffer.length == 15 && received.equals(UNKNOWN_COMMAND))
{
break;
}
totalSize += ret;
}
expectingResult = false;
}
catch (IOException e)
{
throw new CommunicationException(e.getMessage());
}
return stringBuilder.toString().getBytes();
}
private SerialPortUtility getSerialPortUtility() throws IOException
{
if (serialPortUtility == null)
{
File file = new File(SERIAL_FILE);
int baudRate = 115200;
return new SerialPortUtility(file, baudRate, 0);
}
return serialPortUtility;
}
private void toggleSerial(String data) throws IOException
{
FileOutputStream fos = new FileOutputStream(new File(CONTROL_FILE));
fos.write(data.getBytes());
fos.flush();
fos.close();
}
private SerialStatus isSerialOpened() throws IOException
{
byte[] buffer = new byte[1];
FileInputStream fis = new FileInputStream(new File(CONTROL_FILE));
int result = fis.read(buffer);
fis.close();
if (result > -1 && buffer[0] == 1)
{
return SerialStatus.Open;
}
return SerialStatus.Close;
}
}
This class extends custom class Channel that implements an interface with the methods open, close, read, send and implements also AutoCloseable.
Now if I make the open method synchronized any thread that enters here will lock, but will lock until it exits the open method, and when the thread moves to the another method let's say read and stay there until it gets a response, the checker thread will come and enters the open method. Using AutoCloseable the close method will execute and close the serial port communication. If I synchronize an object, there still is a window when the object is not synchronized.
How can I tell the checker thread that the communication is already opened and make him wait until the main thread finish.
Checker looks like this, it is within an timer:
try(Channel ch = CommunicationFactory.getInstance().selectChannel(CommunicationType.SERIAL))
{
ch.open();
//do stuff
}
catch (CommunicationException ex)
{
ex.printStackTrace();
}
The "main" thread looks the same only that it is in an AysncTask.
If additional informations are required please let me know!
Thank you in advance for your effort and time!
How can I tell the checker thread that the communication is already opened and make him wait until the main thread finish.
I don't fully understand your code but the critical thing with threads and locking is to make sure that all threads are calling code that is synchronized on the same object instance.
If I synchronize an object, there still is a window when the object is not synchronized.
Not if you use the same instance of the object. Making each of the public methods in SerialChannel synchronized will make sure that only 1 thread can be using the object at once.
I suspect that your real problem is not about protecting the SerialChannel object but more about race-conditions between the threads. They need to make multiple calls to the methods and they can block each other or interleave in an improper manner.
You can get around this with a couple of changes. You can make the send(...) and receive(...) methods auto-opening. Threads would just call send() or receive() which in turn would internally call open() if the fileInputStream or fileOutputStream was null. The thread would be inside of a synchronized so this would not be interrupted by another thread.
Another completely different model to consider would be to have one thread reading from the serial port and another writing to it that are dedicated to that task -- they would be built into the SerialChannel object. They would share data with the external threads using a read BlockingQueue and a write BlockingQueue. Then the serial port is opened early in your application which starts the IO threads and the external threads never worry about the IO. They just put() and take() from the queues. I typically do this (for example) when reading and writing to the console.
Hope something here helps.

How reliable socket stream's flush() is?

Consider this (simplified) piece of code:
public class Test {
// assigned elsewhere
InetSocketAddress socketAddress;
String socketHost;
int socketPort;
Socket socket;
int COMMAND = 10;
int CONNECTION_TIMEOUT = 10 * 1000;
int SOCKET_TIMEOUT = 30 * 1000;
DataOutputStream dos;
DataInputStream dis;
protected void connect() throws IOException, InterruptedException {
socket.connect(socketAddress != null ? socketAddress : new InetSocketAddress(socketHost, socketPort), CONNECTION_TIMEOUT);
socket.setSoTimeout(SOCKET_TIMEOUT);
socket.setTcpNoDelay(true);
}
void initializeDataStreams() throws IOException {
dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream(), socket.getSendBufferSize()));
dis = new DataInputStream( new BufferedInputStream( socket.getInputStream(), socket.getReceiveBufferSize()));
}
void run() {
try {
connect();
initializeDataStreams();
sendCommand(COMMAND, true);
sendIdAndUsername(true);
sendSyncPreference(true);
sendBlockedIds(true);
sendHeaders();
// reading from 'dis' here
// ...
} catch (InterruptedException | IOException e){
/* ... */
}
}
void sendCommand(int command, boolean buffered) throws IOException {
dos.write(command);
if (!buffered) {
dos.flush();
}
}
void sendIdAndUsername(boolean buffered) throws IOException {
sendId(true); // always buffered
String username = "user name";
dos.writeBoolean(username != null);
if (username != null) {
dos.writeUTF(username);
}
if (!buffered) {
dos.flush();
}
}
void sendId(boolean buffered) throws IOException {
dos.writeUTF("user id");
if (!buffered) {
dos.flush();
}
}
void sendSyncPreference(boolean buffered) throws IOException {
boolean fullSync = true;
dos.writeBoolean(fullSync);
if (!buffered) {
dos.flush();
}
}
void sendBlockedIds(boolean buffered) throws IOException {
Set<String> blockedCrocoIds = new HashSet<>();
ObjectOutputStream oos = new ObjectOutputStream(dos);
oos.writeObject(blockedCrocoIds);
if (!buffered) {
oos.flush();
}
}
private void sendHeaders() throws IOException {
dos.writeUTF("some string");
dos.writeInt(123);
// some other writes...
// this should flush everything, right?
dos.flush();
}
}
I left it intentionally with all the methods, just in case I've made some terribly obvious mistake there. When I execute Test.run(), sometimes (really hard to predict when exactly) it seems like the flush() in sendHeaders() doesn't work at all.
Server side doesn't receive anything on its ServerSocket.accept() for next 22 seconds (don't ask me where this number comes from, part of the mystery).
The idea was that I wont call flush() on every transmission but call it only once, to save the bandwidth.
So what's wrong with this code? How to ensure writes to my stream are reliable / immediate so the server can read it ASAP?
I also accept answer "there's nothing wrong", in that case it must be something which is being done in parallel and affecting the network stack on Android.
EDIT: Server code is really nothing special:
ListeningThread listeningThread = new ListeningThread();
listeningThread.start();
listeningThread.join();
and then:
public class ListeningThread extends Thread {
private ServerSocket serverSocket;
public ListeningThread() {
try {
// unbound server socket
serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true);
serverSocket.bind(new InetSocketAddress(NetworkUtil.APP_SERVER_PORT));
} catch (IOException e) {
log(e);
}
}
#Override
public void run() {
log("run");
while (serverSocket.isBound() && !isInterrupted()) {
try {
Socket socket = serverSocket.accept();
new CommandThread(socket).start();
} catch (IOException e) {
log(e);
}
}
try {
serverSocket.close();
} catch (IOException e) {
log(e);
}
}
}
and finally:
public class CommandThread extends Thread {
private final Socket socket;
public CommandThread(Socket socket) {
log("CommandThread");
this.socket = socket;
}
#Override
public void run() {
log("run");
try {
socket.setSoTimeout(NetworkUtil.SOCKET_TIMEOUT);
socket.setTcpNoDelay(true);
InputStream is = socket.getInputStream();
int cmd = is.read(); // <========= so actually this is failing
switch (cmd) {
// handling of the command
case COMMAND:
new DownloadMessagesThread(socket).start();
break;
}
} catch (IOException | SQLException e) {
log(e);
}
}
}
As mentioned in the comments, I'd open to agree on anything wrong with the object streams & co but the trouble is that I'm unable to reach (again, it's just sometimes, it's very random...) CommandThread's run(). So unless I'm missing something else, there's no way Object Streams could cause this kind of failure.
EDIT 2: Correction: it's not accept() I cannot reach, it's the first read operation:
03-07 11:22:42.965 00010 CommandThread: CommandThread
03-07 11:22:42.966 00108 CommandThread: run
[... nothing happening ...]
03-07 11:23:04.549 00111 DownloadMessagesThread: run
Could this be caused by mixing the object stream and data stream after all?
You should verify that the ObjectOutputStream creation in sendBlockedIds is not the culprit.
I've already had some protocol "deadlocks" while mixing DataStreams and ObjectStreams, since the creation of the Writer/Reader pair of ObjectStreams implies a kind of handshake that may fail while mixing those streams.
EDIT: While reading again your question, I realized that I had not answered it. So yes, it is reliable. And +1 for EJP answer.
To answer the question in your title, it is 100% reliable, as it doesn't do anything. Only the flush() methods of streams that are buffered actually do anything, and that only includes ObjectOutputStream and BufferedOutputStream, and PrintStream depending on how you construct it. Not DataOutputStream, and not the output stream of the socket itself.
So in this case the only flush method that does anything is the buffered output stream's, and you can certainly rely on that, as it is just code, and has been working for twenty years.
If this is affecting the speed of accept(), there must be something odd about your accept loop that you haven't shown us: typically, doing I/O in the accept loop instead of in the started thread.
And you should certainly not create an ObjectOutputStream in the middle of the connection. Create it at the start and use it for everything, and an ObjectInputStream at the other end.
NB setting the buffer sizes to the socket buffer sizes respectively is really fairly pointless. The defaults are adequate.

ObjectInputStream consumes too much memory

I have a Socket that sends a list of Objects every few seconds to a client through ObjectOutputStream. On the server side, after every writeObject(myList) i execute flush then reset. Using VisualVM to check for memory usage, on the server there's no memory leaks, but on the client it seems that the previously read Lists are kept in memory. I tried to execute reset on the ObjectInputStream on the client side but looks like ObjectInputStream does not support this method (it throws a java.io.IOException: mark/reset not supported).
This is my server socket:
public class ConsultaBombas {
public static void inicializarServidorSocket() {
try {
ServerSocket serverSocket = new ServerSocket(5963);
Thread thread = new Thread(() -> {
while (!serverSocket.isClosed()) {
try {
final Socket socket = serverSocket.accept();
new ThreadComunicacao(socket).start();
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.setName("Consulta bombas (Inicializador)");
thread.start();
} catch (Exception e) {
e.printStackTrace();
}
}
static class ThreadComunicacao extends Thread {
private Socket socket;
public ThreadComunicacao(Socket socket) {
this.socket = socket;
setName("Consulta bombas (Comunicação) com início: " + new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new Date()));
}
#Override
public void run() {
try {
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
while (!socket.isClosed()) {
List<Bomba> bombas = new DaoBomba().findAll();
out.writeObject(bombas);
out.flush();
out.reset();
Thread.sleep(1000);
}
} catch (SocketException e) {
if (e.getLocalizedMessage() != null && e.getLocalizedMessage().equalsIgnoreCase("Connection reset by peer: socket write error")) {
System.out.println("Cliente desconectou...");
} else {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
An this is the client (started with start() method):
public class ConsultaBombasClient {
private Socket socket;
private Thread threadConsulta;
public ConsultaBombasClient(BombasListener bombasListener, String maquinaDestino) {
threadConsulta = new Thread(() -> {
try {
Thread.currentThread().setName("Consulta Bombas");
System.out.println("Endereço bagual: "+maquinaDestino);
socket = new Socket(maquinaDestino, 5963);
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
Object leitura;
while ((leitura = in.readObject()) != null) {
List<Bomba> bombas = (List<Bomba>) leitura;
bombasListener.run(bombas);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
});
threadConsulta.setDaemon(true);
}
public void start() {
threadConsulta.start();
}
public interface BombasListener {
void run(List<Bomba> bombas);
}
}
What am i doing wrong?
garbage collection is not immediate, do you have any real memory troubles? Have you tried running the client with low -Xmx value, did you receive the OutOfMemoryError?
– user3707125
You're right, after some time when the memory gets close to the maximum heap size, it clears the objects from memory. I wasn't seeing this because i have a lot of RAM in my pc but with Xmx50m i could see this working as you said. – Mateus Viccari
Clearly bombasListener.run(), whatever it may be, is not releasing the supplied list.
NB ObjectInputStream.readObject() does not return null at end of stream. It is therefore incorrect to use this test as a termination condition for a read loop.

Java runtime.exec user input race condition

I want my app to be able to use a global su instance. I have code that does that, but I have encountered a race condition, I believe.
I am storing some variables for su like so:
public static List<Object> rootObjects = Collections.synchronizedList(new ArrayList<>());
protected void onCreate(Bundle savedInstanceState) {
...
if(PreferenceManager.getDefaultSharedPreferences(
getApplicationContext()).getBoolean("use_su", false) && rootObjects.isEmpty())
{
try {
Process process = Runtime.getRuntime().exec("su");
rootObjects.add(process);
InputStream inputStream = new DataInputStream(process.getInputStream());
rootObjects.add(inputStream);
OutputStream outputStream = new DataOutputStream(process.getOutputStream());
rootObjects.add(outputStream);
} catch (IOException e) {
Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
}
finally {
synchronized (rootObjects) {
rootObjects.notifyAll();
}
}
}
}
and using them like so:
byte[] getPrivateKeyAsSuperUser() {
byte[] data = null;
DataInputStream inputStream = null;
DataOutputStream outputStream = null;
if(MainActivity.rootObjects.size() != 3)
synchronized (MainActivity.rootObjects)
{
try {
MainActivity.rootObjects.wait();
} catch (InterruptedException e) {
Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
}
}
for(Object rootObj : MainActivity.rootObjects)
{
if(rootObj instanceof DataInputStream)
inputStream = (DataInputStream) rootObj;
else if(rootObj instanceof DataOutputStream)
outputStream = (DataOutputStream) rootObj;
}
try {
outputStream.writeBytes(String.format("cat \"%s\"\n", sshPrivateKey.getAbsolutePath()));
outputStream.flush();
data = readStream(inputStream);
} catch (IOException e) {
Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
}
return data;
}
private byte[] readStream(InputStream stream) {
byte[] data = null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte buff[] = new byte[1024];
int count = 0;
while (stream.available() != 0 && (count = stream.read(buff)) != -1) {
bos.write(buff, 0, count);
}
data = bos.toByteArray();
//System.out.println(new String(data));
} catch (IOException e) {
Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
}
return data;
}
But it does not wait like I expect, and I instantly receive a Toast that the returned private key is not valid with my sanity check (It's probably null).
The code works if I let Process finish initializing, but I'd like the program to do that for me.
I've tried some other synchronization techniques such as locks, but apparently as soon as you know if an object has a lock your info is stale.
What is the best thread safe approach to have the caller of getPrivateKeyAsSuperUser() wait if Process is not initialized properly?
EDIT:
I would like to add that through some debugging, I have found that I do not want be waiting for Process to initialize (because what I have DOES that), but rather, that the shell spawned by su is valid to accept further commands. I suppose I could have a thread pipe something like echo DONE and loop until I get DONE back, but that seems like that would waste CPU horsepower. If someone could lend some knowledge on the subject, I would be extremely grateful.
You're attempting the singleton pattern here. I'm not sure why you want to store these objects in a list. The most sensible way to store them is in an object that you guarantee to create one instance of. There are a few ways you could do this. I think in your case the following would work
public class SuProcessHolder {
// store the state of the process here - this would be your Process and streams as above
// these should be non-static members of the class
// this would be the singleton instance you'll use - it will be constructed once
// on first use
private static SuProcessHolder singletonInstance = new SuProcessHolder();
public SuProcessHolder() {
// put your construction code in here to create an SU process
}
// this will access your SU process
public static SuProcessHolder getInstance() { return singletonInstance; }
}
Then, wherever you need your SU process, just call
SuProcessHolder.getInstance()
and it will be there like a Michael Jackson song.
I have solved it. I did end up having to echo and check for done, but I have done it without a loop, or sleeping in my thread, so it will fire as soon as it can, without hogging the CPU. The concurrent class I was looking for was CountDownLatch as well.
The assignment look like this:
process = Runtime.getRuntime().exec("su");
outputStream = new DataOutputStream(process.getOutputStream());
outputStream.writeBytes("echo DONE\n");
outputStream.flush();
inputStream = new DataInputStream(process.getInputStream());
byte[] buff = new byte[4];
inputStream.read(buff);
if(new String(buff).equals("DONE"));
MainActivity.rootLatch.countDown();
and getPrivateKeyAsSuperUser() became:
byte[] getPrivateKeyAsSuperUser() {
byte[] data = null;
try {
MainActivity.rootLatch.await();
} catch (InterruptedException e) {
Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
}
Su su = Su.getStaticInstance();
try {
su.outputStream.writeBytes(String.format("cat \"%s\"\n", sshPrivateKey.getAbsolutePath()));
su.outputStream.flush();
data = readStream(su.inputStream);
} catch (IOException e) {
Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
}
return data;
}
Although, this feels slightly sloppy, I may end up posting this on Code Review.

what is the fastest way to write a byte array to socket outputstream in java

As the title, and assume the size of byte array is no larger than 16 Kbytes.
Currently I am implementing a middleware for MySQL (like MySQL Proxy), which requires high throughput. but the overhead caused by reading data from socket and writing data to socket. For now, I use
in = new DataInputStream(new BufferedInputStream(socket.getInputStream()))
and
out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()))
When read data and write, I use
in.read(byte[] b) and out.write(byte[] b, int offset, int len) with out.flush()
Can anyone tell me a better way to do this?
If you're writing byte arrays it doesn't make much difference. The network is the limiting factor, not the API. I think you're already doing it near-optimally. The most significant factor is the size of your socket send buffer in the kernel, and the socket receive buffer at the receiver.
You could investigate NIO and direct buffers, but I doubt you'll see a significant difference. Direct buffers are really for the case where you're just copying between channels, and the rest of NIO is really about scalability rather than performance over an individual channel.
Since you are just forwarding bytes, you could save a little time by not using DataInputStream, and instead just using BufferedInputStream.read() and BufferedOutputStream.write().
As EJP mentions, the network is the limiting factor. But that did not stop me trying to make the fastest implementation I could imagine without using NIO. The thing is, you can read from a socket while you write to another/the same socket. One thread cannot do this (either reads or writes) so multiple threads are needed. But without NIO, that requires a lot of threads (mostly sitting idle waiting on I/O though). NIO is a bit more complicated but is very good at using very few threads when there are a lot of connections with low volume (see the summary on this page of the article that Baldy mentions).
Anyway, below a non-NIO test class that you can update and use to see for yourself what is (not) the limiting factor.
public class SocketForwarder {
public static void main(String[] args) {
try {
new SocketForwarder().forward();
} catch (Exception e) {
e.printStackTrace();
}
}
public static final int portNumber = 54321;
public static final int maxSend = 1024 * 1024 * 100; // 100 MB
public static final int bufSize = 16 * 1024;
public static final int maxBufInMem = 128;
private static final SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss.SSS");
private final ExecutorService tp = Executors.newCachedThreadPool();
private final ArrayBlockingQueue<byte[]> bq = new ArrayBlockingQueue<byte[]>(maxBufInMem);
private final CountDownLatch allReceived = new CountDownLatch(1);
private Socket from, to, sender, receiver;
private int bytesSend, bytesReceived;
public void forward() throws Exception {
tp.execute(new Runnable() {
public void run() {
ServerSocket ss = null;
try {
ss = new ServerSocket(portNumber);
from = ss.accept();
to = ss.accept();
} catch (Exception e) {
e.printStackTrace();
} finally {
try { ss.close(); } catch (Exception ignored) {}
}
}
});
sender = new Socket(InetAddress.getLocalHost(), portNumber);
receiver = new Socket(InetAddress.getLocalHost(), portNumber);
// Setup proxy reader.
tp.execute(new Runnable() {
public void run() {
byte[] buf = new byte[bufSize];
try {
InputStream in = from.getInputStream();
int l = 0;
while ((l = in.read(buf)) > 0) {
byte[] bufq = new byte[l];
System.arraycopy(buf, 0, bufq, 0, l);
bq.put(bufq);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
// Setup proxy writer.
tp.execute(new Runnable() {
public void run() {
try {
OutputStream out = to.getOutputStream();
while (true) {
byte[] bufq = bq.take();
out.write(bufq);
out.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
// Start receiver.
tp.execute(new Runnable() {
public void run() {
byte[] buf = new byte[bufSize];
try {
InputStream in = receiver.getInputStream();
int l = 0;
while (bytesReceived < maxSend && (l = in.read(buf)) > 0) {
bytesReceived += l;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(df.format(new Date()) + " bytes received: " + bytesReceived);
allReceived.countDown();
}
});
// Start sender.
tp.execute(new Runnable() {
public void run() {
Random random = new Random();
try {
OutputStream out = sender.getOutputStream();
System.out.println(df.format(new Date()) + " start sending.");
while (bytesSend < maxSend) {
byte[] buf = new byte[random.nextInt(bufSize)];
out.write(buf);
out.flush();
bytesSend += buf.length;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Bytes send: " + bytesSend);
}
});
try {
allReceived.await();
} finally {
close(sender);
close(from);
close(to);
close(receiver);
tp.shutdownNow();
}
}
private static void close(Socket s) {
try { s.close(); } catch (Exception ignored) {}
}
}
It took my computer 2 seconds to transfer 100MB locally, expect a lot less when a network is involved.
For the best throughput you're going to want to use NIO and ByteBuffers. NIO keeps most of the work reading and writing to the sockets in native code and so can be much faster.
It is more complex to write good NIO code but depending on what kind of performance you're looking for, it can be worth the effort.
There are some good NIO examples out there along with some good introductions and comparisons. One resource I've used is http://tutorials.jenkov.com/java-nio/index.html.

Categories

Resources