现在在这个新问题中明确地描述了这个问题的发展:Why does the JVM show more latency for the same block of code after a busy spin pause?
我在下面列出了一个简单的服务器和客户端的源代码,用于演示和隔离问题。基本上我是计时乒乓(客户端 - 服务器 - 客户端)消息的延迟时间。我首先每1毫秒发送一条消息。我等待发送200k消息,以便HotSpot有机会优化代码。然后我将暂停时间从1毫秒更改为30秒。令我惊讶的是,我的写入和读取操作变得相当慢。
我不认为这是JIT / HotSpot问题。我能够找到原始JNI调用写入(write0
)和读取的较慢方法。看起来你停的时间越长越慢。
我正在寻找有关如何调试,理解,解释或解决此问题的指示。
Server.java:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class Server {
private final ServerSocketChannel serverSocketChannel;
private final ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024);
private final int port;
private final int msgSize;
public Server(int port, int msgSize) throws IOException {
this.serverSocketChannel = ServerSocketChannel.open();
this.port = port;
this.msgSize = msgSize;
}
public void start() throws IOException {
serverSocketChannel.socket().bind(new InetSocketAddress(port));
final SocketChannel socketChannel = serverSocketChannel.accept(); // blocking mode...
System.out.println("Client accepted!");
socketChannel.configureBlocking(false);
socketChannel.socket().setTcpNoDelay(true);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
while(true) {
int bytesRead = socketChannel.read(readBuffer);
if (bytesRead == -1) {
System.out.println("Client disconnected!");
return;
} else if (bytesRead > 0) {
if (readBuffer.position() == msgSize) {
// have a full message there...
readBuffer.flip();
int bytesSent = socketChannel.write(readBuffer);
if (bytesSent != msgSize) throw new RuntimeException("Could not send full message out: " + bytesSent);
readBuffer.clear();
}
}
}
} catch(Exception e) {
throw new RuntimeException(e);
}
}
});
t.start();
serverSocketChannel.close();
}
public static void main(String[] args) throws Exception {
Server s = new Server(9999, 8);
s.start();
}
}
Client.java:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Client implements Runnable {
private static final int WARMUP = 200000;
private final SocketChannel socketChannel;
private final String host;
private final int port;
private final ByteBuffer outBuffer;
private final ByteBuffer inBuffer = ByteBuffer.allocateDirect(1024);
private final int msgSize;
private final StringBuilder sb = new StringBuilder(1024);
private int interval;
private int totalMessagesSent;
private long timeSent;
private int mod;
public Client(String host, int port, int msgSize) throws IOException {
this.socketChannel = SocketChannel.open();
this.host = host;
this.port = port;
this.outBuffer = ByteBuffer.allocateDirect(msgSize);
this.msgSize = msgSize;
for(int i = 0; i < msgSize; i++) outBuffer.put((byte) i);
outBuffer.flip();
this.interval = 1;
this.mod = 20000;
}
public static long busySleep(long t) {
long x = 0;
for(int i = 0; i < t * 20000; i++) {
x += System.currentTimeMillis() / System.nanoTime();
}
return x;
}
public void start() throws Exception {
this.socketChannel.configureBlocking(false);
this.socketChannel.socket().setTcpNoDelay(true);
this.socketChannel.connect(new InetSocketAddress(host, port));
while(!socketChannel.finishConnect()) {
System.out.println("Waiting to connect");
Thread.sleep(1000);
}
System.out.println("Please wait as output will appear every minute or so. After " + WARMUP + " messages you will see the problem.");
Thread t = new Thread(this);
t.start();
}
private final void printResults(long latency, long timeToWrite, long timeToRead, long zeroReads, long partialReads, long realRead) {
sb.setLength(0);
sb.append(new java.util.Date().toString());
sb.append(" Results: totalMessagesSent=").append(totalMessagesSent);
sb.append(" currInterval=").append(interval);
sb.append(" latency=").append(latency);
sb.append(" timeToWrite=").append(timeToWrite);
sb.append(" timeToRead=").append(timeToRead);
sb.append(" realRead=").append(realRead);
sb.append(" zeroReads=").append(zeroReads);
sb.append(" partialReads=").append(partialReads);
System.out.println(sb);
}
@Override
public void run() {
try {
while(true) {
busySleep(interval);
outBuffer.position(0);
timeSent = System.nanoTime();
int bytesSent = socketChannel.write(outBuffer);
long timeToWrite = System.nanoTime() - timeSent;
if (bytesSent != msgSize) throw new IOException("Can't write message: " + bytesSent);
inBuffer.clear();
long zeroReads = 0;
long partialReads = 0;
long timeToRead = System.nanoTime();
long realRead = 0;
while(inBuffer.position() != msgSize) {
realRead = System.nanoTime();
int bytesRead = socketChannel.read(inBuffer);
if (bytesRead == 0) {
zeroReads++;
} else if (bytesRead == -1) {
System.out.println("Other side disconnected!");
return;
} else if (bytesRead != msgSize) {
partialReads++;
realRead = -1;
} else {
realRead = System.nanoTime() - realRead;
}
}
long now = System.nanoTime();
timeToRead = now - timeToRead;
long latency = now - timeSent;
if (++totalMessagesSent % mod == 0 || totalMessagesSent == 1) {
printResults(latency, timeToWrite, timeToRead, zeroReads, partialReads, realRead);
}
if (totalMessagesSent == WARMUP) {
this.interval = 30000;
this.mod = 1;
}
}
} catch(Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
Client client = new Client("localhost", 9999, 8);
client.start();
}
}
我执行java -server -cp . Server
和java -server -cp . Client
。客户端的输出是:
答案 0 :(得分:1)
您遇到的一个问题是,当没有数据要读取时,JVM,CPU及其缓存正在入睡。一旦发生这种情况,机器在获取数据之前必须做的事情远远超过问题运行时的情况。
简而言之,如果您需要一致的延迟,则需要
注意:鉴于每个操作似乎需要大约2倍的时间,我会首先考虑电源管理。
答案 1 :(得分:1)
我在查看SocketChannelImpl的代码 并注意到read()中涉及两个监视器 - 读锁定和状态锁定。
我的观点是,当它们很热且无争议时,锁的表现会好得多。
以下类基于您的客户端,只进行一些锁定,类似于SocketChannelImpl中的操作。从不可观察的角度来看,我的盒子上的延迟变为~5000(win8,jdk8)
import java.util.concurrent.TimeUnit;
public class Locker implements Runnable {
private static final int WARMUP = 40000;
private final Object readLock = new Object();
private final Object writeLock = new Object();
private final Object stateLock = new Object();
private final StringBuilder sb = new StringBuilder(1024);
private long interval;
private int totalMessagesSent;
private long timeSent;
private int mod;
private long totalOps;
private long readerThread;
private long writerThread;
public Locker() {
this.interval = 1;
this.mod = 20000;
}
public static long busySleep(long t) throws InterruptedException {
long until = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(t);
while(System.nanoTime() < until);
return until;
}
public void start() throws Exception {
Thread t = new Thread(this);
t.start();
}
private final void printResults(long latency, long timeToRead) {
sb.setLength(0);
sb.append(new java.util.Date().toString());
sb.append(" Results: totalMessagesSent=").append(totalMessagesSent);
sb.append(" currInterval=").append(interval);
sb.append(" latency=").append(latency);
sb.append(" timeToRead=").append(timeToRead);
sb.append(" totalOps=").append(totalOps);
sb.append(" reader=").append(readerThread);
sb.append(" writer=").append(writerThread);
System.out.println(sb);
}
@Override
public void run() {
try {
while(true) {
busySleep(interval);
timeSent = System.nanoTime();
try {
synchronized (writeLock) {
synchronized (stateLock) {
writerThread = Thread.currentThread().getId();
}
totalOps++;
}
}
finally {
synchronized (stateLock) {
writerThread = 0;
}
}
long timeToRead = System.nanoTime();
try {
synchronized (readLock) {
synchronized (stateLock) {
readerThread = Thread.currentThread().getId();
}
totalOps++;
}
} finally {
synchronized (stateLock) {
readerThread = 0;
}
}
long now = System.nanoTime();
timeToRead = now - timeToRead;
long latency = now - timeSent;
if (++totalMessagesSent % mod == 0 || totalMessagesSent == 1) {
printResults(latency, timeToRead);
}
if (totalMessagesSent == WARMUP) {
this.interval = 5000;
this.mod = 1;
}
}
} catch(Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
Locker locker = new Locker();
locker.start();
}
}
编辑:每个OP建议的修改代码表现出相同的延迟增加:
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
public class Locker {
static final int WARMUP = 20000;
final Object readLock = new Object();
final Object writeLock = new Object();
final Object stateLock = new Object();
long interval = 1;
int totalMessagesSent;
long totalOps;
long readerThread;
long writerThread;
final long[] measures = new long[WARMUP + 20];
static long busySleep(long t) {
long until = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(t);
while(System.nanoTime() < until);
return until;
}
void printResults(long latency, long timeToRead) {
if (readerThread != 0 || writerThread != 0 || totalMessagesSent > totalOps || timeToRead < 0) throw new Error();
measures[totalMessagesSent] = latency;
}
void run() {
while(totalMessagesSent < measures.length) {
busySleep(interval);
long timeSent = System.nanoTime();
try {
synchronized (writeLock) {
synchronized (stateLock) {
writerThread = Thread.currentThread().getId();
}
totalOps++;
}
}
finally {
synchronized (stateLock) {
writerThread = 0;
}
}
long timeToRead = System.nanoTime();
try {
synchronized (readLock) {
synchronized (stateLock) {
readerThread = Thread.currentThread().getId();
}
totalOps++;
}
} finally {
synchronized (stateLock) {
readerThread = 0;
}
}
long now = System.nanoTime();
timeToRead = now - timeToRead;
long latency = now - timeSent;
printResults(latency, timeToRead);
++totalMessagesSent;
this.interval = (totalMessagesSent/WARMUP * 5000) + 1;
}
System.out.println("last measures = " + Arrays.toString(Arrays.copyOfRange(measures, WARMUP - 20, measures.length - 1)));
}
public static void main(String[] args) {
new Locker().run();
}
}