我们正在创建一个以网络流量为中心的重负载应用程序,并在 Java 8 下运行这些服务器很多很多年。以网络流量为中心意味着服务器通常必须处理高达 700 MBit/s 的速度.
现在我们想切换到 Java 21。
我可以确认 Java 13 的性能表现与 Java 8 类似,而 Java 21 的表现与 Java 14 类似。因此,从 Java 13 到 Java 14 显然发生了变化。我使用 Azul Zulu 进行了测试,但也尝试了另一种实现来确保它的性能不是祖鲁语的问题。
在评估时我们发现,Java 21 的性能比 Java 8 更差,这让我们非常惊讶。
我创建了一个示例,您可以在其中看到效果:
主课
package senderreceiverbenchmark;
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class SenderReceiverBenchmark
{
public static void main(String[] args) throws IOException
{
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
Statistics statistics = null;
switch (args.length)
{
case 1: //receiver mode
{
System.out.println( "Receiver waiting at port " + Integer.valueOf(args[0]));
statistics = new Statistics("Received");
executorService.scheduleAtFixedRate(statistics, 10, 10, TimeUnit.SECONDS);
ServerSocket serverSocket = new ServerSocket(Integer.parseInt(args[0]));
ExecutorService executorServiceReceiver = Executors.newCachedThreadPool();
Socket socket;
while((socket = serverSocket.accept()) != null)
{
executorServiceReceiver.submit(new Receiver(socket.getInputStream(), statistics));
}
break;
}
case 4: //sender mode
{
System.out.println( "Sending to " + args[0] + ":" + Integer.valueOf(args[1]) + " with [" + Integer.valueOf(args[2]) + "] connections and framesize [" + Integer.valueOf(args[3]) + " KB]");
statistics = new Statistics("Send");
executorService.scheduleAtFixedRate(statistics, 10, 10, TimeUnit.SECONDS);
ExecutorService executorServiceSender = Executors.newFixedThreadPool(Integer.parseInt(args[2]));
long SLEEP_TIME_BETWEEN_SENDING = 50;
for (int i = 0; i < Integer.parseInt(args[2]); i++) //creating independant sender ...
{
executorServiceSender.submit(new Sender(args[0], Integer.parseInt(args[1]), Integer.parseInt(args[3]), SLEEP_TIME_BETWEEN_SENDING, statistics));
}
break;
}
default:
System.out.println( "For Receiver use: LoopbackBenchmark <ServerSocket>" );
System.out.println( "For Sender use: LoopbackBenchmark <host> <port> <NumberOfConnections> <Framesize KB>" );
System.exit(-1);
break;
}
}
}
发件人:
package senderreceiverbenchmark;
import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.Callable;
public class Sender implements Callable<Object>
{
private final OutputStream outputStream;
private final Statistics statistics;
private final byte[] preallocatedRandomData = new byte[65535];
private final long sleepTime;
public Sender(String host, int port, int framesizeKB, long sleepTimeBetweenSend, Statistics statistics) throws SocketException, IOException
{
this.statistics = statistics;
Socket socket = new Socket( host, port );
outputStream = socket.getOutputStream();
this.sleepTime = sleepTimeBetweenSend;
}
@Override
public Object call() throws Exception
{
statistics.handledConections.addAndGet(1);
while (true)
{
this.outputStream.write(preallocatedRandomData);
statistics.overallData.addAndGet(preallocatedRandomData.length);
Thread.sleep(sleepTime);
}
}
}
接收者:
package senderreceiverbenchmark;
import java.io.*;
import java.util.concurrent.Callable;
public class Receiver implements Callable<Object>
{
private final InputStream inputStream;
private final Statistics statistics;
private final byte[] buffer = new byte[65535];
public Receiver(InputStream inputStream, Statistics statistics)
{
this.inputStream = inputStream;
this.statistics = statistics;
}
@Override
public Object call() throws Exception
{
statistics.handledConections.addAndGet(1);
while (true)
{
int readBytes = this.inputStream.read(buffer);
if( readBytes > 0 )
{
statistics.overallData.addAndGet(readBytes);
}
}
}
}
一些统计数据:
package senderreceiverbenchmark;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class Statistics implements Runnable
{
public final AtomicLong overallData = new AtomicLong(0L);
public final AtomicLong handledConections = new AtomicLong(0L);
private final String mode;
private long previousRun = System.currentTimeMillis();
public Statistics(String tag)
{
this.mode = tag;
}
@Override
public void run()
{
long dataSentPerSecond = overallData.get() / TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - previousRun));
System.out.println(mode + ", Connections: " + handledConections.get() + ", Sent overall: " + dataSentPerSecond / (1024*1024) + " MB/s" );
overallData.set(0);
previousRun = System.currentTimeMillis();
}
}
请原谅我,该示例没有(良好的)错误处理功能,但用于演示目的应该没问题。
现在首先启动接收器:
Benchmark.bat 4711
然后启动发送器:
Benchmark.bat 127.0.0.1 4711 300 128
现在启动 300 个发送方线程,每 50 毫秒发送一个 128KB 数据包到接收方。
当您第一次使用 Java 8 作为运行时,然后使用 Java 21 作为运行时,您将看到类似这样的内容:
前半部分显示在 Java 8 上运行的示例应用程序,后半部分显示在 Java 21 上运行的示例应用程序。
与 Java 8 相比,较新的 Java 21 需要多 10%-15% 的 CPU 能力。
有人可以解释一下这是从哪里来的以及我能做些什么吗?
我已尝试使用 InteliJ Profiler 多次运行您的应用程序,但无法一致地重现任何 CPU 性能问题以形成可靠的案例。
但是在您的示例中您使用
以下变化可以解释使用 JDK13 及更高版本与 JDK8 相比在特定场景下的性能差异。
Socket
和ServerSocket
已根据JEP 353重新实现,为项目织机的虚拟线程奠定基础。如果您检查关闭 JEP 353,您将发现以下内容:
除了行为差异之外,新产品的性能 运行某些工作负载时,实施可能与旧的有所不同。 在旧的实现中,多个线程调用accept方法 ServerSocket 将在内核中排队。在新的实施中, 一个线程将阻塞在接受系统调用中,其他线程将排队 等待获取 java.util.concurrent 锁。 性能 其他场景中的特征也可能有所不同。