首页 文章 精选 留言 我的

精选列表

搜索[并发编程],共10000篇文章
优秀的个人博客,低调大师

百万并发「零拷贝」技术系列之经典案例Netty

Netty在零拷贝思想上的实现可以理解为是广义的,它和wiki对零拷贝宽泛的定义特别吻合“CPU 不需要将数据从一块内存拷贝到另一块内存”,因为Netty主要是在用户空间尽量减少内存的拷贝次数,而非系统层面的用户空间和内核空间数据的拷贝。 Netty作为Java界知名的NIO网络通讯框架,凭借其高性能木秀于mina、twisted,其因素之一就如官方所述:“减少了不必要的内存拷贝”。 在零拷贝实现上,它有借助于Java NIO的tranferTo实现的FileRegion用于文件传输,也有通过巧妙设计buffer数据结构来避免由于拆分、组合而带来的拷贝。尤其是后者,因为buffer是用来化零为整降低I/O操作频率的重要技术手段,对性能的影响至关重要。 FileRegion FileRegion的零拷贝是体现在系统层面的,它包装了Java NIO的FileChannel.tranferTo方法进行文件传输,从FileRegion的默认实现类DefaultFileRegion可以一探究竟 tranferTo在上一篇推文有较详细的讲解,此处不再累述。 ByteBuf Netty使用了它自己封装的buffer API替代了Java NIO的ByteBuffer:ByteBuf。官方列出了它的一些比较酷的特性 You can define your buffer type if necessary.根据需要可以定制自己的buffer类型。 Transparent zero copy is achieved by built-in composite buffer type.通过内建的组合类型可以实现透明的零拷贝。 A dynamic buffer type is provided out-of-the-box, whose capacity is expanded on demand, just like `StringBuffer`.它是一个开箱即用可根据需求动态扩展的buffer,就像`StringBuffer`。 There's no need to call the `flip()` method anymore.不再需要调用`flip()` 方法。 It is often faster than `ByteBuffer`.通常比`ByteBuffer`更快速。 DirectByteBuffer 实际上ByteBuf提供了非常丰富的实现类如下图所列,在逻辑上主要分为堆内buffer(HeapByteBuf)和堆外buffer(DirectByteBuf)。 DirectBy teBuf是用Java NIO的DirectByteBuffer实现的,所谓堆外buffer是相对于JVM堆内而言的,但网上有些资料把它和DMA混淆了。 DirectByteBuffer只是避免了JVM堆内向堆外的拷贝,但这个“堆内、堆外”依然是用户空间的范畴,因为 DirectByteBuffer是malloc() 分配出来的内存,用户空间和内核空间请参考上一篇。 拿网络传输来说,对于传统的read/write的I/O方式,一般情况而言是这样的:Java堆内存—>用户空间的堆外内存—>内核socket缓冲区—>DMA—>网卡—>网卡—>DMA—>内核socket缓冲区—>用户空间的堆外内存—>Java堆内存。 DirectByteBuffer的使用是有一定的风险的,可能会造成OutOfMemory,官方是这样描述的 allocating many short-lived direct NIO buffers often causes an OutOfMemoryError. 堆内和堆外内存各有优势和劣势,需要根据场景自行选择 网络传输的过程中对数据的拆包、组包等操作十分常见也很频繁,Netty提供了warp、Composite和slice方法来减少数据的拷贝,达到性能的提升的目标。 wrap包装 Netty可以通过各种wrap方法, 将 byte[]、ByteBuf、ByteBuffer等包装成一个ByteBuf对象,而不需要进行数据的拷贝。实际上Java NIO的ByteBuffer也有wrap,但Netty的ByteBuf提供了更丰富和便捷的wrap。 通常将一个对象比如byte数组转换成一个ByteBuffer,传统的作法是把数组拷贝到ByteBuffer对象中,而wrap的方式无须拷贝,它们共用同一块内存。 byte[]tmp=newbyte[]{1,2};//JavaNIO//传统方式(拷贝)ByteBufferbyteBuffer=ByteBuffer.allocate(2);byteBuffer.put(tmp);//wrap方式byteBuffer=ByteBuffer.wrap(tmp);//NettyByteBuf//传统方式(拷贝)ByteBufbyteBuf=Unpooled.buffer();byteBuf.writeBytes(tmp);//wrap方式byteBuf=Unpooled.wrappedBuffer(tmp); CompositeByteBuf组包 CompositeByteBuf将多个ByteBuf组合成一个ByteBuf而不需要数据拷贝,每个ByteBuf都是独立存在的,只是在逻辑上的组合,提高性能的同时可以统一使用ByteBuf的API。假设有一个数据包是由三部分组成header、body、footer,它们可能是由不同的模块创建的,组合示意和代码如下 ByteBufheader=Unpooled.wrappedBuffer(tmp);ByteBufbody=Unpooled.wrappedBuffer(tmp);ByteBuffooter=Unpooled.wrappedBuffer(tmp);//不建议的作法ByteBufwholeBuf=Unpooled.buffer(header.readableBytes()+body.readableBytes()+footer.readableBytes());wholeBuf.writeBytes(header);wholeBuf.writeBytes(body);wholeBuf.writeBytes(footer);//建议使用组合CompositeByteBufcompositeByteBuf=Unpooled.compositeBuffer();//第一个参数increaseWriterIndex,为true会自动增加writerIndexsscompositeByteBuf.addComponents(true,header,body,footer); slice拆分包 slice将一个ByteBuf分解为多个ByteBuf,但没有数据拷贝,而是共享同一个存储区域的,这在拆分包操作时非常有用,如上例数据包由header、body、footer三部分组成,拆分示意和代码如下 ByteBufbyteBuf4slice=.....;ByteBufheader=byteBuf4slice.slice(0,5);ByteBufbody=byteBuf4slice.slice(5,15);ByteBuffooter=byteBuf4slice.slice(15,20); polled池化 Netty 4.x提供了池化的Buffer,类似于线程池或数据库连接池的思想,避免了Buffer频繁的创建和释放带来的性能低效及GC压力。池化和非池化的性能对比如下 intloop=3000000;byte[]content="thisisatest".getBytes();//池化bufferlongstartTime=System.currentTimeMillis();ByteBufpooledBuf=null;for(inti=0;i<loop;i++){pooledBuf=PooledByteBufAllocator.DEFAULT.buffer(1024);pooledBuf.writeBytes(content);pooledBuf.release();}longpooledTime=System.currentTimeMillis()-startTime;System.out.println("3百万次池化buffer消耗的时间:"+pooledTime);//非池化bufferstartTime=System.currentTimeMillis();ByteBufunPooledBuf=null;for(inti=0;i<loop;i++){unPooledBuf=Unpooled.buffer(1024);unPooledBuf.writeBytes(content);unPooledBuf.release();}longunPooledTime=System.currentTimeMillis()-startTime;System.out.println("3百万次池化buffer消耗的时间:"+unPooledTime);//性能提升System.out.println("池化buffer性能提升:"+Double.valueOf(String.format("%.2f",(unPooledTime-pooledTime)/(double)unPooledTime))*100+"%"); 执行后从输出可见,池化后的buffer性能提升20%左右,非常可观 3百万次池化buffer消耗的时间:7663百万次池化buffer消耗的时间:989池化buffer性能提升:23.0% 写在最后 Netty在Java界经之所以久不衰自有它的优势,虽然Netty5夭折了,但Netty4依然足够哦强大,开发者不仅把它用于实现各种通讯应用,还在各种框架中起着顶梁柱的角色,比如阿里的Dubbo。 零拷贝系列以计算机组成及操作系统入手,以零拷贝思想在Linux和Java中的实现为传承,最终以Netty作为经典案例分析收尾,希望能对您有所启发,感谢关注。 End 版权归@码农神说所有,转载须经授权,翻版必究 可回复关键字“转载”联系助手开白 缓存穿透、缓存击穿、缓存雪崩看这篇就够了,文末还送福利哦! 2020-07-15 一口气讲透一致性哈希(Hash),助力「码农变身」 2020-07-13 漫画 | 架构设计中的那些事,文末送福利 2020-07-10 Java中异常处理的9个最佳实践 2020-07-08 Intellij IDEA必备插件,提高效率的“七种武器”! 2020-07-06 接住喽🤗,送你个装逼的技能: JDK动态代理 2020-07-04 给“小白”漫画+图示讲解MyBatis原理,就问香不香! 2020-07-01 面试官:CAP都搞不清楚,别跟我说你懂微服务! 2020-06-23 本文分享自微信公众号 - 码农神说(codeceo)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

每日一博 | 利用 VisualVM 分析高并发项目性能

前面在学习JVM的知识的时候,一般都需要利用相关参数进行分析,而分析一般都需要用到一些分析的工具,因为一般使用IDEA,而VisualVM对于IDEA也不错,所以就选择VisualVM来分析JVM性能,这篇文章就介绍一下如何利用VisualVM进行性能分析,以及在分析之前需要知道一些GC优化的原则,GC优化的目的,以及遇到问题时怎么去解决问题的方法。 1 为什么需要 开发大型 Java 应用程序的过程中难免遇到内存泄露、性能瓶颈等问题,比如文件、网络、数据库的连接未释放,未优化的算法等。随着应用程序的持续运行,可能会造成整个系统运行效率下降,严重的则会造成系统崩溃。为了找出程序中隐藏的这些问题,在项目开发后期往往会使用性能分析工具来对应用程序的性能进行分析和优化。 VisualVM 是一款免费的性能分析工具。它通过 jvmstat、JMX、SA(Serviceability Agent)以及 Attach API 等多种方式从程序运行时获得实时数据,从而进行动态的性能分析。同时,它能自动选择更快更轻量级的技术尽量减少性能分析对应用程序造成的影响,提高性能分析的精度。 2 如何安装 这里有两种方式: 没有按照IDEA插件 如果没有按照IDEA插件的话,我们需要找到JDK的按照目录bin下找到如下执行程序。 然后双击执行,就会出现界面,如下; 但是,我们一般使用IDEA,所以会使用插件,就是下面这种方式。 按照IDEA插件 先在插件中找到VisualVM安装; 安装了之后,在运行的地方就会多出现两个VisualVM的运行按钮; 这样运行程序之后,就可以自动打开VisualVM程序了。 3 基本介绍 这一部分先对这个工具做一个简要的介绍,看看基本有哪些我们会用到的功能。 在没有添加其他插件的时候,是只有下面几个功能的。 3.1 概述 如上图所示,概述基本上都是我们的系统属性、运行程序时设置的JVM参数等信息的展示,所以,这一部分可以让我们查看这些信息。 3.2 监视 监视这个界面的功能还是很有作用的,可以看到cup运行情况、堆的使用情况、类的情况以及线程的动态情况。 因此,我们可以利用这个界面查看cpu情况好不好,更重要的是,我们可以查看堆的使用情况,这对于我们分析JVM还是非常重要的。 3.3 线程 如上图所以,可以看到所有的线程的情况,是运行、休眠、等待、驻留、监视等情况。 注意, 以上这些都不是关键,关键是VisualVM中还有一个很重要的功能,可以添加插件获取更多的功能。 3.4 插件添加 正是因为有了插件的扩展功能,所以这个工具才如此强大,VisualVM可以做到以下: 显示虚拟机进程以及进程的配置、环境信息、jps、jinfo。 监视应用程序的cpu、GC、堆、方法区以及线程的信息(jstat、jstack)。 dump以及分析堆转存储快照(jmap、jhat)。 还有很多其他的功能。 在工具->找到可用插件,安装即可。 下一部分我们就利用已经安装的插件Visual GC进行分析。 4 利用Visual GC分析虚拟机内存区域 这部分会用到一些Java虚拟机的一些基础知识,所以,查看这部分之前,请先查看这篇文章:。 在这个界面分为以下几个部分。 space(Metaspace(元数据)、Old老年代、新生代(Eden、S0、S1)) Graphs(Compile Time(编译时间)、Class Loader Time(类加载时间)、GC Time(垃圾收集时间)、Eden Space、Survivor 0、Survivor 1、Old Gen、Metaspace) Histogram(Parameters参数设置) 那么知道这些参数之后,怎么去分析虚拟机到底运行是好是坏呢,这个时候,我们需要了解一些Java虚拟机基础的优化知识。 首先,需要了解一些GC优化的原则。 多数的Java应用不需要在服务器上进行GC优化; 多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题; 在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合); 减少创建对象的数量; 减少使用全局变量和大对象; GC优化是到最后不得已才采用的手段; 在实际使用中,分析GC情况优化代码比优化GC参数要多得多; 另外,我们需要知道我们GC优化的目的。 将转移到老年代的对象数量降低到最小; 减少full GC的执行时间; 一般,我们需要执行的有以下几点; 减少使用全局变量和大对象; 调整新生代的大小到最合适; 设置老年代的大小为最合适; 选择合适的GC收集器; 至于怎么算合适,后面我会通过一个实例讲解。 其实,如果想要知道更多JVM内存分配和回收策略的原理,可以查看这篇文章:JVM内存分配和回收策略的原理。 一般我们执行了我们的程序之后,接下来就是需要查看GC的状态了,接着分析结果,判断是否需要进行优化。 一般如果达到以下的指标,就不需要进行GC了。 Minor GC执行时间不到50ms,Minor GC执行不频繁,约10秒一次; Full GC执行时间不到1s,Full GC执行频率不算频繁,不低于10分钟1次; 实例 1 我们先看一个GC状态需要优化的例子,在这个实例中,我们给堆分配的最大最小的值都是64M(很小的堆大小)。 GC状态差情况分析 /** * VM Args:-Xms64m -Xmx64m -XX:+HeapDumpOnOutOfMemoryError * @author 欧阳思海 */ public class HeapTest { static class StaticObject { } public static void main(String[] args) { List<StaticObject> list = new ArrayList<StaticObject>(); int i = 1; //不断的向堆中添加对象 while (true) { list.add(new StaticObject()); i++; System.out.println(i); System.out.println(list.size()); } } } 由于分配的堆内存太小,所以导致,堆溢出。 接着我们查看一下Visual GC的监视情况。 监视界面情况 我们可以从堆的使用情况看出,基本已经使用完。 Visual GC监视情况 从上图可知,在短短的运行时间中,Eden进行了49次GC,虽然时间短,但是能说明一个问题,新生代堆内存分配的空间太小,导致频繁GC。 同时,Old老年代也进行了33次GC,虽然运行时间也在不需要优化的范围内,而且从Survivor可以看出,基本没有GC,说明这些都是大对象,直接进入到了Old老年代,导致GC频繁。 所以,我们需要进行的优化就是加大新生代和老年代堆内存的大小,同时减少大对象的产生。 参数优化分析 我们将VM参数改为:-Xms512m -Xmx512m -Xmn128m -XX:+HeapDumpOnOutOfMemoryError,运行大概5分钟再次查看结果。 首先没有出现堆内存溢出。 监视情况 加大了堆内存,所以堆内存没有出现问题。 Visual GC监视情况 加大了堆内存之后,Eden新生代进行了66次GC,使用时间3.381s基本满足要求(执行时间不到50ms,Minor GC执行不频繁,约10秒一次),同时老年代old进行了2次GC,使用时间4.127s,这里还是有待优化的,不太满足优化要求。 dump文件分析 点击堆 dump这个按钮就会生成 dump文件,我们可以分析类及对象的一些情况。 分析之后发现,StaticObject对象大多,没有进行GC,问题主要在这里,所以,下一步需要解决这个问题。 通过以上分析可以说明一个问题,加大了堆内存之后,新生代和老年代的GC情况大大的改善了,但是还有大对象的问题,所以还有待优化。 修改大对象,进行GC 修改程序,如下: /** * VM Args:-Xms512m -Xmx512m -Xmn128m -XX:+HeapDumpOnOutOfMemoryError * * @author 欧阳思海 */ public class HeapTest { /* static class StaticObject { }*/ public static void main(String[] args) { int i = 1; while (true) { i++; System.out.println(i); } } } 监视情况 堆一直运行良好。 Visual GC监视情况 这次相对于上次相比,老年代的情况已经改善了,没有GC,说明大对象不存在了。 通过上面的分析跟优化,就满足GC的需求了,不需要再优化了。 5 总结 通过上面的分析及使用,VisualVM基本的使用以及如何利用VisualVM进行Java虚拟机优化相信你已经掌握了,如果还想了解更过关于Java虚拟机的知识及优化文章,请看本系列的其他文章。 1、原创不易,老铁,文章需要你的 点赞 让更多的人看到,希望能够帮助到大家! 2、文章有不当之处,欢迎指正,如果喜欢微信阅读,你也可以关注我的微信公众号:好好学java,公众号已有 6W 粉丝,回复:1024,获取公众号的大礼包,公众号长期发布 Java 优质系列文章,关注我们一定会让你收获很多!

优秀的个人博客,低调大师

高并发下log4j的性能瓶颈

开篇 近期由于业务需要进行业务迁移,期间因为误设置log4j的日志级别,导致系统性能整体下降,具体表现在QPS下降明显,系统RT上升。迁移期间由于各类系统环境较原来有较大差别,因为在排查过程中也走了一些弯路,总结起来避免他人再次踩坑。 问题背景 问题的背景其实很简单,线上系统的log4j的日志级别由warn调整为info,导致大量的调用log4j的日志接口,导致系统的rt上升伴随着qps下降,具体的影响效果可以看下图。 需要额外解释的就是log4j的这个问题需要在qps较大的情况下才会复现,按照我遇到的情况基本上需要在2k/s以上会比较明显(数据不一定非常准确),只是为了说明需要在大量请求的情况才会触发synchronized的问题。 当然问题的本质在于大量调用log4j的日志接口,导致竞争synchronized影响性能,影响的时间应该也是在ms级别,这其实在另外一个方面说明了只求非常极致追求性能问题才会细致的去分析这些问题。 优化效果 优化后qps的提升效果图,这边是以分钟进行统计的。 优化后系统的响应时间效果图。 现象分析 整个分析过程其实并不是那么具有条理性,只是现在总结起来看上去像那么回事。 对比两套系统的差异点,发现系统的log4j的日志级别不一致,尝试修改日志级别观察效果。 发现日志级别调高后系统性能明显提升,开始怀疑log4j的问题并网上查阅资料。 jstack打印对应的日志,发现在不同日志级别情况下下面的BLOCKED的日志数量不一样。 发现竞争锁的日志waiting to lock <0x00007f3a04020830>。 观察jstack日志并找到log4j的源码发现代码当中有同步锁关键字synchronized。 #514 daemon prio=5 os_prio=0 tid=0x00007f369002f800 nid=0x561a waiting for monitor entry [0x00007f348fb96000] java.lang.Thread.State: BLOCKED (on object monitor) at org.apache.log4j.Category.callAppenders(Category.java:204) - waiting to lock <0x00007f3a04020830> (a org.apache.log4j.spi.RootLogger) at org.apache.log4j.Category.forcedLog(Category.java:391) at org.apache.log4j.Category.log(Category.java:856) #521 daemon prio=5 os_prio=0 tid=0x00007f367c042800 nid=0x5617 waiting for monitor entry [0x00007f348fe99000] java.lang.Thread.State: BLOCKED (on object monitor) at org.apache.log4j.Category.callAppenders(Category.java:204) - waiting to lock <0x00007f3a04020830> (a org.apache.log4j.spi.RootLogger) at org.apache.log4j.Category.forcedLog(Category.java:391) at org.apache.log4j.Category.log(Category.java:856) 原因分析 直接定位log4j的源码位置,发现synchronized的关键字,基本上这时候其实是验证之前的猜想而已。关于synchronized的性能问题,可以网上去参考java几种锁的性能对比,只记得一个简单的结论synchronized在大量竞争的情况下劣势比较明显各种同步方法性能比较(synchronized,ReentrantLock,Atomic)。 void callAppenders(LoggingEvent event) { int writes = 0; for(Category c = this; c != null; c=c.parent) { // Protected against simultaneous call to addAppender, removeAppender,... synchronized(c) { if(c.aai != null) { writes += c.aai.appendLoopOnAppenders(event); } if(!c.additive) { break; } } } if(writes == 0) { repository.emitNoAppenderWarning(this); } } 心得 老大曾说过,凡事做个有心人,会发现更多的东西。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册