Java性能 -- 性能调优策略
性能测试测试方法 微基准性能测试 可以精准定位到某个模块或者某个方法的性能问题,例如对比一个方法使用同步实现和非同步实现的性能差异 宏基准性能测试 宏基准性能测试是一个综合测试,需要考虑到测试环境、测试场景和测试目标 测试环境:模拟线上的真实环境 测试场景:在测试某个接口时,是否有其他业务的接口也在平行运行,进而造成干扰 测试目标 可以通过吞吐量和响应时间来衡量系统是否达标,如果不达标,就需要进行优化 如果达标,就继续加大测试的并发数,探底接口的TPS 除了关注接口的吞吐量和响应时间外,还需要关注CPU、内存和IO的使用率情况 干扰因素热身问题 在Java编程语言和环境中,.java文件编译成.class文件后,需要通过解析器将字节码转换成本地机器码才能运行 为了节约内存和执行效率,代码在最初被执行时,解析器会率先解析执行这段代码 随着代码被执行的次数增加,当JVM发现某个方法或代码块运行得很频繁时,就会把这些代码认定为热点代码 为了提高热点代码的执行效率,在运行时,JVM将通过即时编译器(JIT)把这些代码编译成与本地平台相关的机器码 并进行各层次的优化,然后存储在内存中,之后每次运...
Java并发 -- Actor模型
Actor模型 Actor模型在本质上是一种计算模型,基本的计算单元称为Actor,在Actor模型中,所有的计算都在Actor中执行 在面向对象编程里,一切都是对象,在Actor模型里,一切都是Actor,并且Actor之间是完全隔离的,不会共享任何变量 Java本身并不支持Actor模型,如果需要在Java里使用Actor模型,需要借助第三方类库,比较完备的是Akka Hello Actor12345678910111213141516public class HelloActor extends UntypedAbstractActor { // 该Actor在收到消息message后,会打印Hello message @Override public void onReceive(Object message) throws Throwable { System.out.printf("Hello %s%n", message); } public static void main(String[...
Java性能 -- 性能调优标准
性能瓶颈 CPU 如果应用需要大量计算,会长时间占用CPU资源,导致其它应用因无法争夺到CPU而响应缓慢 场景:代码递归导致的无限循环,JVM频繁的Full GC、多线程编程造成的大量上下文切换 内存 Java程序一般通过JVM对内存进行分配管理,主要使用JVM中的堆内存来存储Java创建的对象 但内存空间有限,当内存空间被占满,对象无法回收,会导致内存溢出,内存泄露等问题 磁盘IO 网络:带宽 异常:Java应用中,抛出异常需要构建异常栈,对异常进行捕获和处理,这个过程非常消耗系统性能 数据库:数据库的操作往往涉及到磁盘IO的读写,大量的数据库读写操作,会导致磁盘IO的性能瓶颈 锁竞争 在并发编程中,经常需要使用到多线程,并发读写同一个共享资源,为了保证数据原子性,会用到锁 锁的使用会带来上下文切换,从而给系统带来性能开销 性能指标响应时间 一个接口的响应时间一般在毫秒级 数据库响应时间:数据库操作所消耗的时间,往往是整个请求链中最耗时的 服务端响应时间:包括Nginx分发请求所消耗的时间以及服务端程序执行所消耗的时间 网络响应时间:在网络传输时,网络硬件对需要传输的请求进行解析...
Java并发 -- Disruptor
有界队列 JUC中的有界队列ArrayBlockingQueue和LinkedBlockingQueue,都是基于ReentrantLock 在高并发场景下,锁的效率并不高,Disruptor是一款性能更高的有界内存队列 Disruptor高性能的原因 内存分配更合理,使用RingBuffer,数组元素在初始化时一次性全部创建 提升缓存命中率,对象循环利用,避免频繁GC 能够避免伪共享,提升缓存利用率 采用无锁算法,避免频繁加锁、解锁的性能消耗 支持批量消费,消费者可以以无锁的方式消费多个消息 简单使用123456789101112131415161718192021222324252627public class DisruptorExample { public static void main(String[] args) throws InterruptedException { // RingBuffer大小,必须是2的N次方 int bufferSize = 1024; // 构建Disruptor ...
Java并发 -- Netty线程模型
BIO BIO即阻塞式IO,使用BIO模型,一般会为每个Socket分配一个独立的线程 为了避免频繁创建和销毁线程,可以采用线程池,但Socket和线程之间的对应关系不会发生变化 BIO适用于Socket连接不是很多的场景,但现在上百万的连接是很常见的,而创建上百万个线程是不现实的 因此BIO线程模型无法解决百万连接的问题 在互联网场景中,连接虽然很多,但每个连接上的请求并不频繁,因此线程大部分时间都在等待IO就绪 理想的线程模型 用一个线程来处理多个连接,可以提高线程的利用率,降低所需要的线程 使用BIO相关的API是无法实现的,BIO相关的Socket读写操作都是阻塞式的 一旦调用了阻塞式的API,在IO就绪前,调用线程会一直阻塞,也就无法处理其他的Socket连接 利用NIO相关的API能够实现一个线程处理多个连接,通过Reactor模式实现 Reactor模式 Handle指的是IO句柄,在Java网络编程里,本质上是一个网络连接 Event Handler是事件处理器,handle_event()处理IO事件,每个Event Handler处理一个IO Han...
Java并发 -- Guava RateLimiter
RateLimiter12345678910111213141516171819// 限流器流速:2请求/秒RateLimiter limiter = RateLimiter.create(2.0);ExecutorService pool = Executors.newFixedThreadPool(1);final long[] prev = {System.nanoTime()};for (int i = 0; i < 20; i++) { // 限流器限流 limiter.acquire(); pool.execute(() -> { long cur = System.nanoTime(); System.out.println((cur - prev[0]) / 1000_000); prev[0] = cur; });}// 输出// 499// 499// 497// 502// 496 令牌桶算法 Guava RateLimiter采...
Java并发 -- 生产者-消费者模式
生产者-消费者模式 生产者-消费者模式的核心是一个_任务队列_ 生产者线程生产任务,并将任务添加到任务队列中,消费者线程从任务队列中获取任务并执行 从架构设计的角度来看,生产者-消费者模式有一个很重要的优点:_解耦_ 生产者-消费者模式另一个重要的优点是支持异步,并且能够平衡生产者和消费者的速度差异(任务队列) 支持批量执行 往数据库INSERT 1000条数据,有两种方案 第一种方案:用1000个线程并发执行,每个线程INSERT一条数据 第二种方案(更优):用1个线程,执行一个批量的SQL,一次性把1000条数据INSERT进去 将原来直接INSERT数据到数据库的线程作为生产者线程,而生产者线程只需将数据添加到任务队列 然后消费者线程负责将任务从任务队列中批量取出并批量执行 1234567891011121314151617181920212223242526272829303132333435363738// 任务队列private BlockingQueue<Task> queue = new LinkedBlockingQueue<>(2...
Java并发 -- 两阶段终止模式
两阶段终止模式第一阶段线程T1向线程T2发送终止指令,第二阶段是线程T2响应终止指令 Java线程生命周期 Java线程进入终止状态的前提是线程进入RUNNABLE状态,而实际上线程可能处于休眠状态 因为如果要终止处于休眠状态的线程,要先通过interrupt把线程的状态从休眠状态转换到RUNNABLE状态 RUNNABLE状态转换到终止状态,优雅的方式是让Java线程自己执行完run方法 设置一个标志位,然后线程会在合适的时机检查这个标志位 如果发现符合终止条件,就会自动退出run方法 第二阶段:响应终止指令 终止监控操作 监控系统需要动态采集一些数据,监控系统发送采集指令给被监控系统的的监控代理 监控代理接收到指令后,从监控目标收集数据,然后回传给监控系统 处于性能的考虑,动态采集一般都会有终止操作 12345678910111213141516171819202122232425262728293031323334353637383940public class Proxy { private boolean started = false; // 采...
Java并发 -- Worker Thread模式
Worker Thread模式 Worker Thread模式可以类比现实世界里车间的工作模式,Worker Thread对应车间里的工人(人数确定) 用阻塞队列做任务池,然后创建固定数量的线程消费阻塞队列中的任务 – 这就是Java中的线程池方案 echo服务123456789101112131415161718192021222324252627private ExecutorService pool = Executors.newFixedThreadPool(500);public void handle() throws IOException { // 处理请求 try (ServerSocketChannel ssc = ServerSocketChannel.open().bind(new InetSocketAddress(8080))) { while (true) { // 接收请求 SocketChannel sc = ssc.accept(); /...
Java并发 -- Thread-Per-Message模式
概述Thread-Per-Message模式:为每个任务分配一个独立的线程 Thread123456789101112131415161718192021// 处理请求try (ServerSocketChannel ssc = ServerSocketChannel.open().bind(new InetSocketAddress(8080))) { while (true) { // 接收请求 SocketChannel sc = ssc.accept(); // 每个请求都创建一个线程 new Thread(() -> { try { // 读Socket ByteBuffer rb = ByteBuffer.allocateDirect(1024); sc.read(rb); TimeUnit.SECONDS.sleep(1); ...













