性能优化

性能优化

String.intern() 祛魅

默认分类admin 发表了文章 • 0 个评论 • 148 次浏览 • 2017-01-17 17:31 • 来自相关话题

String.intern(),写应用的同学不知道也没什么损失。但知道的同学,如果只觉得它很省内存,用==比较字符串很酷,或者期待JDK会做什么神奇的事情,然后恨不得所有地方都用上String.intern(),那很可能会误用,所以把上周在群里的讨论整理成此文。

在社会科学中,祛魅(Disenchantment)是指在现代社会中消去神秘主义(魅惑力)的表面并把文化合理化。这里乱用一下。

 

1. String.itern()的基本原理

详细可看占小狼同学的《浅谈Java String内幕(2)》

String.intern()是一个Native方法,底层调用C++的 StringTable::intern 方法,源码注释:当调用 intern 方法时,如果常量池中已经该字符串,则返回池中的字符串;否则将此字符串添加到常量池中,并返回字符串的引用。

所以明面上,它有两大好处,一是重复的字符串,会用同一个引用代替;二是字符串比较,不再需要逐个字符的equals()比较,而用==对比引用是否相同即可。

 

2. 省内存效果只对长期存在的字符串有效

String.intern()没有神奇的地方,只在字符串生成后,再去常量池里查找引用。所以字符串最初生成时所花的内存,是省不掉的。

String s = new String(bytes, “UTF-8”).intern();
String s = String.valueOf(i).intern();

只有大量对象放在长期存在的集合里,里面是大量重复的字符串,或者对象的属性是重复的字符串时,省内存的效果才显现出来。短生命周期的字符串,GC要干的活是一样的。

 

3. 执行路径上多次的==,才能抵消常量池HasHMap查找的代价

==当然比equals()快得多,但常量池其实是个HashMap,依然没有神奇的地方,依然要执行HashMap的get操作,所以,一次hashCode() 和至少一次的equals()已经预付了,如果hash冲突,那equals()次数更多。

 

4. 真的对性能影响甚微吗?

在我的服务化框架测试里,把几个Header字段intern了,性能立马从七万五调到七万一 QPS,原来从七万一升到七万五 ,曾做过多少效果甚微的优化加上一次Netty使用的优化而成,现在它掉下来倒是飞快。

PS. 七万五 20%CPU这个数字,这两周的博客里都没升过了: (

 

5. 小陷阱

来自R大的提醒, s.intern()是无效的,因为String是不变对象, String s1 = s.intern()后,这个s1才是个引用。

Java的常量池也是不省心的,要注意JDK版本,占小狼同学的《浅谈Java String内幕(2)》 查看全部
String.intern(),写应用的同学不知道也没什么损失。但知道的同学,如果只觉得它很省内存,用==比较字符串很酷,或者期待JDK会做什么神奇的事情,然后恨不得所有地方都用上String.intern(),那很可能会误用,所以把上周在群里的讨论整理成此文。

在社会科学中,祛魅(Disenchantment)是指在现代社会中消去神秘主义(魅惑力)的表面并把文化合理化。这里乱用一下。

 

1. String.itern()的基本原理

详细可看占小狼同学的《浅谈Java String内幕(2)》

String.intern()是一个Native方法,底层调用C++的 StringTable::intern 方法,源码注释:当调用 intern 方法时,如果常量池中已经该字符串,则返回池中的字符串;否则将此字符串添加到常量池中,并返回字符串的引用。

所以明面上,它有两大好处,一是重复的字符串,会用同一个引用代替;二是字符串比较,不再需要逐个字符的equals()比较,而用==对比引用是否相同即可。

 

2. 省内存效果只对长期存在的字符串有效

String.intern()没有神奇的地方,只在字符串生成后,再去常量池里查找引用。所以字符串最初生成时所花的内存,是省不掉的。

String s = new String(bytes, “UTF-8”).intern();
String s = String.valueOf(i).intern();

只有大量对象放在长期存在的集合里,里面是大量重复的字符串,或者对象的属性是重复的字符串时,省内存的效果才显现出来。短生命周期的字符串,GC要干的活是一样的。

 

3. 执行路径上多次的==,才能抵消常量池HasHMap查找的代价

==当然比equals()快得多,但常量池其实是个HashMap,依然没有神奇的地方,依然要执行HashMap的get操作,所以,一次hashCode() 和至少一次的equals()已经预付了,如果hash冲突,那equals()次数更多。

 

4. 真的对性能影响甚微吗?

在我的服务化框架测试里,把几个Header字段intern了,性能立马从七万五调到七万一 QPS,原来从七万一升到七万五 ,曾做过多少效果甚微的优化加上一次Netty使用的优化而成,现在它掉下来倒是飞快。

PS. 七万五 20%CPU这个数字,这两周的博客里都没升过了: (

 

5. 小陷阱

来自R大的提醒, s.intern()是无效的,因为String是不变对象, String s1 = s.intern()后,这个s1才是个引用。

Java的常量池也是不省心的,要注意JDK版本,占小狼同学的《浅谈Java String内幕(2)》

另一份Java应用调优指南之-前菜

默认分类admin 发表了文章 • 0 个评论 • 172 次浏览 • 2017-01-17 17:25 • 来自相关话题

每一次成功的调优,都会诞生又一份的调优指南。

一些必须写在前面的军规,虽然与Java应用的调优没直接关联,但是测试同学经常不留神的地方,导致应用的优化变成一场测试环境调优的戏码。
 

1 独占你的测试机器

包括跑JMeter的那些机器。

"top"或者"pidstat 1" 看一下,其他的路人甲乙丙丁的应用都关干净了没。

如果是云主机,确保更多的占有宿主机的资源,比如深夜大家下班了你在家连VPN回来跑。

 

2 了解你的测试机器

必须完完全全的了解你的机器,才知道有没卡在某个瓶颈,或者与线上环境、其他测试结果的比较。

还是那句, 包括跑JMeter的那些机器。

 

2.1 CPU

"cat /proc/cpuinfo", 看最后一条就好,比如

 

processor : 23
model name : Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz
physical id : 1
cpu cores : 6

所有数字都从零开始,physical id:1即两颗cpu, cpu core: 6即6核,processor : 23即24个处理器。

 

2 CPU * 6 Core * 2HT(Intel超线程技术) = 24 Processor

不过也有很多同事喜欢说24核,也懒得纠正了。

 

2.2 内存

"free -g" 没什么好说的。

 

2.3 硬盘

查看大小、分区、文件系统类型: "df -hT"
硬盘是否SCSI:/dev/sdX就是scsi的,hdX就是普通的。
硬盘是否SSD : "cat /sys/block/sda/queue/rotational", 0是SSD,1是传统硬盘,但也不一定灵

普通硬盘的写速度大概100M/s,RAID级别的查看不方便,SSD的速度也不定,所以用dd测一下最靠谱:

 

dd if=/dev/zero of=dd.file bs=8k count=128k conv=fdatasync
dd if=/dev/zero of=./dd.file bs=1G count=1 conv=fdatasync

上面命令测试了分别以每次8k和1g的大小,写入1g文件的速度。

if:输入文件名, /dev/zero 设备无穷尽地提供0
of:输出文件名
bs:块大小
count:次数
conv=fdatasync :实际写盘,而不是写入Page Cache

硬盘读速度的测试同理,不过要先清理缓存,否则直接从Page Cache读了。

 

sh -c "sync && echo 3 > /proc/sys/vm/drop_caches”
dd if=./dd.file of=/dev/null bs=8k

 

2.4 网卡

先用ifconfig看看有多少块网卡和bonding。bonding是个很棒的东西,可以把多块网卡绑起来,突破单块网卡的带宽限制。

然后检查每块网卡的速度,比如"ethtool eth0"。

再检查bonding,比如"cat /proc/net/bonding/bond0", 留意其Bonding Mode是负载均衡的,再留意其捆绑的网卡的速度。

最后检查测试客户机与服务机之间的带宽,先简单ping或traceroute 一下得到RTT时间,iperf之类的可稍后。

 

2.5 操作系统

Linux的内核版本,是否64位: "uname -a"
Redhat/CentOS版本 : "cat /etc/redhat-release"

 

3. 布置好你的机器状态采集工具

实时观察的,我喜欢dstat,详见《从dstat理解Linux性能监控体系》比vmstat,iostat, sar们都好用,起码对得够齐,单位能自动转换。不过dstat需要安装(yum install dstat,或者去它的网站下载解压即用版)

dstat -tamp:推荐,打印时间戳,比默认打印多一个memory信息,一个有否进程在block状态的信息
dstat -tamN bond0,lo: 如果有bonding,dstat会把bond0和eth0 有时会算双份,还有lo的也算到总量里,所以先用-N指定网卡检查下。

参考资料:后台性能测试不可不知的二三事

 

4. JMeter的调优顶一半的事

JMeter的版本越新越好。

 

4.1 JMeter的JVM参数

它默认连个垃圾收集算法都没有配,对延时要求高的,必须配上CMS或G1,内存也整大点降低GC的频率。其他的,给Server配的啥参数,给JMeter也来上一份。如果想动态改的,不写死在脚本里,可以配置环境变量$JVM_ARGS

 

4.2 测试计划的编写

什么if 语句,以及所有其实用动态语言来实现的都挺慢的。
xPath抽取结果集里的字段之类看着就慢的也别写了。
别加任何监听器和图形。
再配置输出日志的格式,能不要的列都别要了,最极端的其实就延时这列有用。

 

4.3 JMeter的运行

在Linux上用命令行跑,别偷懒用Window开着界面跑。
别开超过200条线程(虚拟机上更少)。
可以在不同机器上起多个JMeter,用集群汇总的模式。

 

4.4 结果的统计

初始连接,Server端热身,JVM编译热点方法等都需要时间,所以建议统计前删掉前面的一些日志。

要配置一下才能看到99.9%, 99.99% 分位数的延时,另外因为之前输出日志时省略了很多列,导入日志的时候配置也要如此。

但如果不能XWindows Forward,还要把日志下载回来再导入本地的JMeter,那还不如自己动动手,用sed, awk, sort配合一下自己写个分析延时的脚本直接在服务器上运行。

 

5. 其他被依赖的节点

比如所依赖的数据库够不够快,Restful API的模拟器够不够快,负载均衡器如HAProxy优化配置了没有。

 
唠叨完这些写给测试同学的话,下篇,就可以正式开始调优了。 查看全部
每一次成功的调优,都会诞生又一份的调优指南。

一些必须写在前面的军规,虽然与Java应用的调优没直接关联,但是测试同学经常不留神的地方,导致应用的优化变成一场测试环境调优的戏码。
 

1 独占你的测试机器

包括跑JMeter的那些机器。

"top"或者"pidstat 1" 看一下,其他的路人甲乙丙丁的应用都关干净了没。

如果是云主机,确保更多的占有宿主机的资源,比如深夜大家下班了你在家连VPN回来跑。

 

2 了解你的测试机器

必须完完全全的了解你的机器,才知道有没卡在某个瓶颈,或者与线上环境、其他测试结果的比较。

还是那句, 包括跑JMeter的那些机器。

 

2.1 CPU

"cat /proc/cpuinfo", 看最后一条就好,比如

 

processor : 23
model name : Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz
physical id : 1
cpu cores : 6

所有数字都从零开始,physical id:1即两颗cpu, cpu core: 6即6核,processor : 23即24个处理器。

 

2 CPU * 6 Core * 2HT(Intel超线程技术) = 24 Processor

不过也有很多同事喜欢说24核,也懒得纠正了。

 

2.2 内存

"free -g" 没什么好说的。

 

2.3 硬盘

查看大小、分区、文件系统类型: "df -hT"
硬盘是否SCSI:/dev/sdX就是scsi的,hdX就是普通的。
硬盘是否SSD : "cat /sys/block/sda/queue/rotational", 0是SSD,1是传统硬盘,但也不一定灵

普通硬盘的写速度大概100M/s,RAID级别的查看不方便,SSD的速度也不定,所以用dd测一下最靠谱:

 

dd if=/dev/zero of=dd.file bs=8k count=128k conv=fdatasync
dd if=/dev/zero of=./dd.file bs=1G count=1 conv=fdatasync

上面命令测试了分别以每次8k和1g的大小,写入1g文件的速度。

if:输入文件名, /dev/zero 设备无穷尽地提供0
of:输出文件名
bs:块大小
count:次数
conv=fdatasync :实际写盘,而不是写入Page Cache

硬盘读速度的测试同理,不过要先清理缓存,否则直接从Page Cache读了。

 

sh -c "sync && echo 3 > /proc/sys/vm/drop_caches”
dd if=./dd.file of=/dev/null bs=8k

 

2.4 网卡

先用ifconfig看看有多少块网卡和bonding。bonding是个很棒的东西,可以把多块网卡绑起来,突破单块网卡的带宽限制。

然后检查每块网卡的速度,比如"ethtool eth0"。

再检查bonding,比如"cat /proc/net/bonding/bond0", 留意其Bonding Mode是负载均衡的,再留意其捆绑的网卡的速度。

最后检查测试客户机与服务机之间的带宽,先简单ping或traceroute 一下得到RTT时间,iperf之类的可稍后。

 

2.5 操作系统

Linux的内核版本,是否64位: "uname -a"
Redhat/CentOS版本 : "cat /etc/redhat-release"

 

3. 布置好你的机器状态采集工具

实时观察的,我喜欢dstat,详见《从dstat理解Linux性能监控体系》比vmstat,iostat, sar们都好用,起码对得够齐,单位能自动转换。不过dstat需要安装(yum install dstat,或者去它的网站下载解压即用版)

dstat -tamp:推荐,打印时间戳,比默认打印多一个memory信息,一个有否进程在block状态的信息
dstat -tamN bond0,lo: 如果有bonding,dstat会把bond0和eth0 有时会算双份,还有lo的也算到总量里,所以先用-N指定网卡检查下。

参考资料:后台性能测试不可不知的二三事

 

4. JMeter的调优顶一半的事

JMeter的版本越新越好。

 

4.1 JMeter的JVM参数

它默认连个垃圾收集算法都没有配,对延时要求高的,必须配上CMS或G1,内存也整大点降低GC的频率。其他的,给Server配的啥参数,给JMeter也来上一份。如果想动态改的,不写死在脚本里,可以配置环境变量$JVM_ARGS

 

4.2 测试计划的编写

什么if 语句,以及所有其实用动态语言来实现的都挺慢的。
xPath抽取结果集里的字段之类看着就慢的也别写了。
别加任何监听器和图形。
再配置输出日志的格式,能不要的列都别要了,最极端的其实就延时这列有用。

 

4.3 JMeter的运行

在Linux上用命令行跑,别偷懒用Window开着界面跑。
别开超过200条线程(虚拟机上更少)。
可以在不同机器上起多个JMeter,用集群汇总的模式。

 

4.4 结果的统计

初始连接,Server端热身,JVM编译热点方法等都需要时间,所以建议统计前删掉前面的一些日志。

要配置一下才能看到99.9%, 99.99% 分位数的延时,另外因为之前输出日志时省略了很多列,导入日志的时候配置也要如此。

但如果不能XWindows Forward,还要把日志下载回来再导入本地的JMeter,那还不如自己动动手,用sed, awk, sort配合一下自己写个分析延时的脚本直接在服务器上运行。

 

5. 其他被依赖的节点

比如所依赖的数据库够不够快,Restful API的模拟器够不够快,负载均衡器如HAProxy优化配置了没有。

 
唠叨完这些写给测试同学的话,下篇,就可以正式开始调优了。

Java ThreadPool的正确打开方式

默认分类admin 发表了文章 • 0 个评论 • 149 次浏览 • 2017-01-17 16:45 • 来自相关话题

原文链接:http://calvin1978.blogcn.com/a ... .html
 线程池应对于突然增大、来不及处理的请求,无非两种应对方式:

将未完成的请求放在队列里等待
临时增加处理线程,等高峰回落后再结束临时线程

JDK的Executors.newFixedPool() 和newCachedPool(),分别使用了这两种方式。

不过,这俩函数在方便之余,也屏蔽了ThreadPool原本多样的配置,对一些不求甚解的码农来说,就错过了一些更适合自己项目的选择。

 

1. ThreadPoolExecutor的原理

经典书《Java Concurrency in Pratice(Java并发编程实战)》的第8章,浓缩如下:

1. 每次提交任务时,如果线程数还没达到coreSize就创建新线程并绑定该任务。
所以第coreSize次提交任务后线程总数必达到coreSize,不会重用之前的空闲线程。
在生产环境,为了避免首次调用超时,可以调用executor.prestartCoreThread()预创建所有core线程,避免来一个创一个带来首次调用慢的问题。

2. 线程数达到coreSize后,新增的任务就放到工作队列里,而线程池里的线程则努力的使用take()阻塞地从工作队列里拉活来干。

3. 如果队列是个有界队列,又如果线程池里的线程不能及时将任务取走,工作队列可能会满掉,插入任务就会失败,此时线程池就会紧急的再创建新的临时线程来补救。

4. 临时线程使用poll(keepAliveTime,timeUnit)来从工作队列拉活,如果时候到了仍然两手空空没拉到活,表明它太闲了,就会被解雇掉。

5. 如果core线程数+临时线程数 >maxSize,则不能再创建新的临时线程了,转头执行RejectExecutionHanlder。默认的AbortPolicy抛RejectedExecutionException异常,其他选择包括静默放弃当前任务(Discard),放弃工作队列里最老的任务(DisacardOldest),或由主线程来直接执行(CallerRuns),或你自己发挥想象力写的一个。

 

2. FixedPool 与 CachedPool

FixedPool默认用了一条无界的工作队列 LinkedBlockingQueue, 所以只去到上面的第2步就不会继续往下走了,coreSize的线程做不完的任务不断堆积到无限长的Queue中。
所以只有coreSize一个参数,其他maxSize,keepAliveTime,RejectHandler的配置都不会实际生效。

CachedPool则把coreSize设成0,然后选用了一种特殊的Queue -- SynchronousQueue,只要当前没有空闲线程,Queue就会立刻报插入失败,让线程池增加新的临时线程,默认的KeepAliveTime是1分钟,而且maxSize是整型的最大值,也就是说只要有干不完的活,都会无限增增加线程数,直到高峰过去线程数才会回落。

 

3. 对FixedPool的进一步配置
3.1 设置QueueSize

如果不想搞一条无限长的Queue,避免任务无限等待显得像假死,同时占用太多内存,可能会把它换成一条有界的ArrayBlockingQueue,那就要同时关注一下这条队列满了之后的场景,选择正确的rejectHanlder。

此时,最好还是把maxSize设为coreSize一样的值,不把临时线程及其keepAlive时间拉进来,Queue+临时线程两者结合听是好听,但很难设置好。

3.2 有界队列选LinkedBlockingQueue 还是ArrayBlockingQueue?

按Executors的JavaDoc上说是ArrayBlockingQueue,起码ArrayBlockingQueue每插入一个Runnable就直接放到内部的数组里,而LinkedBlockingQueue则要 new Node(runnable),无疑会产生更多对象。而性能方面有兴趣的同学可以自己测一下。

allowCoreThreadTimeOut(true)

允许core线程也在完全没流量时收缩到0,但因为JDK的算法,只要当前线程数低于core,请求一来就会创建线程,不管现在有没有空闲的线程能服务这个请求,所以这个选项的作用有限,仅在完全没流量时有效。 但都完全没流量了,怎么滴其实也没所谓了。除非是同时有很多个线程池的情况。

 

4. 对CachedPool的进一步配置
4.1 设置coreSize

coreSize默认为0,但很多时候也希望是一个类似FixedPool的固定值,能处理大部分的情况,不要有太多加加减减的波动,等待和消耗的精力。

4.2 设置maxSize及rejectHandler

同理,maxSize默认是整形最大值,但太多的线程也很可能让系统崩溃,所以建议还是设一下maxSize和rejectHandler。

4.3 设置keepAliveTime

默认1分钟,可以根据项目再设置一把。

4.4 SynchronousQueue的性能?

高并发下,SynchronousQueue的性能绝对比LinkedBlockingQueue/ArrayBlockingQueue低一大截。虽然JDK6的实现号称比JDK5的改进很多,但还是慢,据文章说只在20线程并发下它才是快的。

 

5. SpringSide的ThreadPoolBuilder

广告时间,SpringSide的ThreadPoolBuilder能简化上述的配置。

此文太科普太水,主要就是为了帮SpringSide-Utils项目打广告:) 查看全部
原文链接:http://calvin1978.blogcn.com/a ... .html
 线程池应对于突然增大、来不及处理的请求,无非两种应对方式:

将未完成的请求放在队列里等待
临时增加处理线程,等高峰回落后再结束临时线程

JDK的Executors.newFixedPool() 和newCachedPool(),分别使用了这两种方式。

不过,这俩函数在方便之余,也屏蔽了ThreadPool原本多样的配置,对一些不求甚解的码农来说,就错过了一些更适合自己项目的选择。

 

1. ThreadPoolExecutor的原理

经典书《Java Concurrency in Pratice(Java并发编程实战)》的第8章,浓缩如下:

1. 每次提交任务时,如果线程数还没达到coreSize就创建新线程并绑定该任务。
所以第coreSize次提交任务后线程总数必达到coreSize,不会重用之前的空闲线程。
在生产环境,为了避免首次调用超时,可以调用executor.prestartCoreThread()预创建所有core线程,避免来一个创一个带来首次调用慢的问题。

2. 线程数达到coreSize后,新增的任务就放到工作队列里,而线程池里的线程则努力的使用take()阻塞地从工作队列里拉活来干。

3. 如果队列是个有界队列,又如果线程池里的线程不能及时将任务取走,工作队列可能会满掉,插入任务就会失败,此时线程池就会紧急的再创建新的临时线程来补救。

4. 临时线程使用poll(keepAliveTime,timeUnit)来从工作队列拉活,如果时候到了仍然两手空空没拉到活,表明它太闲了,就会被解雇掉。

5. 如果core线程数+临时线程数 >maxSize,则不能再创建新的临时线程了,转头执行RejectExecutionHanlder。默认的AbortPolicy抛RejectedExecutionException异常,其他选择包括静默放弃当前任务(Discard),放弃工作队列里最老的任务(DisacardOldest),或由主线程来直接执行(CallerRuns),或你自己发挥想象力写的一个。

 

2. FixedPool 与 CachedPool

FixedPool默认用了一条无界的工作队列 LinkedBlockingQueue, 所以只去到上面的第2步就不会继续往下走了,coreSize的线程做不完的任务不断堆积到无限长的Queue中。
所以只有coreSize一个参数,其他maxSize,keepAliveTime,RejectHandler的配置都不会实际生效。

CachedPool则把coreSize设成0,然后选用了一种特殊的Queue -- SynchronousQueue,只要当前没有空闲线程,Queue就会立刻报插入失败,让线程池增加新的临时线程,默认的KeepAliveTime是1分钟,而且maxSize是整型的最大值,也就是说只要有干不完的活,都会无限增增加线程数,直到高峰过去线程数才会回落。

 

3. 对FixedPool的进一步配置
3.1 设置QueueSize

如果不想搞一条无限长的Queue,避免任务无限等待显得像假死,同时占用太多内存,可能会把它换成一条有界的ArrayBlockingQueue,那就要同时关注一下这条队列满了之后的场景,选择正确的rejectHanlder。

此时,最好还是把maxSize设为coreSize一样的值,不把临时线程及其keepAlive时间拉进来,Queue+临时线程两者结合听是好听,但很难设置好。

3.2 有界队列选LinkedBlockingQueue 还是ArrayBlockingQueue?

按Executors的JavaDoc上说是ArrayBlockingQueue,起码ArrayBlockingQueue每插入一个Runnable就直接放到内部的数组里,而LinkedBlockingQueue则要 new Node(runnable),无疑会产生更多对象。而性能方面有兴趣的同学可以自己测一下。

allowCoreThreadTimeOut(true)

允许core线程也在完全没流量时收缩到0,但因为JDK的算法,只要当前线程数低于core,请求一来就会创建线程,不管现在有没有空闲的线程能服务这个请求,所以这个选项的作用有限,仅在完全没流量时有效。 但都完全没流量了,怎么滴其实也没所谓了。除非是同时有很多个线程池的情况。

 

4. 对CachedPool的进一步配置
4.1 设置coreSize

coreSize默认为0,但很多时候也希望是一个类似FixedPool的固定值,能处理大部分的情况,不要有太多加加减减的波动,等待和消耗的精力。

4.2 设置maxSize及rejectHandler

同理,maxSize默认是整形最大值,但太多的线程也很可能让系统崩溃,所以建议还是设一下maxSize和rejectHandler。

4.3 设置keepAliveTime

默认1分钟,可以根据项目再设置一把。

4.4 SynchronousQueue的性能?

高并发下,SynchronousQueue的性能绝对比LinkedBlockingQueue/ArrayBlockingQueue低一大截。虽然JDK6的实现号称比JDK5的改进很多,但还是慢,据文章说只在20线程并发下它才是快的。

 

5. SpringSide的ThreadPoolBuilder

广告时间,SpringSide的ThreadPoolBuilder能简化上述的配置。

此文太科普太水,主要就是为了帮SpringSide-Utils项目打广告:)

StringBuilder在高性能场景下的正确用法

默认分类admin 发表了文章 • 0 个评论 • 157 次浏览 • 2017-01-17 16:44 • 来自相关话题

原文链接:http://calvin1978.blogcn.com/a ... .html
 关于StringBuilder,一般同学只简单记住了,字符串拼接要用StringBuilder,不要用+,也不要用StringBuffer,然后性能就是最好的了,真的吗吗吗吗?

还有些同学,还听过三句似是而非的经验:

1. Java编译优化后+和StringBuilder的效果一样;

2. StringBuilder不是线程安全的,为了“安全”起见最好还是用StringBuffer;

3. 永远不要自己拼接日志信息的字符串,交给slf4j来。

 

1. 初始长度好重要,值得说四次。

StringBuilder的内部有一个char[], 不断的append()就是不断的往char[]里填东西的过程。

new StringBuilder() 时char[]的默认长度是16,然后,如果要append第17个字符,怎么办?

用System.arraycopy成倍复制扩容!!!!

这样一来有数组拷贝的成本,二来原来的char[]也白白浪费了要被GC掉。可以想见,一个129字符长度的字符串,经过了16,32,64, 128四次的复制和丢弃,合共申请了496字符的数组,在高性能场景下,这几乎不能忍。

所以,合理设置一个初始值多重要。

但如果我实在估算不好呢?多估一点点好了,只要字符串最后大于16,就算浪费一点点,也比成倍的扩容好。

 

2. Liferay的StringBundler类

Liferay的StringBundler类提供了另一个长度设置的思路,它在append()的时候,不急着往char[]里塞东西,而是先拿一个String[]把它们都存起来,到了最后才把所有String的length加起来,构造一个合理长度的StringBuilder。

 

3. 但,还是浪费了一倍的char[]

浪费发生在最后一步,StringBuilder.toString()

 

//创建拷贝, 不共享数组
return new String(value, 0, count);

String的构造函数会用 System.arraycopy()复制一把传入的char[]来保证安全性不可变性,如果故事就这样结束,StringBuilder里的char[]还是被白白牺牲了。

为了不浪费这些char[],一种方法是用Unsafe之类的各种黑科技,绕过构造函数直接给String的char[]属性赋值,但很少人这样做。

另一个靠谱一些的办法就是重用StringBuilder。而重用,还解决了前面的长度设置问题,因为即使一开始估算不准,多扩容几次之后也够了。

 

4. 重用StringBuilder

这个做法来源于JDK里的BigDecimal类(没事看看JDK代码多重要),后来发现Netty也同样使用。SpringSide里将代码提取成StringBuilderHolder,里面只有一个函数

 

public StringBuilder getStringBuilder() {
sb.setLength(0);
return sb;
}

StringBuilder.setLength()函数只重置它的count指针,而char[]则会继续重用,而toString()时会把当前的count指针也作为参数传给String的构造函数,所以不用担心把超过新内容大小的旧内容也传进去了。可见,StringBuilder是完全可以被重用的。

为了避免并发冲突,这个Holder一般设为ThreadLocal,标准写法见BigDecimal或StringBuilderHolder的注释。

不过,如果String的长度不大,那从ThreadLocal里取一次值的代价还更大的多,所以也不能把这个ThreadLocalStringBuilder搞出来后,见到StringBuilder就替换。。。
 

5. + 与 StringBuilder

 

String s = “hello ” + user.getName();

这一句经过javac编译后的效果,的确等价于使用StringBuilder,但没有设定长度。

 

String s = new StringBuilder().append(“hello”).append(user.getName());

但是,如果像下面这样:

 

String s = “hello ”;
// 隔了其他一些语句
s = s + user.getName();

每一条语句,都会生成一个新的StringBuilder,这里就有了两个StringBuilder,性能就完全不一样了。如果是在循环体里s+=i; 就更加多得没谱。

据R大说,努力的JVM工程师们在运行优化阶段, 根据+XX:+OptimizeStringConcat(JDK7u40后默认打开),把相邻的(中间没隔着控制语句) StringBuilder合成一个,也会努力的猜长度。

所以,保险起见还是继续自己用StringBuilder并设定长度好了。

 

6. StringBuffer 与 StringBuilder

StringBuffer与StringBuilder都是继承于AbstractStringBuilder,唯一的区别就是StringBuffer的函数上都有synchronized关键字。

那些说StringBuffer “安全”的同学,其实你几时看过几个线程轮流append一个StringBuffer的情况???

 

7. 永远把日志的字符串拼接交给slf4j??

 

logger.info("Hello {}", user.getName());

对于不知道要不要输出的日志,交给slf4j在真的需要输出时才去拼接的确能省节约成本。

但对于一定要输出的日志,直接自己用StringBuilder拼接更快。因为看看slf4j的实现,实际上就是不断的indexof("{}"), 不断的subString(),再不断的用StringBuilder拼起来而已,没有银弹。

PS. slf4j中的StringBuilder在原始Message之外预留了50个字符,如果可变参数加起来长过50字符还是得复制扩容......而且StringBuilder也没有重用。

 

8. 小结

StringBuilder默认的写法,会为129长度的字符串拼接,合共申请625字符的数组。所以高性能的场景下,永远要考虑用一个ThreadLocal 可重用的StringBuilder。而且重用之后,就不用再玩猜长度的游戏了。当然,如果字符串只有一百几十字节,也不一定要考虑重用,设好初始值就好。 查看全部
原文链接:http://calvin1978.blogcn.com/a ... .html
 关于StringBuilder,一般同学只简单记住了,字符串拼接要用StringBuilder,不要用+,也不要用StringBuffer,然后性能就是最好的了,真的吗吗吗吗?

还有些同学,还听过三句似是而非的经验:

1. Java编译优化后+和StringBuilder的效果一样;

2. StringBuilder不是线程安全的,为了“安全”起见最好还是用StringBuffer;

3. 永远不要自己拼接日志信息的字符串,交给slf4j来。

 

1. 初始长度好重要,值得说四次。

StringBuilder的内部有一个char[], 不断的append()就是不断的往char[]里填东西的过程。

new StringBuilder() 时char[]的默认长度是16,然后,如果要append第17个字符,怎么办?

用System.arraycopy成倍复制扩容!!!!

这样一来有数组拷贝的成本,二来原来的char[]也白白浪费了要被GC掉。可以想见,一个129字符长度的字符串,经过了16,32,64, 128四次的复制和丢弃,合共申请了496字符的数组,在高性能场景下,这几乎不能忍。

所以,合理设置一个初始值多重要。

但如果我实在估算不好呢?多估一点点好了,只要字符串最后大于16,就算浪费一点点,也比成倍的扩容好。

 

2. Liferay的StringBundler类

Liferay的StringBundler类提供了另一个长度设置的思路,它在append()的时候,不急着往char[]里塞东西,而是先拿一个String[]把它们都存起来,到了最后才把所有String的length加起来,构造一个合理长度的StringBuilder。

 

3. 但,还是浪费了一倍的char[]

浪费发生在最后一步,StringBuilder.toString()

 

//创建拷贝, 不共享数组
return new String(value, 0, count);

String的构造函数会用 System.arraycopy()复制一把传入的char[]来保证安全性不可变性,如果故事就这样结束,StringBuilder里的char[]还是被白白牺牲了。

为了不浪费这些char[],一种方法是用Unsafe之类的各种黑科技,绕过构造函数直接给String的char[]属性赋值,但很少人这样做。

另一个靠谱一些的办法就是重用StringBuilder。而重用,还解决了前面的长度设置问题,因为即使一开始估算不准,多扩容几次之后也够了。

 

4. 重用StringBuilder

这个做法来源于JDK里的BigDecimal类(没事看看JDK代码多重要),后来发现Netty也同样使用。SpringSide里将代码提取成StringBuilderHolder,里面只有一个函数

 

public StringBuilder getStringBuilder() {
sb.setLength(0);
return sb;
}

StringBuilder.setLength()函数只重置它的count指针,而char[]则会继续重用,而toString()时会把当前的count指针也作为参数传给String的构造函数,所以不用担心把超过新内容大小的旧内容也传进去了。可见,StringBuilder是完全可以被重用的。

为了避免并发冲突,这个Holder一般设为ThreadLocal,标准写法见BigDecimal或StringBuilderHolder的注释。

不过,如果String的长度不大,那从ThreadLocal里取一次值的代价还更大的多,所以也不能把这个ThreadLocalStringBuilder搞出来后,见到StringBuilder就替换。。。
 

5. + 与 StringBuilder

 

String s = “hello ” + user.getName();

这一句经过javac编译后的效果,的确等价于使用StringBuilder,但没有设定长度。

 

String s = new StringBuilder().append(“hello”).append(user.getName());

但是,如果像下面这样:

 

String s = “hello ”;
// 隔了其他一些语句
s = s + user.getName();

每一条语句,都会生成一个新的StringBuilder,这里就有了两个StringBuilder,性能就完全不一样了。如果是在循环体里s+=i; 就更加多得没谱。

据R大说,努力的JVM工程师们在运行优化阶段, 根据+XX:+OptimizeStringConcat(JDK7u40后默认打开),把相邻的(中间没隔着控制语句) StringBuilder合成一个,也会努力的猜长度。

所以,保险起见还是继续自己用StringBuilder并设定长度好了。

 

6. StringBuffer 与 StringBuilder

StringBuffer与StringBuilder都是继承于AbstractStringBuilder,唯一的区别就是StringBuffer的函数上都有synchronized关键字。

那些说StringBuffer “安全”的同学,其实你几时看过几个线程轮流append一个StringBuffer的情况???

 

7. 永远把日志的字符串拼接交给slf4j??

 

logger.info("Hello {}", user.getName());

对于不知道要不要输出的日志,交给slf4j在真的需要输出时才去拼接的确能省节约成本。

但对于一定要输出的日志,直接自己用StringBuilder拼接更快。因为看看slf4j的实现,实际上就是不断的indexof("{}"), 不断的subString(),再不断的用StringBuilder拼起来而已,没有银弹。

PS. slf4j中的StringBuilder在原始Message之外预留了50个字符,如果可变参数加起来长过50字符还是得复制扩容......而且StringBuilder也没有重用。

 

8. 小结

StringBuilder默认的写法,会为129长度的字符串拼接,合共申请625字符的数组。所以高性能的场景下,永远要考虑用一个ThreadLocal 可重用的StringBuilder。而且重用之后,就不用再玩猜长度的游戏了。当然,如果字符串只有一百几十字节,也不一定要考虑重用,设好初始值就好。

另一份Java应用调优指南之-前菜

默认分类admin 发表了文章 • 0 个评论 • 115 次浏览 • 2017-01-17 11:46 • 来自相关话题

原文链接:http://calvin1978.blogcn.com/articles/perf-tunning-1.html
 
每一次成功的调优,都会诞生又一份的调优指南。

一些必须写在前面的军规,虽然与Java应用的调优没直接关联,但是测试同学经常不留神的地方,导致应用的优化变成一场测试环境调优的戏码。
 

1 独占你的测试机器

包括跑JMeter的那些机器。

"top"或者"pidstat 1" 看一下,其他的路人甲乙丙丁的应用都关干净了没。

如果是云主机,确保更多的占有宿主机的资源,比如深夜大家下班了你在家连VPN回来跑。

 

2 了解你的测试机器

必须完完全全的了解你的机器,才知道有没卡在某个瓶颈,或者与线上环境、其他测试结果的比较。

还是那句, 包括跑JMeter的那些机器。

 

2.1 CPU

"cat /proc/cpuinfo", 看最后一条就好,比如

 

processor : 23
model name : Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz
physical id : 1
cpu cores : 6

所有数字都从零开始,physical id:1即两颗cpu, cpu core: 6即6核,processor : 23即24个处理器。

 

2 CPU * 6 Core * 2HT(Intel超线程技术) = 24 Processor

不过也有很多同事喜欢说24核,也懒得纠正了。

 

2.2 内存

"free -g" 没什么好说的。

 

2.3 硬盘

查看大小、分区、文件系统类型: "df -hT"
硬盘是否SCSI:/dev/sdX就是scsi的,hdX就是普通的。
硬盘是否SSD : "cat /sys/block/sda/queue/rotational", 0是SSD,1是传统硬盘,但也不一定灵

普通硬盘的写速度大概100M/s,RAID级别的查看不方便,SSD的速度也不定,所以用dd测一下最靠谱:

 

dd if=/dev/zero of=dd.file bs=8k count=128k conv=fdatasync
dd if=/dev/zero of=./dd.file bs=1G count=1 conv=fdatasync

上面命令测试了分别以每次8k和1g的大小,写入1g文件的速度。

if:输入文件名, /dev/zero 设备无穷尽地提供0
of:输出文件名
bs:块大小
count:次数
conv=fdatasync :实际写盘,而不是写入Page Cache

硬盘读速度的测试同理,不过要先清理缓存,否则直接从Page Cache读了。

 

sh -c "sync && echo 3 > /proc/sys/vm/drop_caches”
dd if=./dd.file of=/dev/null bs=8k

 

2.4 网卡

先用ifconfig看看有多少块网卡和bonding。bonding是个很棒的东西,可以把多块网卡绑起来,突破单块网卡的带宽限制。

然后检查每块网卡的速度,比如"ethtool eth0"。

再检查bonding,比如"cat /proc/net/bonding/bond0", 留意其Bonding Mode是负载均衡的,再留意其捆绑的网卡的速度。

最后检查测试客户机与服务机之间的带宽,先简单ping或traceroute 一下得到RTT时间,iperf之类的可稍后。

 

2.5 操作系统

Linux的内核版本,是否64位: "uname -a"
Redhat/CentOS版本 : "cat /etc/redhat-release"

 

3. 布置好你的机器状态采集工具

实时观察的,我喜欢dstat,详见《从dstat理解Linux性能监控体系》比vmstat,iostat, sar们都好用,起码对得够齐,单位能自动转换。不过dstat需要安装(yum install dstat,或者去它的网站下载解压即用版)

dstat -tamp:推荐,打印时间戳,比默认打印多一个memory信息,一个有否进程在block状态的信息
dstat -tamN bond0,lo: 如果有bonding,dstat会把bond0和eth0 有时会算双份,还有lo的也算到总量里,所以先用-N指定网卡检查下。

参考资料:后台性能测试不可不知的二三事

 

4. JMeter的调优顶一半的事

JMeter的版本越新越好。

 

4.1 JMeter的JVM参数

它默认连个垃圾收集算法都没有配,对延时要求高的,必须配上CMS或G1,内存也整大点降低GC的频率。其他的,给Server配的啥参数,给JMeter也来上一份。如果想动态改的,不写死在脚本里,可以配置环境变量$JVM_ARGS

 

4.2 测试计划的编写

什么if 语句,以及所有其实用动态语言来实现的都挺慢的。
xPath抽取结果集里的字段之类看着就慢的也别写了。
别加任何监听器和图形。
再配置输出日志的格式,能不要的列都别要了,最极端的其实就延时这列有用。

 

4.3 JMeter的运行

在Linux上用命令行跑,别偷懒用Window开着界面跑。
别开超过200条线程(虚拟机上更少)。
可以在不同机器上起多个JMeter,用集群汇总的模式。

 

4.4 结果的统计

初始连接,Server端热身,JVM编译热点方法等都需要时间,所以建议统计前删掉前面的一些日志。

要配置一下才能看到99.9%, 99.99% 分位数的延时,另外因为之前输出日志时省略了很多列,导入日志的时候配置也要如此。

但如果不能XWindows Forward,还要把日志下载回来再导入本地的JMeter,那还不如自己动动手,用sed, awk, sort配合一下自己写个分析延时的脚本直接在服务器上运行。

 

5. 其他被依赖的节点

比如所依赖的数据库够不够快,Restful API的模拟器够不够快,负载均衡器如HAProxy优化配置了没有。

  查看全部
原文链接:http://calvin1978.blogcn.com/articles/perf-tunning-1.html
 
每一次成功的调优,都会诞生又一份的调优指南。

一些必须写在前面的军规,虽然与Java应用的调优没直接关联,但是测试同学经常不留神的地方,导致应用的优化变成一场测试环境调优的戏码。
 

1 独占你的测试机器

包括跑JMeter的那些机器。

"top"或者"pidstat 1" 看一下,其他的路人甲乙丙丁的应用都关干净了没。

如果是云主机,确保更多的占有宿主机的资源,比如深夜大家下班了你在家连VPN回来跑。

 

2 了解你的测试机器

必须完完全全的了解你的机器,才知道有没卡在某个瓶颈,或者与线上环境、其他测试结果的比较。

还是那句, 包括跑JMeter的那些机器。

 

2.1 CPU

"cat /proc/cpuinfo", 看最后一条就好,比如

 

processor : 23
model name : Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz
physical id : 1
cpu cores : 6

所有数字都从零开始,physical id:1即两颗cpu, cpu core: 6即6核,processor : 23即24个处理器。

 

2 CPU * 6 Core * 2HT(Intel超线程技术) = 24 Processor

不过也有很多同事喜欢说24核,也懒得纠正了。

 

2.2 内存

"free -g" 没什么好说的。

 

2.3 硬盘

查看大小、分区、文件系统类型: "df -hT"
硬盘是否SCSI:/dev/sdX就是scsi的,hdX就是普通的。
硬盘是否SSD : "cat /sys/block/sda/queue/rotational", 0是SSD,1是传统硬盘,但也不一定灵

普通硬盘的写速度大概100M/s,RAID级别的查看不方便,SSD的速度也不定,所以用dd测一下最靠谱:

 

dd if=/dev/zero of=dd.file bs=8k count=128k conv=fdatasync
dd if=/dev/zero of=./dd.file bs=1G count=1 conv=fdatasync

上面命令测试了分别以每次8k和1g的大小,写入1g文件的速度。

if:输入文件名, /dev/zero 设备无穷尽地提供0
of:输出文件名
bs:块大小
count:次数
conv=fdatasync :实际写盘,而不是写入Page Cache

硬盘读速度的测试同理,不过要先清理缓存,否则直接从Page Cache读了。

 

sh -c "sync && echo 3 > /proc/sys/vm/drop_caches”
dd if=./dd.file of=/dev/null bs=8k

 

2.4 网卡

先用ifconfig看看有多少块网卡和bonding。bonding是个很棒的东西,可以把多块网卡绑起来,突破单块网卡的带宽限制。

然后检查每块网卡的速度,比如"ethtool eth0"。

再检查bonding,比如"cat /proc/net/bonding/bond0", 留意其Bonding Mode是负载均衡的,再留意其捆绑的网卡的速度。

最后检查测试客户机与服务机之间的带宽,先简单ping或traceroute 一下得到RTT时间,iperf之类的可稍后。

 

2.5 操作系统

Linux的内核版本,是否64位: "uname -a"
Redhat/CentOS版本 : "cat /etc/redhat-release"

 

3. 布置好你的机器状态采集工具

实时观察的,我喜欢dstat,详见《从dstat理解Linux性能监控体系》比vmstat,iostat, sar们都好用,起码对得够齐,单位能自动转换。不过dstat需要安装(yum install dstat,或者去它的网站下载解压即用版)

dstat -tamp:推荐,打印时间戳,比默认打印多一个memory信息,一个有否进程在block状态的信息
dstat -tamN bond0,lo: 如果有bonding,dstat会把bond0和eth0 有时会算双份,还有lo的也算到总量里,所以先用-N指定网卡检查下。

参考资料:后台性能测试不可不知的二三事

 

4. JMeter的调优顶一半的事

JMeter的版本越新越好。

 

4.1 JMeter的JVM参数

它默认连个垃圾收集算法都没有配,对延时要求高的,必须配上CMS或G1,内存也整大点降低GC的频率。其他的,给Server配的啥参数,给JMeter也来上一份。如果想动态改的,不写死在脚本里,可以配置环境变量$JVM_ARGS

 

4.2 测试计划的编写

什么if 语句,以及所有其实用动态语言来实现的都挺慢的。
xPath抽取结果集里的字段之类看着就慢的也别写了。
别加任何监听器和图形。
再配置输出日志的格式,能不要的列都别要了,最极端的其实就延时这列有用。

 

4.3 JMeter的运行

在Linux上用命令行跑,别偷懒用Window开着界面跑。
别开超过200条线程(虚拟机上更少)。
可以在不同机器上起多个JMeter,用集群汇总的模式。

 

4.4 结果的统计

初始连接,Server端热身,JVM编译热点方法等都需要时间,所以建议统计前删掉前面的一些日志。

要配置一下才能看到99.9%, 99.99% 分位数的延时,另外因为之前输出日志时省略了很多列,导入日志的时候配置也要如此。

但如果不能XWindows Forward,还要把日志下载回来再导入本地的JMeter,那还不如自己动动手,用sed, awk, sort配合一下自己写个分析延时的脚本直接在服务器上运行。

 

5. 其他被依赖的节点

比如所依赖的数据库够不够快,Restful API的模拟器够不够快,负载均衡器如HAProxy优化配置了没有。

 

另一份Java应用调优指南之-工具篇

默认分类admin 发表了文章 • 0 个评论 • 154 次浏览 • 2017-01-17 11:39 • 来自相关话题

Java应用的调优,再不写都要忘光了,先对付着写完,免费的JMC真的好用,大家越早用上越好。

前一篇是三个月前的 另一份Java应用调优指南 - 前菜

 

1. 土法调优两大件

先忆苦思甜,一般人在没有Profile工具的时候,调优的两大件,无非Heap Dump 与 Thread Dump。

 

1.1 Heap Dump

 

jmap -dump:live,format=b,file=heap.hprof pid

从安全点日志看,从Heap Dump开始,整个JVM都是停顿的,考虑到IO(虽是写到Page Cache,但或许会遇到background flush),几G的Heap可能产生几秒的停顿,在生产环境上执行时谨慎再谨慎。

live的选项,实际上是产生一次Full GC来保证只看还存活的对象。有时候也会故意不加live选项,看历史对象。

Dump出来的文件建议用JDK自带的VisualVM或Eclipse的MAT插件打开,对象的大小有两种统计方式:

本身大小(Shallow Size):对象本来的大小。
保留大小(Retained Size): 当前对象大小 + 当前对象直接或间接引用到的对象的大小总和。

看本身大小时,占大头的都是char[] ,byte[]之类的,没什么意思(用jmap -histo:live pid 看的也是本身大小)。所以需要关心的是保留大小比较大的对象,看谁在引用这些char[], byte[]。

(MAT能看的信息更多,但VisualVM胜在JVM自带,用法如下:命令行输入jvisualvm,文件->装入->堆Dump->检查 -> 查找20保留大小最大的对象,就会触发保留大小的计算,然后就可以类视图里浏览,按保留大小排序了)
 

1.2 Thread Dump

ThreadDump 同样会造成JVM停顿,在生产系统上执行要谨慎。

可以命令行方式执行它,"jstack pid” 或"jstack -l pid" ,其中-l 会同时打印各种lock,但会使得JVM停顿得长久得多(可能会差很多倍,比如普通的jstack可能几毫秒和一次GC没区别,加了-l 就是近一秒的时间),-l 建议不要用。

另一种是直接用代码来打印,比如线程池满了无法添加新任务,在开发或性能测试模式下,可以在代码里捕捉该异常后直接把当前线程池的情况打印出来。

 

ThreadMXBean threadMBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMBean.dumpAllThreads(false, false);

同样注意,这里threadMBean.dumpAllThreads(false,false)的参数为false,把参数改为true,则打印synchronizers与monitor,同样使得JVM停顿久很多。

线程状态:

RUNNABLE: 运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。
BLOCKED:被某个锁(synchronizers)給block住了。
WAITING:等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。
TIME_WAITING:和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。

分析工具:

IBM Thread and Monitor Dump Analyze for Java 一个小巧的Jar包,能方便的按状态,线程名称,线程停留的函数排序,快速浏览。
http://spotify.github.io/threaddump-analyzer Spotify提供的Web版在线分析工具,可以将锁或条件相关联的线程聚合到一起。

 

2. 你真正要的Java Mission Control

如果你使用过JProfiler,Yourkit,VisualVM还有Eclipse的Profiler插件等一堆Profiler工具,或者用JavaSimion等在代码里打印过metrics,最后会发现免费的JMC才是你想要的。
 

2.1 优点

代替收费的JProfiler的好东西,以前Bea JRockit的宝贝,现在随着JDK7 up40以后的版本免费自带了,不过只在开发环境免费,就是说理论上不能拿它来连生产环境的机器。

另一个让人开心的事情就是JMC采用采样,而不是传统的代码植入的技术,对应用性能的影响非常非常小,完全可以开着JMC来做压测(唯一影响可能是full gc多了,减少一些监控项看看)。不会像以前,开了代码植入型的Profiler,出来的性能测试结果差了一个数量级不说,热点完全可能是错误的,这是一个真实的故事,具体细节就不说了。
 

2.2 功能

JMC里可以看的东西太多了,自己觉得最有用的如下:

内存Tab:分配Tab里的按类、按线程、对象的创建调用栈来归类的对象创建情况,让对象创建无处躲藏。
内存Tab:GC Tab的GC详细情况,以及TLAB外的分配情况(每条线程在Heap里分了一个Thread Local Area,在TLAB里的内存分配不需要线程竞争,所以TLAB之外的分配是不好的)
代码Tab:热点方法类及它的调用栈,超有用的功能。调用树是从线程角度看的方法调用,而按包名分类可以看3PP包的问题。
线程Tab:热点线程,再换个姿势来看热点方法和调用树。
线程Tab:争用,等待时间,锁定实例等。

 

2.3 使用方法简述

JDK7在启动服务时加上-XX:+UnlockCommercialFeatures -XX:+FlightRecorder 。
如果是远程服务器,要开JMX:

 

“-Dcom.sun.management.jmxremote.port=7001 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=your ip”

JDK自带的jmc命令,文件->连接->设定JMX连接,启动飞行纪录,固定时间选1分钟或更多,事件设置选为profiling,然后进一步修改,自己查看下都Profile了哪些信息,觉得不够的再添加些(下次就直接用上次设定就好了),比如:

加上对象数量的统计:Java Virtual Machine->GC->Detail->Object Count/Object Count after GC
方法调用采样的间隔从10ms改为1ms(但不能低于1ms,否则会影响性能了): Java Virtual Machine->Profiling下的两个选项
Socket与File采样, 10ms太久,但即使改为1ms也未必能抓住什么,可以干脆取消掉: Java Application->File Read/FileWrite/Socket Read/Socket Write

然后就开始Profile,到时间后Profile结束,会自动把记录下载回来,在JMC中展示。

其他资料:

java-performance.info上的介绍文章
JMC作者的博客
JMC官网

 

3. BTrace

神器,在生产环境上,动态监控某些方法的执行时长及其他信息,不再需要自己手工打日志,发版本,部署与重启服务。

据说淘宝就是经常开着BTrace在线上找问题的,我们最近也在生产上试了几把,太爽利了。

使用方法网上一搜大把,就不重复了。

原理就是自己写一个类似AspectJ的,希望监控哪个方法,监控后做什么动作的脚本,然后动态执行btrace命令将这个脚本attach到某个JVM上就行

 
这是一篇严肃的文章,就不配图了。 查看全部
Java应用的调优,再不写都要忘光了,先对付着写完,免费的JMC真的好用,大家越早用上越好。

前一篇是三个月前的 另一份Java应用调优指南 - 前菜

 

1. 土法调优两大件

先忆苦思甜,一般人在没有Profile工具的时候,调优的两大件,无非Heap Dump 与 Thread Dump。

 

1.1 Heap Dump

 

jmap -dump:live,format=b,file=heap.hprof pid

从安全点日志看,从Heap Dump开始,整个JVM都是停顿的,考虑到IO(虽是写到Page Cache,但或许会遇到background flush),几G的Heap可能产生几秒的停顿,在生产环境上执行时谨慎再谨慎。

live的选项,实际上是产生一次Full GC来保证只看还存活的对象。有时候也会故意不加live选项,看历史对象。

Dump出来的文件建议用JDK自带的VisualVM或Eclipse的MAT插件打开,对象的大小有两种统计方式:

本身大小(Shallow Size):对象本来的大小。
保留大小(Retained Size): 当前对象大小 + 当前对象直接或间接引用到的对象的大小总和。

看本身大小时,占大头的都是char[] ,byte[]之类的,没什么意思(用jmap -histo:live pid 看的也是本身大小)。所以需要关心的是保留大小比较大的对象,看谁在引用这些char[], byte[]。

(MAT能看的信息更多,但VisualVM胜在JVM自带,用法如下:命令行输入jvisualvm,文件->装入->堆Dump->检查 -> 查找20保留大小最大的对象,就会触发保留大小的计算,然后就可以类视图里浏览,按保留大小排序了)
 

1.2 Thread Dump

ThreadDump 同样会造成JVM停顿,在生产系统上执行要谨慎。

可以命令行方式执行它,"jstack pid” 或"jstack -l pid" ,其中-l 会同时打印各种lock,但会使得JVM停顿得长久得多(可能会差很多倍,比如普通的jstack可能几毫秒和一次GC没区别,加了-l 就是近一秒的时间),-l 建议不要用。

另一种是直接用代码来打印,比如线程池满了无法添加新任务,在开发或性能测试模式下,可以在代码里捕捉该异常后直接把当前线程池的情况打印出来。

 

ThreadMXBean threadMBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMBean.dumpAllThreads(false, false);

同样注意,这里threadMBean.dumpAllThreads(false,false)的参数为false,把参数改为true,则打印synchronizers与monitor,同样使得JVM停顿久很多。

线程状态:

RUNNABLE: 运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。
BLOCKED:被某个锁(synchronizers)給block住了。
WAITING:等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。
TIME_WAITING:和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。

分析工具:

IBM Thread and Monitor Dump Analyze for Java 一个小巧的Jar包,能方便的按状态,线程名称,线程停留的函数排序,快速浏览。
http://spotify.github.io/threaddump-analyzer Spotify提供的Web版在线分析工具,可以将锁或条件相关联的线程聚合到一起。

 

2. 你真正要的Java Mission Control

如果你使用过JProfiler,Yourkit,VisualVM还有Eclipse的Profiler插件等一堆Profiler工具,或者用JavaSimion等在代码里打印过metrics,最后会发现免费的JMC才是你想要的。
 

2.1 优点

代替收费的JProfiler的好东西,以前Bea JRockit的宝贝,现在随着JDK7 up40以后的版本免费自带了,不过只在开发环境免费,就是说理论上不能拿它来连生产环境的机器。

另一个让人开心的事情就是JMC采用采样,而不是传统的代码植入的技术,对应用性能的影响非常非常小,完全可以开着JMC来做压测(唯一影响可能是full gc多了,减少一些监控项看看)。不会像以前,开了代码植入型的Profiler,出来的性能测试结果差了一个数量级不说,热点完全可能是错误的,这是一个真实的故事,具体细节就不说了。
 

2.2 功能

JMC里可以看的东西太多了,自己觉得最有用的如下:

内存Tab:分配Tab里的按类、按线程、对象的创建调用栈来归类的对象创建情况,让对象创建无处躲藏。
内存Tab:GC Tab的GC详细情况,以及TLAB外的分配情况(每条线程在Heap里分了一个Thread Local Area,在TLAB里的内存分配不需要线程竞争,所以TLAB之外的分配是不好的)
代码Tab:热点方法类及它的调用栈,超有用的功能。调用树是从线程角度看的方法调用,而按包名分类可以看3PP包的问题。
线程Tab:热点线程,再换个姿势来看热点方法和调用树。
线程Tab:争用,等待时间,锁定实例等。

 

2.3 使用方法简述

JDK7在启动服务时加上-XX:+UnlockCommercialFeatures -XX:+FlightRecorder 。
如果是远程服务器,要开JMX:

 

“-Dcom.sun.management.jmxremote.port=7001 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=your ip”

JDK自带的jmc命令,文件->连接->设定JMX连接,启动飞行纪录,固定时间选1分钟或更多,事件设置选为profiling,然后进一步修改,自己查看下都Profile了哪些信息,觉得不够的再添加些(下次就直接用上次设定就好了),比如:

加上对象数量的统计:Java Virtual Machine->GC->Detail->Object Count/Object Count after GC
方法调用采样的间隔从10ms改为1ms(但不能低于1ms,否则会影响性能了): Java Virtual Machine->Profiling下的两个选项
Socket与File采样, 10ms太久,但即使改为1ms也未必能抓住什么,可以干脆取消掉: Java Application->File Read/FileWrite/Socket Read/Socket Write

然后就开始Profile,到时间后Profile结束,会自动把记录下载回来,在JMC中展示。

其他资料:

java-performance.info上的介绍文章
JMC作者的博客
JMC官网

 

3. BTrace

神器,在生产环境上,动态监控某些方法的执行时长及其他信息,不再需要自己手工打日志,发版本,部署与重启服务。

据说淘宝就是经常开着BTrace在线上找问题的,我们最近也在生产上试了几把,太爽利了。

使用方法网上一搜大把,就不重复了。

原理就是自己写一个类似AspectJ的,希望监控哪个方法,监控后做什么动作的脚本,然后动态执行btrace命令将这个脚本attach到某个JVM上就行

 
这是一篇严肃的文章,就不配图了。

Tomcat线程池,更符合大家想象的可扩展线程池

默认分类admin 发表了文章 • 0 个评论 • 127 次浏览 • 2017-01-17 11:35 • 来自相关话题

原文链接:http://calvin1978.blogcn.com/a ... .html
 因由

说起线程池,大家可能受连接池的印象影响,天然的认为,它应该是一开始有core条线程,忙不过来了就扩展到max条线程,闲的时候又回落到core条线程,如果还有更高的高峰,就放进一个缓冲队列里缓冲一下。

有些整天只和SSH打交道的同学,可能现在还是这样认为的。

无情的现实就是,JDK只有两种典型的线程池,FixedPool 与 CachedPool:

FixedPool固定线程数,忙不过来的全放到无限长的缓冲队列里。
CachedPool,忙不过来时无限的增加临时线程,闲时回落,没有缓冲队列。

在Java ThreadPool的正确打开方式里,建议了如何设置,避免上面两句吓人的“无限”。但无论怎么配,都无法用现成的东西,配出文章一开始的想象图来。

但不得不说,这幅想象图还是比较美好的,特别对于偶有卡顿,偶有不可预测高峰的业务线程池来说。

当然,也有人说请求积压在最后的缓冲队列里不好控制,看具体业务场景了,缓冲队列也有不同的玩法(后详)。

 

原理

我们的同事老王,研究了一番ThreadPool的机制后,提出了自己实现队列的方式。碰巧,Tomcat也正是这么做的,比起Jetty完全自己写线程池,Tomcat基于JDK的线程池稍作定制,要斯文一些。

JDK线程池的逻辑很简单( 更详细描述还是见Java ThreadPool的正确打开方式 )

- 前core个请求,来一个请求就创建一个线程。
- 之后,把请求插入缓冲队列里让所有的线程去抢;如果插入失败则创建新线程。
- 如果达到max条线程了,抛出拒绝异常。

貌似控制的枢纽都在第2句那里--队列插入的结果。JDK也是通过使用LinkedBlockingQueue 与 特殊的SynchronousQueue,实现自己的控制效果。

那我可不可以自己封装一个Queue,在插入时增加以下逻辑呢?

如果当前有空闲线程等待接客,则把任务加入队列让孩儿们去抢。
如果没有空闲的了,总线程数又没到达max,那就返回false,让Executor去创建线程。
如果总线程数已达到max,则继续把任务加入队列缓冲一下。
如果缓冲队列也满了,抛出拒绝异常。

说白了就是这么简单。

 

Tomcat的实现

Tomcat的TaskQueue实现:

 

public class TaskQueue extends LinkedBlockingQueue {

@Override

public boolean offer(Runnable o) {

if (parent . getPoolSize() == parent . getMaximumPoolSize()) return super . offer(o);


if (parent . getSubmittedCount() < parent.getPoolSize()) return super . offer(o);


if (parent . getPoolSize() < parent . getMaximumPoolSize()) return false;


return super.offer(o);


}

}

非常简单的代码,唯一要说明的是,如何判断当前有没有空闲的线程等待接客。JDK的CachedPool是靠特殊的零长度的SynchronousQueue实现。而Tomcat则靠扩展Executor ,增加一个当前请求数的计数器,在execute()方法前加1,再重载afterExecute()方法减1,然后判断当前线程总数是否大于当前请求总数就知道有咩有围观群众。

 

更进一步

因为相信Tomcat这种百年老店,我们就不自己写这个池了,把Tomcat实现里一些无关需求剥掉即用。

但Tomcat就完美了吗?

首先,TaskQueue的offer()里,调用了executor.getPoolSize(),这是个有锁的函数,这是最遗憾的地方,在大家都在嫌线程池里一条队列锁得太厉害,像ForkJoinPool或Netty的设计都是一个线程一个队列时,这个有锁的函数相当碍眼。而且,最过分的是,Tomcat居然一口气调了三次(在Tomcat9 M9依然如此)。反复看了下,不求那么精准的话貌似一次就够了,真的有并发的变化的情况,executor里还有个处理RejectException,把任务重新放回队列的保险。

最后,说说缓冲队列的两种玩法:

一种是队列相对比较长,比如4096,主线程把任务丢进去就立刻返回了,如果队列满了就直接报拒绝异常。

一种是队列相对比较短的,比如512,如果满了,主线程就以queue.force(command, timeout)等在那里等队列有空,等到超时才报拒绝异常。

Tomcat的机制支持这两种玩法,自己设置就好。

 
文章可能还要修改,转载请保留原文链接,否则视为侵权:
http://calvin1978.blogcn.com/a ... .html 查看全部
原文链接:http://calvin1978.blogcn.com/a ... .html
 因由

说起线程池,大家可能受连接池的印象影响,天然的认为,它应该是一开始有core条线程,忙不过来了就扩展到max条线程,闲的时候又回落到core条线程,如果还有更高的高峰,就放进一个缓冲队列里缓冲一下。

有些整天只和SSH打交道的同学,可能现在还是这样认为的。

无情的现实就是,JDK只有两种典型的线程池,FixedPool 与 CachedPool:

FixedPool固定线程数,忙不过来的全放到无限长的缓冲队列里。
CachedPool,忙不过来时无限的增加临时线程,闲时回落,没有缓冲队列。

在Java ThreadPool的正确打开方式里,建议了如何设置,避免上面两句吓人的“无限”。但无论怎么配,都无法用现成的东西,配出文章一开始的想象图来。

但不得不说,这幅想象图还是比较美好的,特别对于偶有卡顿,偶有不可预测高峰的业务线程池来说。

当然,也有人说请求积压在最后的缓冲队列里不好控制,看具体业务场景了,缓冲队列也有不同的玩法(后详)。

 

原理

我们的同事老王,研究了一番ThreadPool的机制后,提出了自己实现队列的方式。碰巧,Tomcat也正是这么做的,比起Jetty完全自己写线程池,Tomcat基于JDK的线程池稍作定制,要斯文一些。

JDK线程池的逻辑很简单( 更详细描述还是见Java ThreadPool的正确打开方式 )

- 前core个请求,来一个请求就创建一个线程。
- 之后,把请求插入缓冲队列里让所有的线程去抢;如果插入失败则创建新线程。
- 如果达到max条线程了,抛出拒绝异常。

貌似控制的枢纽都在第2句那里--队列插入的结果。JDK也是通过使用LinkedBlockingQueue 与 特殊的SynchronousQueue,实现自己的控制效果。

那我可不可以自己封装一个Queue,在插入时增加以下逻辑呢?

如果当前有空闲线程等待接客,则把任务加入队列让孩儿们去抢。
如果没有空闲的了,总线程数又没到达max,那就返回false,让Executor去创建线程。
如果总线程数已达到max,则继续把任务加入队列缓冲一下。
如果缓冲队列也满了,抛出拒绝异常。

说白了就是这么简单。

 

Tomcat的实现

Tomcat的TaskQueue实现:

 

public class TaskQueue extends LinkedBlockingQueue {

@Override

public boolean offer(Runnable o) {

if (parent . getPoolSize() == parent . getMaximumPoolSize()) return super . offer(o);


if (parent . getSubmittedCount() < parent.getPoolSize()) return super . offer(o);


if (parent . getPoolSize() < parent . getMaximumPoolSize()) return false;


return super.offer(o);


}

}

非常简单的代码,唯一要说明的是,如何判断当前有没有空闲的线程等待接客。JDK的CachedPool是靠特殊的零长度的SynchronousQueue实现。而Tomcat则靠扩展Executor ,增加一个当前请求数的计数器,在execute()方法前加1,再重载afterExecute()方法减1,然后判断当前线程总数是否大于当前请求总数就知道有咩有围观群众。

 

更进一步

因为相信Tomcat这种百年老店,我们就不自己写这个池了,把Tomcat实现里一些无关需求剥掉即用。

但Tomcat就完美了吗?

首先,TaskQueue的offer()里,调用了executor.getPoolSize(),这是个有锁的函数,这是最遗憾的地方,在大家都在嫌线程池里一条队列锁得太厉害,像ForkJoinPool或Netty的设计都是一个线程一个队列时,这个有锁的函数相当碍眼。而且,最过分的是,Tomcat居然一口气调了三次(在Tomcat9 M9依然如此)。反复看了下,不求那么精准的话貌似一次就够了,真的有并发的变化的情况,executor里还有个处理RejectException,把任务重新放回队列的保险。

最后,说说缓冲队列的两种玩法:

一种是队列相对比较长,比如4096,主线程把任务丢进去就立刻返回了,如果队列满了就直接报拒绝异常。

一种是队列相对比较短的,比如512,如果满了,主线程就以queue.force(command, timeout)等在那里等队列有空,等到超时才报拒绝异常。

Tomcat的机制支持这两种玩法,自己设置就好。

 
文章可能还要修改,转载请保留原文链接,否则视为侵权:
http://calvin1978.blogcn.com/a ... .html

Java应用调优之-总览导航

默认分类admin 发表了文章 • 0 个评论 • 141 次浏览 • 2017-01-17 11:34 • 来自相关话题

原文链接:http://calvin1978.blogcn.com/a ... .html
 1. 目标

调优之前,先认清自己的目标。

1. QPS
地球人都懂的指标。

2. 平均值,中值响应时间
其实,中值比平均值更好,因为平均值可能被少量极高的最高值拉高,而中值是所有结果值里位置排在中间的那个值。

3. 99 . 99%响应时间
99. 99%延时=30ms的意思就是,99 . 99%的请求,都能在30ms毫秒以内完成。
对于服务SLA的评估,毛刺率也很重要,一个服务可能平均响应时间是2ms,但遇上GC等Java绕不过去的坎,20ms也可以接受,但如果是200ms,就要找原因了。

4. CPU消耗
CPU能省就省,特别是docker时代,省了自己就方便了别人。
但CPU也要能压上去,否则就是哪里有锁了。

5. 内存、网络IO、磁盘IO消耗
特定场景特定需求。
 

2. 为什么说调优是艺术

说调优是艺术,因为它源于深厚的知识,丰富的经验和敏锐的直觉 -《Java性能权威指南》
所有网上的文章,都只能拓展你知识的深度。

《认清性能问题》 by 瞬息之间翻译

 

3. 文章一览
3.1 基本的准备

另一份Java应用调优指南之-工具篇
另一份Java应用调优指南 - 前菜
从dstat理解Linux性能监控体系
AWK处理日志入门
从Apache Kafka 重温文件高效读写

 

3.2 面向GC编程

StringBuilder在高性能场景下的正确用法
欠帐1:通用的节约内存的方法,《面向GC的Java编程珠玉》在前,再补充一些

 

3.3 并发与锁

欠账2:锁优化的一般技巧 -- 分散锁,缩短锁,读写锁,ThreadLocal,CMS
Tomcat线程池,更符合大家想象的可扩展线程池
Java ThreadPool的正确打开方式

 

3.4 JVM优化

关键业务系统的JVM启动参数推荐 2.0版
JVM的Stop The World,安全点,黑暗的地底世界
在你的代码之外,响应时间超时的三个追查方向(上)
在你的代码之外,响应时间超时的三个追查方向(下)
欠帐4: JIT编译优化,内联

 

3.5 其他技巧

Java ThreadPool的正确打开方式
Tomcat线程池,更符合大家想象的可扩展线程池
陌生但默默一统江湖的MurmurHash
高性能场景下,HashMap的优化使用建议
估计长期欠帐5: 静态异常,其他小技巧

 

4. 资料
4.1 书籍

《Java性能权威指南》
比起多年前那部调优圣经,讲得更加深入,也更加贴近现在的JDK。

《深入理解 Java 虚拟机 第2版》
理解虚拟机并不是那么难,Java程序员来说,很多知识其实是必须的。另外还有几本类似主题的书,忽然一下子都出来了。

《大话Java性能优化》
把书列于此不代表我完全同意书中的所有观点,仅供参考,辩证阅读。 查看全部
原文链接:http://calvin1978.blogcn.com/a ... .html
 1. 目标

调优之前,先认清自己的目标。

1. QPS
地球人都懂的指标。

2. 平均值,中值响应时间
其实,中值比平均值更好,因为平均值可能被少量极高的最高值拉高,而中值是所有结果值里位置排在中间的那个值。

3. 99 . 99%响应时间
99. 99%延时=30ms的意思就是,99 . 99%的请求,都能在30ms毫秒以内完成。
对于服务SLA的评估,毛刺率也很重要,一个服务可能平均响应时间是2ms,但遇上GC等Java绕不过去的坎,20ms也可以接受,但如果是200ms,就要找原因了。

4. CPU消耗
CPU能省就省,特别是docker时代,省了自己就方便了别人。
但CPU也要能压上去,否则就是哪里有锁了。

5. 内存、网络IO、磁盘IO消耗
特定场景特定需求。
 

2. 为什么说调优是艺术

说调优是艺术,因为它源于深厚的知识,丰富的经验和敏锐的直觉 -《Java性能权威指南》
所有网上的文章,都只能拓展你知识的深度。

《认清性能问题》 by 瞬息之间翻译

 

3. 文章一览
3.1 基本的准备

另一份Java应用调优指南之-工具篇
另一份Java应用调优指南 - 前菜
从dstat理解Linux性能监控体系
AWK处理日志入门
从Apache Kafka 重温文件高效读写

 

3.2 面向GC编程

StringBuilder在高性能场景下的正确用法
欠帐1:通用的节约内存的方法,《面向GC的Java编程珠玉》在前,再补充一些

 

3.3 并发与锁

欠账2:锁优化的一般技巧 -- 分散锁,缩短锁,读写锁,ThreadLocal,CMS
Tomcat线程池,更符合大家想象的可扩展线程池
Java ThreadPool的正确打开方式

 

3.4 JVM优化

关键业务系统的JVM启动参数推荐 2.0版
JVM的Stop The World,安全点,黑暗的地底世界
在你的代码之外,响应时间超时的三个追查方向(上)
在你的代码之外,响应时间超时的三个追查方向(下)
欠帐4: JIT编译优化,内联

 

3.5 其他技巧

Java ThreadPool的正确打开方式
Tomcat线程池,更符合大家想象的可扩展线程池
陌生但默默一统江湖的MurmurHash
高性能场景下,HashMap的优化使用建议
估计长期欠帐5: 静态异常,其他小技巧

 

4. 资料
4.1 书籍

《Java性能权威指南》
比起多年前那部调优圣经,讲得更加深入,也更加贴近现在的JDK。

《深入理解 Java 虚拟机 第2版》
理解虚拟机并不是那么难,Java程序员来说,很多知识其实是必须的。另外还有几本类似主题的书,忽然一下子都出来了。

《大话Java性能优化》
把书列于此不代表我完全同意书中的所有观点,仅供参考,辩证阅读。

在你的代码之外,服务时延过长的三个追查方向(上)

默认分类admin 发表了文章 • 0 个评论 • 126 次浏览 • 2017-01-17 11:29 • 来自相关话题

服务化体系里一般都有着严格的超时设定,为业务部门排查那些毛刺慢响应,也是基础架构部门的专家坐诊服务之一。

有时候,即使你的代码写的很努力了,但还是会出现慢响应,因为这本就是艰难的世界。 本文从三个方向上各举一些例子:

第一方面主要是热身,更加有趣的两方面见下集。

 

第一方面,操作系统篇

准备知识: 《从Apache Kafka 重温文件高效读写》中Swap 和 PageCache的部分。

1. 禁用swap

Linux有个很怪的癖好,当内存不足时,看心情,有很大机率不是把用作IO缓存的Page Cache收回,而是把冷的应用内存page out到磁盘上(具体算法看准备知识)。当这段内存重新要被访问时,再把它重新page in回内存(所谓的主缺页错误),这个过程进程是停顿的。增长缓慢的老生代,池化的堆外内存,都可能被认为是冷内存,用 cat /proc/[pid]/status 看看 VmSwap的大小, 再dstat里看看监控page in发生的时间。

在 /etc/sysctl.conf 放入下面一句,基本可以杜绝swap。设成0会导致OOM,案例在此,有些同学就设成1,喜欢就好。

 

vm.swappiness = 10

 

2. 加快Page Cache Flush的频率

又是一个Linux自己的奇怪设置,Linux的Page Cache机制说来话长(还是看看准备知识), 简单说IO其实都默认不是先写磁盘,而是写进Page Cache内存,inode脏了30秒,或脏数据达到了10%可用内存(Free+PageCache -mmap),才开始起flusher线程写盘。

我们的生产机内存起码都剩20G的样子,想想要以普通硬盘100MB/s级别的速度,一次写2G文件的速度....好在一般都达不到这个条件,一般是由几个日志文件轮流30秒触发,一次写几百M,花个三秒左右的时间。

文章里说,后台刷盘线程不会阻塞应用程序的write(2)。但是,

应用的write 过程是这样的:
锁inode -> 锁page -> 写入page -> 解锁page -> 解锁inode -> 锁inode page -> 写inode page -> 解锁inode page

而flusher的过程是这样的:
锁page -> 将page放进IO队列,等待IO调度写盘完成返回 -> 解锁page

可见,还是有锁,IO调度器也不是绝对公平,当IO繁忙,应用还是会发生阻塞 。

我们的做法是用一个100MB的绝对值来代替可用内存百分比来作为阀值。

在 /etc/sysctl.conf 里加入

 

vm.dirty_background_bytes = 104857600

在第二篇举的JVM停顿的例子,全和IO相关,即使不做后面JVM的调优,光降低这个阀值,也能大大缓解。

当然什么值是最优,必须根据机器配置,应用特性来具体分析。

 

3. 网络参数

太多可配置的地方,可以参考阿里云团队的一个很好的文章 Linux TCP队列相关参数的总结。 还是那句,不能看着文章就开始设,必须根据自己的情况。

比如我们自己设置网卡软中断队列的CPU亲和度:

平时网卡中断可能只用一个核来响应,在大流量下那个核会跑满。
运行irqbalance,也只是用到了1个CPU,12个核。
最后自己设定24条网卡中断队列对应24个核,效果最佳。。。。。。但你的情况就不一定一样啊。 查看全部
服务化体系里一般都有着严格的超时设定,为业务部门排查那些毛刺慢响应,也是基础架构部门的专家坐诊服务之一。

有时候,即使你的代码写的很努力了,但还是会出现慢响应,因为这本就是艰难的世界。 本文从三个方向上各举一些例子:

第一方面主要是热身,更加有趣的两方面见下集。

 

第一方面,操作系统篇

准备知识: 《从Apache Kafka 重温文件高效读写》中Swap 和 PageCache的部分。

1. 禁用swap

Linux有个很怪的癖好,当内存不足时,看心情,有很大机率不是把用作IO缓存的Page Cache收回,而是把冷的应用内存page out到磁盘上(具体算法看准备知识)。当这段内存重新要被访问时,再把它重新page in回内存(所谓的主缺页错误),这个过程进程是停顿的。增长缓慢的老生代,池化的堆外内存,都可能被认为是冷内存,用 cat /proc/[pid]/status 看看 VmSwap的大小, 再dstat里看看监控page in发生的时间。

在 /etc/sysctl.conf 放入下面一句,基本可以杜绝swap。设成0会导致OOM,案例在此,有些同学就设成1,喜欢就好。

 

vm.swappiness = 10

 

2. 加快Page Cache Flush的频率

又是一个Linux自己的奇怪设置,Linux的Page Cache机制说来话长(还是看看准备知识), 简单说IO其实都默认不是先写磁盘,而是写进Page Cache内存,inode脏了30秒,或脏数据达到了10%可用内存(Free+PageCache -mmap),才开始起flusher线程写盘。

我们的生产机内存起码都剩20G的样子,想想要以普通硬盘100MB/s级别的速度,一次写2G文件的速度....好在一般都达不到这个条件,一般是由几个日志文件轮流30秒触发,一次写几百M,花个三秒左右的时间。

文章里说,后台刷盘线程不会阻塞应用程序的write(2)。但是,

应用的write 过程是这样的:
锁inode -> 锁page -> 写入page -> 解锁page -> 解锁inode -> 锁inode page -> 写inode page -> 解锁inode page

而flusher的过程是这样的:
锁page -> 将page放进IO队列,等待IO调度写盘完成返回 -> 解锁page

可见,还是有锁,IO调度器也不是绝对公平,当IO繁忙,应用还是会发生阻塞 。

我们的做法是用一个100MB的绝对值来代替可用内存百分比来作为阀值。

在 /etc/sysctl.conf 里加入

 

vm.dirty_background_bytes = 104857600

在第二篇举的JVM停顿的例子,全和IO相关,即使不做后面JVM的调优,光降低这个阀值,也能大大缓解。

当然什么值是最优,必须根据机器配置,应用特性来具体分析。

 

3. 网络参数

太多可配置的地方,可以参考阿里云团队的一个很好的文章 Linux TCP队列相关参数的总结。 还是那句,不能看着文章就开始设,必须根据自己的情况。

比如我们自己设置网卡软中断队列的CPU亲和度:

平时网卡中断可能只用一个核来响应,在大流量下那个核会跑满。
运行irqbalance,也只是用到了1个CPU,12个核。
最后自己设定24条网卡中断队列对应24个核,效果最佳。。。。。。但你的情况就不一定一样啊。

在你的代码之外,服务时延过长的三个追查方向(下)

默认分类admin 发表了文章 • 0 个评论 • 134 次浏览 • 2017-01-17 11:28 • 来自相关话题

本文含一些比较有趣的地方,所以和上一篇截开,免得大家看着swap,page cache没劲就把文章关了。

 

第二方面 : JVM篇 — 高IO波动下的JVM停顿

准备知识:JVM的Stop The World,安全点,黑暗的地底世界

在压测中非常偶然的超时,但CPU正常,网络正常,只有IO在刷盘时偶然写个几秒。那,就继续怀疑IO吧。

第一步,日志都异步写了吗?而且,异步队列满时会选择丢弃吗?

Logback在异步日志队列满时,默认并不是丢弃的而是阻塞等待的,需要自己配置。

或者干脆自己重新写一个更高效的异步Appender。

 

第二步,探究高IO 如何造成JVM停顿

有时GC日志都很正常,只有十几毫秒GC停顿,加上-XX:+PrintGCApplicationStoppedTime 后,真正的停顿才现形。

又或者像下面这样的,user+sys很低,但real很高,因为是多线程并发GC,所以real本来的时间应该是user+sys的十分之一不到的。


[ParNew: 1767244K->134466K(1887488K), 0.0331500 secs] 1767244K->134466K(3984640K), 0.0332520 secs] [Times: user=1.54 sys=0.00, real=1.04 secs]

但日志都异步了呀,JVM和IO还有什么关系?

来吧,用lsof彻底检查一下,去掉那些网络的部分,jar和so的文件

 

lsof -p $pid|grep /|grep -v ".jar"|grep -v ".so"

撇开熟悉的日志文件,发现这两个文件:


/tmp/hsperfdata_calvin/44337
/**我是马赛克**/**公司安全部门别找我**/myapp/logs/gc.log


 

第三步,GC日志

一拍脑袋,对哦,GC日志,如果它被锁住,GC的安全点就出不去哦......

但也不能不写GC日志啊,怎么办?

好在,早有知识储备,详见 Linux下tempfs及/dev/shm原理与应用。 简单说,/dev/shm 就是个默认存在,权限开放的,拿内存当硬盘用的分区,最大上限是总内存的一半,当系统内存不足时会使用swap。板着指头算了一下,由于每次JVM重启时GC日志都会被重置(所以你在启动脚本里总会先把GC日志备份下来),那以一秒写一条日志的速度,一点内存就够写几年了,于是放心的把日志指向它。

 

-Xloggc:/dev/shm/gc-myapplication.log

怎么好像从来没有人关心过GC日志被锁住的? 后来才发现,原来网上还是有Linkedin的同学关注过的:Eliminating Large JVM GC Pauses Caused by Background IO Traffic

 

第四步,hsperfdata文件

改完之后继续运行,超时虽然少了,还是有啊~~~

等等,还有一个文件什么鬼?

/tmp/hsperfdata_$user/$pid

涨知识的时刻到了。原来JVM在安全点里会将statistics写入这个文件,给jps,jstats命令用的。它本来已经是MMAP将文件映射到内存了,读写文件本不会锁,但无奈还是要更新inode元信息,在这里被锁了。在安全点里被锁。。。整个JVM就被锁了。

真不知道,JVM里原来还默默做了这事情。。。

解决办法,又把它指向/dev/shm? 可惜不行。所以,咬咬牙,把它禁止了,反正生产上都靠JMX,用不上jstats和jps。 有两种方式禁用它,据说-XX:-UsePerfData 会影响JVM本身做的优化,所以:

 

-XX:+PerfDisableSharedMem

又是哪个疑问,为什么很少听到有人说的,就我们全碰上了?其实有的,比如Cassandra就禁止了它,见JVM statistics cause garbage collection pauses

高IO可以造成各种问题,如果参考上篇,将Linux的刷盘阀值降低,可以治标不治本的缓解很多症状。
 

第三方面 : 后台辅助程序篇

把前面都都改完了,在线下压测也很棒了,在生产环境里以30ms为目标,还是会有非GC时段的超时,这是为什么呢?

再换个思路,是不是被其他程序影响了?虽然生产上每台机只会部署一个应用,但还有不少监控程序,日志收集程序在跑呀。

运行pidstat 1监控,这些程序不会吃满所有CPU啊。等等,pidstat 以秒来单位运行,见 从dstat理解Linux性能监控体系 ,那毫秒级的峰值它是监控不到的。

又到了发挥想象力乱猜的时刻,主要是看着收集日志的Flueme怎么都不顺眼,看久了忽然发现,Flueme的启动参数几乎是裸奔的,没有配什么GC参数。。。那么按ParallelGCThreads=8+( Processor - 8 ) ( 5/8 )的公式,24核的服务器,flueme 在Young GC时会有18个比较高优先级的线程在狂抢CPU,会不会是它把CPU给抢得太多了?

反正Flueme是后台收集日志的,停久一点也没所谓,所以加上-XX: ParallelGCThreads=4把它的并发GC线程数降低

参数修改前 15分钟内, 大于30ms的业务调用173次
参数修改后 246分钟内, 大于30ms业务调用 41次

应用被后台老王害了的典型例子。。。。。。。

 
本文内容薛院长亦有贡献,他基于strace的分析方法更工程化,这里就偷懒不写了。

调优路漫漫,超时还没彻底消失,比如偶然会出现SYS time较高的GC,系统又没有Swap ,未完待续。 查看全部
本文含一些比较有趣的地方,所以和上一篇截开,免得大家看着swap,page cache没劲就把文章关了。

 

第二方面 : JVM篇 — 高IO波动下的JVM停顿

准备知识:JVM的Stop The World,安全点,黑暗的地底世界

在压测中非常偶然的超时,但CPU正常,网络正常,只有IO在刷盘时偶然写个几秒。那,就继续怀疑IO吧。

第一步,日志都异步写了吗?而且,异步队列满时会选择丢弃吗?

Logback在异步日志队列满时,默认并不是丢弃的而是阻塞等待的,需要自己配置。

或者干脆自己重新写一个更高效的异步Appender。

 

第二步,探究高IO 如何造成JVM停顿

有时GC日志都很正常,只有十几毫秒GC停顿,加上-XX:+PrintGCApplicationStoppedTime 后,真正的停顿才现形。

又或者像下面这样的,user+sys很低,但real很高,因为是多线程并发GC,所以real本来的时间应该是user+sys的十分之一不到的。


[ParNew: 1767244K->134466K(1887488K), 0.0331500 secs] 1767244K->134466K(3984640K), 0.0332520 secs] [Times: user=1.54 sys=0.00, real=1.04 secs]

但日志都异步了呀,JVM和IO还有什么关系?

来吧,用lsof彻底检查一下,去掉那些网络的部分,jar和so的文件

 

lsof -p $pid|grep /|grep -v ".jar"|grep -v ".so"

撇开熟悉的日志文件,发现这两个文件:


/tmp/hsperfdata_calvin/44337
/**我是马赛克**/**公司安全部门别找我**/myapp/logs/gc.log


 

第三步,GC日志

一拍脑袋,对哦,GC日志,如果它被锁住,GC的安全点就出不去哦......

但也不能不写GC日志啊,怎么办?

好在,早有知识储备,详见 Linux下tempfs及/dev/shm原理与应用。 简单说,/dev/shm 就是个默认存在,权限开放的,拿内存当硬盘用的分区,最大上限是总内存的一半,当系统内存不足时会使用swap。板着指头算了一下,由于每次JVM重启时GC日志都会被重置(所以你在启动脚本里总会先把GC日志备份下来),那以一秒写一条日志的速度,一点内存就够写几年了,于是放心的把日志指向它。

 

-Xloggc:/dev/shm/gc-myapplication.log

怎么好像从来没有人关心过GC日志被锁住的? 后来才发现,原来网上还是有Linkedin的同学关注过的:Eliminating Large JVM GC Pauses Caused by Background IO Traffic

 

第四步,hsperfdata文件

改完之后继续运行,超时虽然少了,还是有啊~~~

等等,还有一个文件什么鬼?

/tmp/hsperfdata_$user/$pid

涨知识的时刻到了。原来JVM在安全点里会将statistics写入这个文件,给jps,jstats命令用的。它本来已经是MMAP将文件映射到内存了,读写文件本不会锁,但无奈还是要更新inode元信息,在这里被锁了。在安全点里被锁。。。整个JVM就被锁了。

真不知道,JVM里原来还默默做了这事情。。。

解决办法,又把它指向/dev/shm? 可惜不行。所以,咬咬牙,把它禁止了,反正生产上都靠JMX,用不上jstats和jps。 有两种方式禁用它,据说-XX:-UsePerfData 会影响JVM本身做的优化,所以:

 

-XX:+PerfDisableSharedMem

又是哪个疑问,为什么很少听到有人说的,就我们全碰上了?其实有的,比如Cassandra就禁止了它,见JVM statistics cause garbage collection pauses

高IO可以造成各种问题,如果参考上篇,将Linux的刷盘阀值降低,可以治标不治本的缓解很多症状。
 

第三方面 : 后台辅助程序篇

把前面都都改完了,在线下压测也很棒了,在生产环境里以30ms为目标,还是会有非GC时段的超时,这是为什么呢?

再换个思路,是不是被其他程序影响了?虽然生产上每台机只会部署一个应用,但还有不少监控程序,日志收集程序在跑呀。

运行pidstat 1监控,这些程序不会吃满所有CPU啊。等等,pidstat 以秒来单位运行,见 从dstat理解Linux性能监控体系 ,那毫秒级的峰值它是监控不到的。

又到了发挥想象力乱猜的时刻,主要是看着收集日志的Flueme怎么都不顺眼,看久了忽然发现,Flueme的启动参数几乎是裸奔的,没有配什么GC参数。。。那么按ParallelGCThreads=8+( Processor - 8 ) ( 5/8 )的公式,24核的服务器,flueme 在Young GC时会有18个比较高优先级的线程在狂抢CPU,会不会是它把CPU给抢得太多了?

反正Flueme是后台收集日志的,停久一点也没所谓,所以加上-XX: ParallelGCThreads=4把它的并发GC线程数降低

参数修改前 15分钟内, 大于30ms的业务调用173次
参数修改后 246分钟内, 大于30ms业务调用 41次

应用被后台老王害了的典型例子。。。。。。。

 
本文内容薛院长亦有贡献,他基于strace的分析方法更工程化,这里就偷懒不写了。

调优路漫漫,超时还没彻底消失,比如偶然会出现SYS time较高的GC,系统又没有Swap ,未完待续。

String.intern() 祛魅

默认分类admin 发表了文章 • 0 个评论 • 148 次浏览 • 2017-01-17 17:31 • 来自相关话题

String.intern(),写应用的同学不知道也没什么损失。但知道的同学,如果只觉得它很省内存,用==比较字符串很酷,或者期待JDK会做什么神奇的事情,然后恨不得所有地方都用上String.intern(),那很可能会误用,所以把上周在群里的讨论整理成此文。

在社会科学中,祛魅(Disenchantment)是指在现代社会中消去神秘主义(魅惑力)的表面并把文化合理化。这里乱用一下。

 

1. String.itern()的基本原理

详细可看占小狼同学的《浅谈Java String内幕(2)》

String.intern()是一个Native方法,底层调用C++的 StringTable::intern 方法,源码注释:当调用 intern 方法时,如果常量池中已经该字符串,则返回池中的字符串;否则将此字符串添加到常量池中,并返回字符串的引用。

所以明面上,它有两大好处,一是重复的字符串,会用同一个引用代替;二是字符串比较,不再需要逐个字符的equals()比较,而用==对比引用是否相同即可。

 

2. 省内存效果只对长期存在的字符串有效

String.intern()没有神奇的地方,只在字符串生成后,再去常量池里查找引用。所以字符串最初生成时所花的内存,是省不掉的。

String s = new String(bytes, “UTF-8”).intern();
String s = String.valueOf(i).intern();

只有大量对象放在长期存在的集合里,里面是大量重复的字符串,或者对象的属性是重复的字符串时,省内存的效果才显现出来。短生命周期的字符串,GC要干的活是一样的。

 

3. 执行路径上多次的==,才能抵消常量池HasHMap查找的代价

==当然比equals()快得多,但常量池其实是个HashMap,依然没有神奇的地方,依然要执行HashMap的get操作,所以,一次hashCode() 和至少一次的equals()已经预付了,如果hash冲突,那equals()次数更多。

 

4. 真的对性能影响甚微吗?

在我的服务化框架测试里,把几个Header字段intern了,性能立马从七万五调到七万一 QPS,原来从七万一升到七万五 ,曾做过多少效果甚微的优化加上一次Netty使用的优化而成,现在它掉下来倒是飞快。

PS. 七万五 20%CPU这个数字,这两周的博客里都没升过了: (

 

5. 小陷阱

来自R大的提醒, s.intern()是无效的,因为String是不变对象, String s1 = s.intern()后,这个s1才是个引用。

Java的常量池也是不省心的,要注意JDK版本,占小狼同学的《浅谈Java String内幕(2)》 查看全部
String.intern(),写应用的同学不知道也没什么损失。但知道的同学,如果只觉得它很省内存,用==比较字符串很酷,或者期待JDK会做什么神奇的事情,然后恨不得所有地方都用上String.intern(),那很可能会误用,所以把上周在群里的讨论整理成此文。

在社会科学中,祛魅(Disenchantment)是指在现代社会中消去神秘主义(魅惑力)的表面并把文化合理化。这里乱用一下。

 

1. String.itern()的基本原理

详细可看占小狼同学的《浅谈Java String内幕(2)》

String.intern()是一个Native方法,底层调用C++的 StringTable::intern 方法,源码注释:当调用 intern 方法时,如果常量池中已经该字符串,则返回池中的字符串;否则将此字符串添加到常量池中,并返回字符串的引用。

所以明面上,它有两大好处,一是重复的字符串,会用同一个引用代替;二是字符串比较,不再需要逐个字符的equals()比较,而用==对比引用是否相同即可。

 

2. 省内存效果只对长期存在的字符串有效

String.intern()没有神奇的地方,只在字符串生成后,再去常量池里查找引用。所以字符串最初生成时所花的内存,是省不掉的。

String s = new String(bytes, “UTF-8”).intern();
String s = String.valueOf(i).intern();

只有大量对象放在长期存在的集合里,里面是大量重复的字符串,或者对象的属性是重复的字符串时,省内存的效果才显现出来。短生命周期的字符串,GC要干的活是一样的。

 

3. 执行路径上多次的==,才能抵消常量池HasHMap查找的代价

==当然比equals()快得多,但常量池其实是个HashMap,依然没有神奇的地方,依然要执行HashMap的get操作,所以,一次hashCode() 和至少一次的equals()已经预付了,如果hash冲突,那equals()次数更多。

 

4. 真的对性能影响甚微吗?

在我的服务化框架测试里,把几个Header字段intern了,性能立马从七万五调到七万一 QPS,原来从七万一升到七万五 ,曾做过多少效果甚微的优化加上一次Netty使用的优化而成,现在它掉下来倒是飞快。

PS. 七万五 20%CPU这个数字,这两周的博客里都没升过了: (

 

5. 小陷阱

来自R大的提醒, s.intern()是无效的,因为String是不变对象, String s1 = s.intern()后,这个s1才是个引用。

Java的常量池也是不省心的,要注意JDK版本,占小狼同学的《浅谈Java String内幕(2)》

另一份Java应用调优指南之-前菜

默认分类admin 发表了文章 • 0 个评论 • 172 次浏览 • 2017-01-17 17:25 • 来自相关话题

每一次成功的调优,都会诞生又一份的调优指南。

一些必须写在前面的军规,虽然与Java应用的调优没直接关联,但是测试同学经常不留神的地方,导致应用的优化变成一场测试环境调优的戏码。
 

1 独占你的测试机器

包括跑JMeter的那些机器。

"top"或者"pidstat 1" 看一下,其他的路人甲乙丙丁的应用都关干净了没。

如果是云主机,确保更多的占有宿主机的资源,比如深夜大家下班了你在家连VPN回来跑。

 

2 了解你的测试机器

必须完完全全的了解你的机器,才知道有没卡在某个瓶颈,或者与线上环境、其他测试结果的比较。

还是那句, 包括跑JMeter的那些机器。

 

2.1 CPU

"cat /proc/cpuinfo", 看最后一条就好,比如

 

processor : 23
model name : Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz
physical id : 1
cpu cores : 6

所有数字都从零开始,physical id:1即两颗cpu, cpu core: 6即6核,processor : 23即24个处理器。

 

2 CPU * 6 Core * 2HT(Intel超线程技术) = 24 Processor

不过也有很多同事喜欢说24核,也懒得纠正了。

 

2.2 内存

"free -g" 没什么好说的。

 

2.3 硬盘

查看大小、分区、文件系统类型: "df -hT"
硬盘是否SCSI:/dev/sdX就是scsi的,hdX就是普通的。
硬盘是否SSD : "cat /sys/block/sda/queue/rotational", 0是SSD,1是传统硬盘,但也不一定灵

普通硬盘的写速度大概100M/s,RAID级别的查看不方便,SSD的速度也不定,所以用dd测一下最靠谱:

 

dd if=/dev/zero of=dd.file bs=8k count=128k conv=fdatasync
dd if=/dev/zero of=./dd.file bs=1G count=1 conv=fdatasync

上面命令测试了分别以每次8k和1g的大小,写入1g文件的速度。

if:输入文件名, /dev/zero 设备无穷尽地提供0
of:输出文件名
bs:块大小
count:次数
conv=fdatasync :实际写盘,而不是写入Page Cache

硬盘读速度的测试同理,不过要先清理缓存,否则直接从Page Cache读了。

 

sh -c "sync && echo 3 > /proc/sys/vm/drop_caches”
dd if=./dd.file of=/dev/null bs=8k

 

2.4 网卡

先用ifconfig看看有多少块网卡和bonding。bonding是个很棒的东西,可以把多块网卡绑起来,突破单块网卡的带宽限制。

然后检查每块网卡的速度,比如"ethtool eth0"。

再检查bonding,比如"cat /proc/net/bonding/bond0", 留意其Bonding Mode是负载均衡的,再留意其捆绑的网卡的速度。

最后检查测试客户机与服务机之间的带宽,先简单ping或traceroute 一下得到RTT时间,iperf之类的可稍后。

 

2.5 操作系统

Linux的内核版本,是否64位: "uname -a"
Redhat/CentOS版本 : "cat /etc/redhat-release"

 

3. 布置好你的机器状态采集工具

实时观察的,我喜欢dstat,详见《从dstat理解Linux性能监控体系》比vmstat,iostat, sar们都好用,起码对得够齐,单位能自动转换。不过dstat需要安装(yum install dstat,或者去它的网站下载解压即用版)

dstat -tamp:推荐,打印时间戳,比默认打印多一个memory信息,一个有否进程在block状态的信息
dstat -tamN bond0,lo: 如果有bonding,dstat会把bond0和eth0 有时会算双份,还有lo的也算到总量里,所以先用-N指定网卡检查下。

参考资料:后台性能测试不可不知的二三事

 

4. JMeter的调优顶一半的事

JMeter的版本越新越好。

 

4.1 JMeter的JVM参数

它默认连个垃圾收集算法都没有配,对延时要求高的,必须配上CMS或G1,内存也整大点降低GC的频率。其他的,给Server配的啥参数,给JMeter也来上一份。如果想动态改的,不写死在脚本里,可以配置环境变量$JVM_ARGS

 

4.2 测试计划的编写

什么if 语句,以及所有其实用动态语言来实现的都挺慢的。
xPath抽取结果集里的字段之类看着就慢的也别写了。
别加任何监听器和图形。
再配置输出日志的格式,能不要的列都别要了,最极端的其实就延时这列有用。

 

4.3 JMeter的运行

在Linux上用命令行跑,别偷懒用Window开着界面跑。
别开超过200条线程(虚拟机上更少)。
可以在不同机器上起多个JMeter,用集群汇总的模式。

 

4.4 结果的统计

初始连接,Server端热身,JVM编译热点方法等都需要时间,所以建议统计前删掉前面的一些日志。

要配置一下才能看到99.9%, 99.99% 分位数的延时,另外因为之前输出日志时省略了很多列,导入日志的时候配置也要如此。

但如果不能XWindows Forward,还要把日志下载回来再导入本地的JMeter,那还不如自己动动手,用sed, awk, sort配合一下自己写个分析延时的脚本直接在服务器上运行。

 

5. 其他被依赖的节点

比如所依赖的数据库够不够快,Restful API的模拟器够不够快,负载均衡器如HAProxy优化配置了没有。

 
唠叨完这些写给测试同学的话,下篇,就可以正式开始调优了。 查看全部
每一次成功的调优,都会诞生又一份的调优指南。

一些必须写在前面的军规,虽然与Java应用的调优没直接关联,但是测试同学经常不留神的地方,导致应用的优化变成一场测试环境调优的戏码。
 

1 独占你的测试机器

包括跑JMeter的那些机器。

"top"或者"pidstat 1" 看一下,其他的路人甲乙丙丁的应用都关干净了没。

如果是云主机,确保更多的占有宿主机的资源,比如深夜大家下班了你在家连VPN回来跑。

 

2 了解你的测试机器

必须完完全全的了解你的机器,才知道有没卡在某个瓶颈,或者与线上环境、其他测试结果的比较。

还是那句, 包括跑JMeter的那些机器。

 

2.1 CPU

"cat /proc/cpuinfo", 看最后一条就好,比如

 

processor : 23
model name : Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz
physical id : 1
cpu cores : 6

所有数字都从零开始,physical id:1即两颗cpu, cpu core: 6即6核,processor : 23即24个处理器。

 

2 CPU * 6 Core * 2HT(Intel超线程技术) = 24 Processor

不过也有很多同事喜欢说24核,也懒得纠正了。

 

2.2 内存

"free -g" 没什么好说的。

 

2.3 硬盘

查看大小、分区、文件系统类型: "df -hT"
硬盘是否SCSI:/dev/sdX就是scsi的,hdX就是普通的。
硬盘是否SSD : "cat /sys/block/sda/queue/rotational", 0是SSD,1是传统硬盘,但也不一定灵

普通硬盘的写速度大概100M/s,RAID级别的查看不方便,SSD的速度也不定,所以用dd测一下最靠谱:

 

dd if=/dev/zero of=dd.file bs=8k count=128k conv=fdatasync
dd if=/dev/zero of=./dd.file bs=1G count=1 conv=fdatasync

上面命令测试了分别以每次8k和1g的大小,写入1g文件的速度。

if:输入文件名, /dev/zero 设备无穷尽地提供0
of:输出文件名
bs:块大小
count:次数
conv=fdatasync :实际写盘,而不是写入Page Cache

硬盘读速度的测试同理,不过要先清理缓存,否则直接从Page Cache读了。

 

sh -c "sync && echo 3 > /proc/sys/vm/drop_caches”
dd if=./dd.file of=/dev/null bs=8k

 

2.4 网卡

先用ifconfig看看有多少块网卡和bonding。bonding是个很棒的东西,可以把多块网卡绑起来,突破单块网卡的带宽限制。

然后检查每块网卡的速度,比如"ethtool eth0"。

再检查bonding,比如"cat /proc/net/bonding/bond0", 留意其Bonding Mode是负载均衡的,再留意其捆绑的网卡的速度。

最后检查测试客户机与服务机之间的带宽,先简单ping或traceroute 一下得到RTT时间,iperf之类的可稍后。

 

2.5 操作系统

Linux的内核版本,是否64位: "uname -a"
Redhat/CentOS版本 : "cat /etc/redhat-release"

 

3. 布置好你的机器状态采集工具

实时观察的,我喜欢dstat,详见《从dstat理解Linux性能监控体系》比vmstat,iostat, sar们都好用,起码对得够齐,单位能自动转换。不过dstat需要安装(yum install dstat,或者去它的网站下载解压即用版)

dstat -tamp:推荐,打印时间戳,比默认打印多一个memory信息,一个有否进程在block状态的信息
dstat -tamN bond0,lo: 如果有bonding,dstat会把bond0和eth0 有时会算双份,还有lo的也算到总量里,所以先用-N指定网卡检查下。

参考资料:后台性能测试不可不知的二三事

 

4. JMeter的调优顶一半的事

JMeter的版本越新越好。

 

4.1 JMeter的JVM参数

它默认连个垃圾收集算法都没有配,对延时要求高的,必须配上CMS或G1,内存也整大点降低GC的频率。其他的,给Server配的啥参数,给JMeter也来上一份。如果想动态改的,不写死在脚本里,可以配置环境变量$JVM_ARGS

 

4.2 测试计划的编写

什么if 语句,以及所有其实用动态语言来实现的都挺慢的。
xPath抽取结果集里的字段之类看着就慢的也别写了。
别加任何监听器和图形。
再配置输出日志的格式,能不要的列都别要了,最极端的其实就延时这列有用。

 

4.3 JMeter的运行

在Linux上用命令行跑,别偷懒用Window开着界面跑。
别开超过200条线程(虚拟机上更少)。
可以在不同机器上起多个JMeter,用集群汇总的模式。

 

4.4 结果的统计

初始连接,Server端热身,JVM编译热点方法等都需要时间,所以建议统计前删掉前面的一些日志。

要配置一下才能看到99.9%, 99.99% 分位数的延时,另外因为之前输出日志时省略了很多列,导入日志的时候配置也要如此。

但如果不能XWindows Forward,还要把日志下载回来再导入本地的JMeter,那还不如自己动动手,用sed, awk, sort配合一下自己写个分析延时的脚本直接在服务器上运行。

 

5. 其他被依赖的节点

比如所依赖的数据库够不够快,Restful API的模拟器够不够快,负载均衡器如HAProxy优化配置了没有。

 
唠叨完这些写给测试同学的话,下篇,就可以正式开始调优了。

Java ThreadPool的正确打开方式

默认分类admin 发表了文章 • 0 个评论 • 149 次浏览 • 2017-01-17 16:45 • 来自相关话题

原文链接:http://calvin1978.blogcn.com/a ... .html
 线程池应对于突然增大、来不及处理的请求,无非两种应对方式:

将未完成的请求放在队列里等待
临时增加处理线程,等高峰回落后再结束临时线程

JDK的Executors.newFixedPool() 和newCachedPool(),分别使用了这两种方式。

不过,这俩函数在方便之余,也屏蔽了ThreadPool原本多样的配置,对一些不求甚解的码农来说,就错过了一些更适合自己项目的选择。

 

1. ThreadPoolExecutor的原理

经典书《Java Concurrency in Pratice(Java并发编程实战)》的第8章,浓缩如下:

1. 每次提交任务时,如果线程数还没达到coreSize就创建新线程并绑定该任务。
所以第coreSize次提交任务后线程总数必达到coreSize,不会重用之前的空闲线程。
在生产环境,为了避免首次调用超时,可以调用executor.prestartCoreThread()预创建所有core线程,避免来一个创一个带来首次调用慢的问题。

2. 线程数达到coreSize后,新增的任务就放到工作队列里,而线程池里的线程则努力的使用take()阻塞地从工作队列里拉活来干。

3. 如果队列是个有界队列,又如果线程池里的线程不能及时将任务取走,工作队列可能会满掉,插入任务就会失败,此时线程池就会紧急的再创建新的临时线程来补救。

4. 临时线程使用poll(keepAliveTime,timeUnit)来从工作队列拉活,如果时候到了仍然两手空空没拉到活,表明它太闲了,就会被解雇掉。

5. 如果core线程数+临时线程数 >maxSize,则不能再创建新的临时线程了,转头执行RejectExecutionHanlder。默认的AbortPolicy抛RejectedExecutionException异常,其他选择包括静默放弃当前任务(Discard),放弃工作队列里最老的任务(DisacardOldest),或由主线程来直接执行(CallerRuns),或你自己发挥想象力写的一个。

 

2. FixedPool 与 CachedPool

FixedPool默认用了一条无界的工作队列 LinkedBlockingQueue, 所以只去到上面的第2步就不会继续往下走了,coreSize的线程做不完的任务不断堆积到无限长的Queue中。
所以只有coreSize一个参数,其他maxSize,keepAliveTime,RejectHandler的配置都不会实际生效。

CachedPool则把coreSize设成0,然后选用了一种特殊的Queue -- SynchronousQueue,只要当前没有空闲线程,Queue就会立刻报插入失败,让线程池增加新的临时线程,默认的KeepAliveTime是1分钟,而且maxSize是整型的最大值,也就是说只要有干不完的活,都会无限增增加线程数,直到高峰过去线程数才会回落。

 

3. 对FixedPool的进一步配置
3.1 设置QueueSize

如果不想搞一条无限长的Queue,避免任务无限等待显得像假死,同时占用太多内存,可能会把它换成一条有界的ArrayBlockingQueue,那就要同时关注一下这条队列满了之后的场景,选择正确的rejectHanlder。

此时,最好还是把maxSize设为coreSize一样的值,不把临时线程及其keepAlive时间拉进来,Queue+临时线程两者结合听是好听,但很难设置好。

3.2 有界队列选LinkedBlockingQueue 还是ArrayBlockingQueue?

按Executors的JavaDoc上说是ArrayBlockingQueue,起码ArrayBlockingQueue每插入一个Runnable就直接放到内部的数组里,而LinkedBlockingQueue则要 new Node(runnable),无疑会产生更多对象。而性能方面有兴趣的同学可以自己测一下。

allowCoreThreadTimeOut(true)

允许core线程也在完全没流量时收缩到0,但因为JDK的算法,只要当前线程数低于core,请求一来就会创建线程,不管现在有没有空闲的线程能服务这个请求,所以这个选项的作用有限,仅在完全没流量时有效。 但都完全没流量了,怎么滴其实也没所谓了。除非是同时有很多个线程池的情况。

 

4. 对CachedPool的进一步配置
4.1 设置coreSize

coreSize默认为0,但很多时候也希望是一个类似FixedPool的固定值,能处理大部分的情况,不要有太多加加减减的波动,等待和消耗的精力。

4.2 设置maxSize及rejectHandler

同理,maxSize默认是整形最大值,但太多的线程也很可能让系统崩溃,所以建议还是设一下maxSize和rejectHandler。

4.3 设置keepAliveTime

默认1分钟,可以根据项目再设置一把。

4.4 SynchronousQueue的性能?

高并发下,SynchronousQueue的性能绝对比LinkedBlockingQueue/ArrayBlockingQueue低一大截。虽然JDK6的实现号称比JDK5的改进很多,但还是慢,据文章说只在20线程并发下它才是快的。

 

5. SpringSide的ThreadPoolBuilder

广告时间,SpringSide的ThreadPoolBuilder能简化上述的配置。

此文太科普太水,主要就是为了帮SpringSide-Utils项目打广告:) 查看全部
原文链接:http://calvin1978.blogcn.com/a ... .html
 线程池应对于突然增大、来不及处理的请求,无非两种应对方式:

将未完成的请求放在队列里等待
临时增加处理线程,等高峰回落后再结束临时线程

JDK的Executors.newFixedPool() 和newCachedPool(),分别使用了这两种方式。

不过,这俩函数在方便之余,也屏蔽了ThreadPool原本多样的配置,对一些不求甚解的码农来说,就错过了一些更适合自己项目的选择。

 

1. ThreadPoolExecutor的原理

经典书《Java Concurrency in Pratice(Java并发编程实战)》的第8章,浓缩如下:

1. 每次提交任务时,如果线程数还没达到coreSize就创建新线程并绑定该任务。
所以第coreSize次提交任务后线程总数必达到coreSize,不会重用之前的空闲线程。
在生产环境,为了避免首次调用超时,可以调用executor.prestartCoreThread()预创建所有core线程,避免来一个创一个带来首次调用慢的问题。

2. 线程数达到coreSize后,新增的任务就放到工作队列里,而线程池里的线程则努力的使用take()阻塞地从工作队列里拉活来干。

3. 如果队列是个有界队列,又如果线程池里的线程不能及时将任务取走,工作队列可能会满掉,插入任务就会失败,此时线程池就会紧急的再创建新的临时线程来补救。

4. 临时线程使用poll(keepAliveTime,timeUnit)来从工作队列拉活,如果时候到了仍然两手空空没拉到活,表明它太闲了,就会被解雇掉。

5. 如果core线程数+临时线程数 >maxSize,则不能再创建新的临时线程了,转头执行RejectExecutionHanlder。默认的AbortPolicy抛RejectedExecutionException异常,其他选择包括静默放弃当前任务(Discard),放弃工作队列里最老的任务(DisacardOldest),或由主线程来直接执行(CallerRuns),或你自己发挥想象力写的一个。

 

2. FixedPool 与 CachedPool

FixedPool默认用了一条无界的工作队列 LinkedBlockingQueue, 所以只去到上面的第2步就不会继续往下走了,coreSize的线程做不完的任务不断堆积到无限长的Queue中。
所以只有coreSize一个参数,其他maxSize,keepAliveTime,RejectHandler的配置都不会实际生效。

CachedPool则把coreSize设成0,然后选用了一种特殊的Queue -- SynchronousQueue,只要当前没有空闲线程,Queue就会立刻报插入失败,让线程池增加新的临时线程,默认的KeepAliveTime是1分钟,而且maxSize是整型的最大值,也就是说只要有干不完的活,都会无限增增加线程数,直到高峰过去线程数才会回落。

 

3. 对FixedPool的进一步配置
3.1 设置QueueSize

如果不想搞一条无限长的Queue,避免任务无限等待显得像假死,同时占用太多内存,可能会把它换成一条有界的ArrayBlockingQueue,那就要同时关注一下这条队列满了之后的场景,选择正确的rejectHanlder。

此时,最好还是把maxSize设为coreSize一样的值,不把临时线程及其keepAlive时间拉进来,Queue+临时线程两者结合听是好听,但很难设置好。

3.2 有界队列选LinkedBlockingQueue 还是ArrayBlockingQueue?

按Executors的JavaDoc上说是ArrayBlockingQueue,起码ArrayBlockingQueue每插入一个Runnable就直接放到内部的数组里,而LinkedBlockingQueue则要 new Node(runnable),无疑会产生更多对象。而性能方面有兴趣的同学可以自己测一下。

allowCoreThreadTimeOut(true)

允许core线程也在完全没流量时收缩到0,但因为JDK的算法,只要当前线程数低于core,请求一来就会创建线程,不管现在有没有空闲的线程能服务这个请求,所以这个选项的作用有限,仅在完全没流量时有效。 但都完全没流量了,怎么滴其实也没所谓了。除非是同时有很多个线程池的情况。

 

4. 对CachedPool的进一步配置
4.1 设置coreSize

coreSize默认为0,但很多时候也希望是一个类似FixedPool的固定值,能处理大部分的情况,不要有太多加加减减的波动,等待和消耗的精力。

4.2 设置maxSize及rejectHandler

同理,maxSize默认是整形最大值,但太多的线程也很可能让系统崩溃,所以建议还是设一下maxSize和rejectHandler。

4.3 设置keepAliveTime

默认1分钟,可以根据项目再设置一把。

4.4 SynchronousQueue的性能?

高并发下,SynchronousQueue的性能绝对比LinkedBlockingQueue/ArrayBlockingQueue低一大截。虽然JDK6的实现号称比JDK5的改进很多,但还是慢,据文章说只在20线程并发下它才是快的。

 

5. SpringSide的ThreadPoolBuilder

广告时间,SpringSide的ThreadPoolBuilder能简化上述的配置。

此文太科普太水,主要就是为了帮SpringSide-Utils项目打广告:)

StringBuilder在高性能场景下的正确用法

默认分类admin 发表了文章 • 0 个评论 • 157 次浏览 • 2017-01-17 16:44 • 来自相关话题

原文链接:http://calvin1978.blogcn.com/a ... .html
 关于StringBuilder,一般同学只简单记住了,字符串拼接要用StringBuilder,不要用+,也不要用StringBuffer,然后性能就是最好的了,真的吗吗吗吗?

还有些同学,还听过三句似是而非的经验:

1. Java编译优化后+和StringBuilder的效果一样;

2. StringBuilder不是线程安全的,为了“安全”起见最好还是用StringBuffer;

3. 永远不要自己拼接日志信息的字符串,交给slf4j来。

 

1. 初始长度好重要,值得说四次。

StringBuilder的内部有一个char[], 不断的append()就是不断的往char[]里填东西的过程。

new StringBuilder() 时char[]的默认长度是16,然后,如果要append第17个字符,怎么办?

用System.arraycopy成倍复制扩容!!!!

这样一来有数组拷贝的成本,二来原来的char[]也白白浪费了要被GC掉。可以想见,一个129字符长度的字符串,经过了16,32,64, 128四次的复制和丢弃,合共申请了496字符的数组,在高性能场景下,这几乎不能忍。

所以,合理设置一个初始值多重要。

但如果我实在估算不好呢?多估一点点好了,只要字符串最后大于16,就算浪费一点点,也比成倍的扩容好。

 

2. Liferay的StringBundler类

Liferay的StringBundler类提供了另一个长度设置的思路,它在append()的时候,不急着往char[]里塞东西,而是先拿一个String[]把它们都存起来,到了最后才把所有String的length加起来,构造一个合理长度的StringBuilder。

 

3. 但,还是浪费了一倍的char[]

浪费发生在最后一步,StringBuilder.toString()

 

//创建拷贝, 不共享数组
return new String(value, 0, count);

String的构造函数会用 System.arraycopy()复制一把传入的char[]来保证安全性不可变性,如果故事就这样结束,StringBuilder里的char[]还是被白白牺牲了。

为了不浪费这些char[],一种方法是用Unsafe之类的各种黑科技,绕过构造函数直接给String的char[]属性赋值,但很少人这样做。

另一个靠谱一些的办法就是重用StringBuilder。而重用,还解决了前面的长度设置问题,因为即使一开始估算不准,多扩容几次之后也够了。

 

4. 重用StringBuilder

这个做法来源于JDK里的BigDecimal类(没事看看JDK代码多重要),后来发现Netty也同样使用。SpringSide里将代码提取成StringBuilderHolder,里面只有一个函数

 

public StringBuilder getStringBuilder() {
sb.setLength(0);
return sb;
}

StringBuilder.setLength()函数只重置它的count指针,而char[]则会继续重用,而toString()时会把当前的count指针也作为参数传给String的构造函数,所以不用担心把超过新内容大小的旧内容也传进去了。可见,StringBuilder是完全可以被重用的。

为了避免并发冲突,这个Holder一般设为ThreadLocal,标准写法见BigDecimal或StringBuilderHolder的注释。

不过,如果String的长度不大,那从ThreadLocal里取一次值的代价还更大的多,所以也不能把这个ThreadLocalStringBuilder搞出来后,见到StringBuilder就替换。。。
 

5. + 与 StringBuilder

 

String s = “hello ” + user.getName();

这一句经过javac编译后的效果,的确等价于使用StringBuilder,但没有设定长度。

 

String s = new StringBuilder().append(“hello”).append(user.getName());

但是,如果像下面这样:

 

String s = “hello ”;
// 隔了其他一些语句
s = s + user.getName();

每一条语句,都会生成一个新的StringBuilder,这里就有了两个StringBuilder,性能就完全不一样了。如果是在循环体里s+=i; 就更加多得没谱。

据R大说,努力的JVM工程师们在运行优化阶段, 根据+XX:+OptimizeStringConcat(JDK7u40后默认打开),把相邻的(中间没隔着控制语句) StringBuilder合成一个,也会努力的猜长度。

所以,保险起见还是继续自己用StringBuilder并设定长度好了。

 

6. StringBuffer 与 StringBuilder

StringBuffer与StringBuilder都是继承于AbstractStringBuilder,唯一的区别就是StringBuffer的函数上都有synchronized关键字。

那些说StringBuffer “安全”的同学,其实你几时看过几个线程轮流append一个StringBuffer的情况???

 

7. 永远把日志的字符串拼接交给slf4j??

 

logger.info("Hello {}", user.getName());

对于不知道要不要输出的日志,交给slf4j在真的需要输出时才去拼接的确能省节约成本。

但对于一定要输出的日志,直接自己用StringBuilder拼接更快。因为看看slf4j的实现,实际上就是不断的indexof("{}"), 不断的subString(),再不断的用StringBuilder拼起来而已,没有银弹。

PS. slf4j中的StringBuilder在原始Message之外预留了50个字符,如果可变参数加起来长过50字符还是得复制扩容......而且StringBuilder也没有重用。

 

8. 小结

StringBuilder默认的写法,会为129长度的字符串拼接,合共申请625字符的数组。所以高性能的场景下,永远要考虑用一个ThreadLocal 可重用的StringBuilder。而且重用之后,就不用再玩猜长度的游戏了。当然,如果字符串只有一百几十字节,也不一定要考虑重用,设好初始值就好。 查看全部
原文链接:http://calvin1978.blogcn.com/a ... .html
 关于StringBuilder,一般同学只简单记住了,字符串拼接要用StringBuilder,不要用+,也不要用StringBuffer,然后性能就是最好的了,真的吗吗吗吗?

还有些同学,还听过三句似是而非的经验:

1. Java编译优化后+和StringBuilder的效果一样;

2. StringBuilder不是线程安全的,为了“安全”起见最好还是用StringBuffer;

3. 永远不要自己拼接日志信息的字符串,交给slf4j来。

 

1. 初始长度好重要,值得说四次。

StringBuilder的内部有一个char[], 不断的append()就是不断的往char[]里填东西的过程。

new StringBuilder() 时char[]的默认长度是16,然后,如果要append第17个字符,怎么办?

用System.arraycopy成倍复制扩容!!!!

这样一来有数组拷贝的成本,二来原来的char[]也白白浪费了要被GC掉。可以想见,一个129字符长度的字符串,经过了16,32,64, 128四次的复制和丢弃,合共申请了496字符的数组,在高性能场景下,这几乎不能忍。

所以,合理设置一个初始值多重要。

但如果我实在估算不好呢?多估一点点好了,只要字符串最后大于16,就算浪费一点点,也比成倍的扩容好。

 

2. Liferay的StringBundler类

Liferay的StringBundler类提供了另一个长度设置的思路,它在append()的时候,不急着往char[]里塞东西,而是先拿一个String[]把它们都存起来,到了最后才把所有String的length加起来,构造一个合理长度的StringBuilder。

 

3. 但,还是浪费了一倍的char[]

浪费发生在最后一步,StringBuilder.toString()

 

//创建拷贝, 不共享数组
return new String(value, 0, count);

String的构造函数会用 System.arraycopy()复制一把传入的char[]来保证安全性不可变性,如果故事就这样结束,StringBuilder里的char[]还是被白白牺牲了。

为了不浪费这些char[],一种方法是用Unsafe之类的各种黑科技,绕过构造函数直接给String的char[]属性赋值,但很少人这样做。

另一个靠谱一些的办法就是重用StringBuilder。而重用,还解决了前面的长度设置问题,因为即使一开始估算不准,多扩容几次之后也够了。

 

4. 重用StringBuilder

这个做法来源于JDK里的BigDecimal类(没事看看JDK代码多重要),后来发现Netty也同样使用。SpringSide里将代码提取成StringBuilderHolder,里面只有一个函数

 

public StringBuilder getStringBuilder() {
sb.setLength(0);
return sb;
}

StringBuilder.setLength()函数只重置它的count指针,而char[]则会继续重用,而toString()时会把当前的count指针也作为参数传给String的构造函数,所以不用担心把超过新内容大小的旧内容也传进去了。可见,StringBuilder是完全可以被重用的。

为了避免并发冲突,这个Holder一般设为ThreadLocal,标准写法见BigDecimal或StringBuilderHolder的注释。

不过,如果String的长度不大,那从ThreadLocal里取一次值的代价还更大的多,所以也不能把这个ThreadLocalStringBuilder搞出来后,见到StringBuilder就替换。。。
 

5. + 与 StringBuilder

 

String s = “hello ” + user.getName();

这一句经过javac编译后的效果,的确等价于使用StringBuilder,但没有设定长度。

 

String s = new StringBuilder().append(“hello”).append(user.getName());

但是,如果像下面这样:

 

String s = “hello ”;
// 隔了其他一些语句
s = s + user.getName();

每一条语句,都会生成一个新的StringBuilder,这里就有了两个StringBuilder,性能就完全不一样了。如果是在循环体里s+=i; 就更加多得没谱。

据R大说,努力的JVM工程师们在运行优化阶段, 根据+XX:+OptimizeStringConcat(JDK7u40后默认打开),把相邻的(中间没隔着控制语句) StringBuilder合成一个,也会努力的猜长度。

所以,保险起见还是继续自己用StringBuilder并设定长度好了。

 

6. StringBuffer 与 StringBuilder

StringBuffer与StringBuilder都是继承于AbstractStringBuilder,唯一的区别就是StringBuffer的函数上都有synchronized关键字。

那些说StringBuffer “安全”的同学,其实你几时看过几个线程轮流append一个StringBuffer的情况???

 

7. 永远把日志的字符串拼接交给slf4j??

 

logger.info("Hello {}", user.getName());

对于不知道要不要输出的日志,交给slf4j在真的需要输出时才去拼接的确能省节约成本。

但对于一定要输出的日志,直接自己用StringBuilder拼接更快。因为看看slf4j的实现,实际上就是不断的indexof("{}"), 不断的subString(),再不断的用StringBuilder拼起来而已,没有银弹。

PS. slf4j中的StringBuilder在原始Message之外预留了50个字符,如果可变参数加起来长过50字符还是得复制扩容......而且StringBuilder也没有重用。

 

8. 小结

StringBuilder默认的写法,会为129长度的字符串拼接,合共申请625字符的数组。所以高性能的场景下,永远要考虑用一个ThreadLocal 可重用的StringBuilder。而且重用之后,就不用再玩猜长度的游戏了。当然,如果字符串只有一百几十字节,也不一定要考虑重用,设好初始值就好。

另一份Java应用调优指南之-前菜

默认分类admin 发表了文章 • 0 个评论 • 115 次浏览 • 2017-01-17 11:46 • 来自相关话题

原文链接:http://calvin1978.blogcn.com/articles/perf-tunning-1.html
 
每一次成功的调优,都会诞生又一份的调优指南。

一些必须写在前面的军规,虽然与Java应用的调优没直接关联,但是测试同学经常不留神的地方,导致应用的优化变成一场测试环境调优的戏码。
 

1 独占你的测试机器

包括跑JMeter的那些机器。

"top"或者"pidstat 1" 看一下,其他的路人甲乙丙丁的应用都关干净了没。

如果是云主机,确保更多的占有宿主机的资源,比如深夜大家下班了你在家连VPN回来跑。

 

2 了解你的测试机器

必须完完全全的了解你的机器,才知道有没卡在某个瓶颈,或者与线上环境、其他测试结果的比较。

还是那句, 包括跑JMeter的那些机器。

 

2.1 CPU

"cat /proc/cpuinfo", 看最后一条就好,比如

 

processor : 23
model name : Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz
physical id : 1
cpu cores : 6

所有数字都从零开始,physical id:1即两颗cpu, cpu core: 6即6核,processor : 23即24个处理器。

 

2 CPU * 6 Core * 2HT(Intel超线程技术) = 24 Processor

不过也有很多同事喜欢说24核,也懒得纠正了。

 

2.2 内存

"free -g" 没什么好说的。

 

2.3 硬盘

查看大小、分区、文件系统类型: "df -hT"
硬盘是否SCSI:/dev/sdX就是scsi的,hdX就是普通的。
硬盘是否SSD : "cat /sys/block/sda/queue/rotational", 0是SSD,1是传统硬盘,但也不一定灵

普通硬盘的写速度大概100M/s,RAID级别的查看不方便,SSD的速度也不定,所以用dd测一下最靠谱:

 

dd if=/dev/zero of=dd.file bs=8k count=128k conv=fdatasync
dd if=/dev/zero of=./dd.file bs=1G count=1 conv=fdatasync

上面命令测试了分别以每次8k和1g的大小,写入1g文件的速度。

if:输入文件名, /dev/zero 设备无穷尽地提供0
of:输出文件名
bs:块大小
count:次数
conv=fdatasync :实际写盘,而不是写入Page Cache

硬盘读速度的测试同理,不过要先清理缓存,否则直接从Page Cache读了。

 

sh -c "sync && echo 3 > /proc/sys/vm/drop_caches”
dd if=./dd.file of=/dev/null bs=8k

 

2.4 网卡

先用ifconfig看看有多少块网卡和bonding。bonding是个很棒的东西,可以把多块网卡绑起来,突破单块网卡的带宽限制。

然后检查每块网卡的速度,比如"ethtool eth0"。

再检查bonding,比如"cat /proc/net/bonding/bond0", 留意其Bonding Mode是负载均衡的,再留意其捆绑的网卡的速度。

最后检查测试客户机与服务机之间的带宽,先简单ping或traceroute 一下得到RTT时间,iperf之类的可稍后。

 

2.5 操作系统

Linux的内核版本,是否64位: "uname -a"
Redhat/CentOS版本 : "cat /etc/redhat-release"

 

3. 布置好你的机器状态采集工具

实时观察的,我喜欢dstat,详见《从dstat理解Linux性能监控体系》比vmstat,iostat, sar们都好用,起码对得够齐,单位能自动转换。不过dstat需要安装(yum install dstat,或者去它的网站下载解压即用版)

dstat -tamp:推荐,打印时间戳,比默认打印多一个memory信息,一个有否进程在block状态的信息
dstat -tamN bond0,lo: 如果有bonding,dstat会把bond0和eth0 有时会算双份,还有lo的也算到总量里,所以先用-N指定网卡检查下。

参考资料:后台性能测试不可不知的二三事

 

4. JMeter的调优顶一半的事

JMeter的版本越新越好。

 

4.1 JMeter的JVM参数

它默认连个垃圾收集算法都没有配,对延时要求高的,必须配上CMS或G1,内存也整大点降低GC的频率。其他的,给Server配的啥参数,给JMeter也来上一份。如果想动态改的,不写死在脚本里,可以配置环境变量$JVM_ARGS

 

4.2 测试计划的编写

什么if 语句,以及所有其实用动态语言来实现的都挺慢的。
xPath抽取结果集里的字段之类看着就慢的也别写了。
别加任何监听器和图形。
再配置输出日志的格式,能不要的列都别要了,最极端的其实就延时这列有用。

 

4.3 JMeter的运行

在Linux上用命令行跑,别偷懒用Window开着界面跑。
别开超过200条线程(虚拟机上更少)。
可以在不同机器上起多个JMeter,用集群汇总的模式。

 

4.4 结果的统计

初始连接,Server端热身,JVM编译热点方法等都需要时间,所以建议统计前删掉前面的一些日志。

要配置一下才能看到99.9%, 99.99% 分位数的延时,另外因为之前输出日志时省略了很多列,导入日志的时候配置也要如此。

但如果不能XWindows Forward,还要把日志下载回来再导入本地的JMeter,那还不如自己动动手,用sed, awk, sort配合一下自己写个分析延时的脚本直接在服务器上运行。

 

5. 其他被依赖的节点

比如所依赖的数据库够不够快,Restful API的模拟器够不够快,负载均衡器如HAProxy优化配置了没有。

  查看全部
原文链接:http://calvin1978.blogcn.com/articles/perf-tunning-1.html
 
每一次成功的调优,都会诞生又一份的调优指南。

一些必须写在前面的军规,虽然与Java应用的调优没直接关联,但是测试同学经常不留神的地方,导致应用的优化变成一场测试环境调优的戏码。
 

1 独占你的测试机器

包括跑JMeter的那些机器。

"top"或者"pidstat 1" 看一下,其他的路人甲乙丙丁的应用都关干净了没。

如果是云主机,确保更多的占有宿主机的资源,比如深夜大家下班了你在家连VPN回来跑。

 

2 了解你的测试机器

必须完完全全的了解你的机器,才知道有没卡在某个瓶颈,或者与线上环境、其他测试结果的比较。

还是那句, 包括跑JMeter的那些机器。

 

2.1 CPU

"cat /proc/cpuinfo", 看最后一条就好,比如

 

processor : 23
model name : Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz
physical id : 1
cpu cores : 6

所有数字都从零开始,physical id:1即两颗cpu, cpu core: 6即6核,processor : 23即24个处理器。

 

2 CPU * 6 Core * 2HT(Intel超线程技术) = 24 Processor

不过也有很多同事喜欢说24核,也懒得纠正了。

 

2.2 内存

"free -g" 没什么好说的。

 

2.3 硬盘

查看大小、分区、文件系统类型: "df -hT"
硬盘是否SCSI:/dev/sdX就是scsi的,hdX就是普通的。
硬盘是否SSD : "cat /sys/block/sda/queue/rotational", 0是SSD,1是传统硬盘,但也不一定灵

普通硬盘的写速度大概100M/s,RAID级别的查看不方便,SSD的速度也不定,所以用dd测一下最靠谱:

 

dd if=/dev/zero of=dd.file bs=8k count=128k conv=fdatasync
dd if=/dev/zero of=./dd.file bs=1G count=1 conv=fdatasync

上面命令测试了分别以每次8k和1g的大小,写入1g文件的速度。

if:输入文件名, /dev/zero 设备无穷尽地提供0
of:输出文件名
bs:块大小
count:次数
conv=fdatasync :实际写盘,而不是写入Page Cache

硬盘读速度的测试同理,不过要先清理缓存,否则直接从Page Cache读了。

 

sh -c "sync && echo 3 > /proc/sys/vm/drop_caches”
dd if=./dd.file of=/dev/null bs=8k

 

2.4 网卡

先用ifconfig看看有多少块网卡和bonding。bonding是个很棒的东西,可以把多块网卡绑起来,突破单块网卡的带宽限制。

然后检查每块网卡的速度,比如"ethtool eth0"。

再检查bonding,比如"cat /proc/net/bonding/bond0", 留意其Bonding Mode是负载均衡的,再留意其捆绑的网卡的速度。

最后检查测试客户机与服务机之间的带宽,先简单ping或traceroute 一下得到RTT时间,iperf之类的可稍后。

 

2.5 操作系统

Linux的内核版本,是否64位: "uname -a"
Redhat/CentOS版本 : "cat /etc/redhat-release"

 

3. 布置好你的机器状态采集工具

实时观察的,我喜欢dstat,详见《从dstat理解Linux性能监控体系》比vmstat,iostat, sar们都好用,起码对得够齐,单位能自动转换。不过dstat需要安装(yum install dstat,或者去它的网站下载解压即用版)

dstat -tamp:推荐,打印时间戳,比默认打印多一个memory信息,一个有否进程在block状态的信息
dstat -tamN bond0,lo: 如果有bonding,dstat会把bond0和eth0 有时会算双份,还有lo的也算到总量里,所以先用-N指定网卡检查下。

参考资料:后台性能测试不可不知的二三事

 

4. JMeter的调优顶一半的事

JMeter的版本越新越好。

 

4.1 JMeter的JVM参数

它默认连个垃圾收集算法都没有配,对延时要求高的,必须配上CMS或G1,内存也整大点降低GC的频率。其他的,给Server配的啥参数,给JMeter也来上一份。如果想动态改的,不写死在脚本里,可以配置环境变量$JVM_ARGS

 

4.2 测试计划的编写

什么if 语句,以及所有其实用动态语言来实现的都挺慢的。
xPath抽取结果集里的字段之类看着就慢的也别写了。
别加任何监听器和图形。
再配置输出日志的格式,能不要的列都别要了,最极端的其实就延时这列有用。

 

4.3 JMeter的运行

在Linux上用命令行跑,别偷懒用Window开着界面跑。
别开超过200条线程(虚拟机上更少)。
可以在不同机器上起多个JMeter,用集群汇总的模式。

 

4.4 结果的统计

初始连接,Server端热身,JVM编译热点方法等都需要时间,所以建议统计前删掉前面的一些日志。

要配置一下才能看到99.9%, 99.99% 分位数的延时,另外因为之前输出日志时省略了很多列,导入日志的时候配置也要如此。

但如果不能XWindows Forward,还要把日志下载回来再导入本地的JMeter,那还不如自己动动手,用sed, awk, sort配合一下自己写个分析延时的脚本直接在服务器上运行。

 

5. 其他被依赖的节点

比如所依赖的数据库够不够快,Restful API的模拟器够不够快,负载均衡器如HAProxy优化配置了没有。

 

另一份Java应用调优指南之-工具篇

默认分类admin 发表了文章 • 0 个评论 • 154 次浏览 • 2017-01-17 11:39 • 来自相关话题

Java应用的调优,再不写都要忘光了,先对付着写完,免费的JMC真的好用,大家越早用上越好。

前一篇是三个月前的 另一份Java应用调优指南 - 前菜

 

1. 土法调优两大件

先忆苦思甜,一般人在没有Profile工具的时候,调优的两大件,无非Heap Dump 与 Thread Dump。

 

1.1 Heap Dump

 

jmap -dump:live,format=b,file=heap.hprof pid

从安全点日志看,从Heap Dump开始,整个JVM都是停顿的,考虑到IO(虽是写到Page Cache,但或许会遇到background flush),几G的Heap可能产生几秒的停顿,在生产环境上执行时谨慎再谨慎。

live的选项,实际上是产生一次Full GC来保证只看还存活的对象。有时候也会故意不加live选项,看历史对象。

Dump出来的文件建议用JDK自带的VisualVM或Eclipse的MAT插件打开,对象的大小有两种统计方式:

本身大小(Shallow Size):对象本来的大小。
保留大小(Retained Size): 当前对象大小 + 当前对象直接或间接引用到的对象的大小总和。

看本身大小时,占大头的都是char[] ,byte[]之类的,没什么意思(用jmap -histo:live pid 看的也是本身大小)。所以需要关心的是保留大小比较大的对象,看谁在引用这些char[], byte[]。

(MAT能看的信息更多,但VisualVM胜在JVM自带,用法如下:命令行输入jvisualvm,文件->装入->堆Dump->检查 -> 查找20保留大小最大的对象,就会触发保留大小的计算,然后就可以类视图里浏览,按保留大小排序了)
 

1.2 Thread Dump

ThreadDump 同样会造成JVM停顿,在生产系统上执行要谨慎。

可以命令行方式执行它,"jstack pid” 或"jstack -l pid" ,其中-l 会同时打印各种lock,但会使得JVM停顿得长久得多(可能会差很多倍,比如普通的jstack可能几毫秒和一次GC没区别,加了-l 就是近一秒的时间),-l 建议不要用。

另一种是直接用代码来打印,比如线程池满了无法添加新任务,在开发或性能测试模式下,可以在代码里捕捉该异常后直接把当前线程池的情况打印出来。

 

ThreadMXBean threadMBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMBean.dumpAllThreads(false, false);

同样注意,这里threadMBean.dumpAllThreads(false,false)的参数为false,把参数改为true,则打印synchronizers与monitor,同样使得JVM停顿久很多。

线程状态:

RUNNABLE: 运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。
BLOCKED:被某个锁(synchronizers)給block住了。
WAITING:等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。
TIME_WAITING:和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。

分析工具:

IBM Thread and Monitor Dump Analyze for Java 一个小巧的Jar包,能方便的按状态,线程名称,线程停留的函数排序,快速浏览。
http://spotify.github.io/threaddump-analyzer Spotify提供的Web版在线分析工具,可以将锁或条件相关联的线程聚合到一起。

 

2. 你真正要的Java Mission Control

如果你使用过JProfiler,Yourkit,VisualVM还有Eclipse的Profiler插件等一堆Profiler工具,或者用JavaSimion等在代码里打印过metrics,最后会发现免费的JMC才是你想要的。
 

2.1 优点

代替收费的JProfiler的好东西,以前Bea JRockit的宝贝,现在随着JDK7 up40以后的版本免费自带了,不过只在开发环境免费,就是说理论上不能拿它来连生产环境的机器。

另一个让人开心的事情就是JMC采用采样,而不是传统的代码植入的技术,对应用性能的影响非常非常小,完全可以开着JMC来做压测(唯一影响可能是full gc多了,减少一些监控项看看)。不会像以前,开了代码植入型的Profiler,出来的性能测试结果差了一个数量级不说,热点完全可能是错误的,这是一个真实的故事,具体细节就不说了。
 

2.2 功能

JMC里可以看的东西太多了,自己觉得最有用的如下:

内存Tab:分配Tab里的按类、按线程、对象的创建调用栈来归类的对象创建情况,让对象创建无处躲藏。
内存Tab:GC Tab的GC详细情况,以及TLAB外的分配情况(每条线程在Heap里分了一个Thread Local Area,在TLAB里的内存分配不需要线程竞争,所以TLAB之外的分配是不好的)
代码Tab:热点方法类及它的调用栈,超有用的功能。调用树是从线程角度看的方法调用,而按包名分类可以看3PP包的问题。
线程Tab:热点线程,再换个姿势来看热点方法和调用树。
线程Tab:争用,等待时间,锁定实例等。

 

2.3 使用方法简述

JDK7在启动服务时加上-XX:+UnlockCommercialFeatures -XX:+FlightRecorder 。
如果是远程服务器,要开JMX:

 

“-Dcom.sun.management.jmxremote.port=7001 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=your ip”

JDK自带的jmc命令,文件->连接->设定JMX连接,启动飞行纪录,固定时间选1分钟或更多,事件设置选为profiling,然后进一步修改,自己查看下都Profile了哪些信息,觉得不够的再添加些(下次就直接用上次设定就好了),比如:

加上对象数量的统计:Java Virtual Machine->GC->Detail->Object Count/Object Count after GC
方法调用采样的间隔从10ms改为1ms(但不能低于1ms,否则会影响性能了): Java Virtual Machine->Profiling下的两个选项
Socket与File采样, 10ms太久,但即使改为1ms也未必能抓住什么,可以干脆取消掉: Java Application->File Read/FileWrite/Socket Read/Socket Write

然后就开始Profile,到时间后Profile结束,会自动把记录下载回来,在JMC中展示。

其他资料:

java-performance.info上的介绍文章
JMC作者的博客
JMC官网

 

3. BTrace

神器,在生产环境上,动态监控某些方法的执行时长及其他信息,不再需要自己手工打日志,发版本,部署与重启服务。

据说淘宝就是经常开着BTrace在线上找问题的,我们最近也在生产上试了几把,太爽利了。

使用方法网上一搜大把,就不重复了。

原理就是自己写一个类似AspectJ的,希望监控哪个方法,监控后做什么动作的脚本,然后动态执行btrace命令将这个脚本attach到某个JVM上就行

 
这是一篇严肃的文章,就不配图了。 查看全部
Java应用的调优,再不写都要忘光了,先对付着写完,免费的JMC真的好用,大家越早用上越好。

前一篇是三个月前的 另一份Java应用调优指南 - 前菜

 

1. 土法调优两大件

先忆苦思甜,一般人在没有Profile工具的时候,调优的两大件,无非Heap Dump 与 Thread Dump。

 

1.1 Heap Dump

 

jmap -dump:live,format=b,file=heap.hprof pid

从安全点日志看,从Heap Dump开始,整个JVM都是停顿的,考虑到IO(虽是写到Page Cache,但或许会遇到background flush),几G的Heap可能产生几秒的停顿,在生产环境上执行时谨慎再谨慎。

live的选项,实际上是产生一次Full GC来保证只看还存活的对象。有时候也会故意不加live选项,看历史对象。

Dump出来的文件建议用JDK自带的VisualVM或Eclipse的MAT插件打开,对象的大小有两种统计方式:

本身大小(Shallow Size):对象本来的大小。
保留大小(Retained Size): 当前对象大小 + 当前对象直接或间接引用到的对象的大小总和。

看本身大小时,占大头的都是char[] ,byte[]之类的,没什么意思(用jmap -histo:live pid 看的也是本身大小)。所以需要关心的是保留大小比较大的对象,看谁在引用这些char[], byte[]。

(MAT能看的信息更多,但VisualVM胜在JVM自带,用法如下:命令行输入jvisualvm,文件->装入->堆Dump->检查 -> 查找20保留大小最大的对象,就会触发保留大小的计算,然后就可以类视图里浏览,按保留大小排序了)
 

1.2 Thread Dump

ThreadDump 同样会造成JVM停顿,在生产系统上执行要谨慎。

可以命令行方式执行它,"jstack pid” 或"jstack -l pid" ,其中-l 会同时打印各种lock,但会使得JVM停顿得长久得多(可能会差很多倍,比如普通的jstack可能几毫秒和一次GC没区别,加了-l 就是近一秒的时间),-l 建议不要用。

另一种是直接用代码来打印,比如线程池满了无法添加新任务,在开发或性能测试模式下,可以在代码里捕捉该异常后直接把当前线程池的情况打印出来。

 

ThreadMXBean threadMBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMBean.dumpAllThreads(false, false);

同样注意,这里threadMBean.dumpAllThreads(false,false)的参数为false,把参数改为true,则打印synchronizers与monitor,同样使得JVM停顿久很多。

线程状态:

RUNNABLE: 运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。
BLOCKED:被某个锁(synchronizers)給block住了。
WAITING:等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。
TIME_WAITING:和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。

分析工具:

IBM Thread and Monitor Dump Analyze for Java 一个小巧的Jar包,能方便的按状态,线程名称,线程停留的函数排序,快速浏览。
http://spotify.github.io/threaddump-analyzer Spotify提供的Web版在线分析工具,可以将锁或条件相关联的线程聚合到一起。

 

2. 你真正要的Java Mission Control

如果你使用过JProfiler,Yourkit,VisualVM还有Eclipse的Profiler插件等一堆Profiler工具,或者用JavaSimion等在代码里打印过metrics,最后会发现免费的JMC才是你想要的。
 

2.1 优点

代替收费的JProfiler的好东西,以前Bea JRockit的宝贝,现在随着JDK7 up40以后的版本免费自带了,不过只在开发环境免费,就是说理论上不能拿它来连生产环境的机器。

另一个让人开心的事情就是JMC采用采样,而不是传统的代码植入的技术,对应用性能的影响非常非常小,完全可以开着JMC来做压测(唯一影响可能是full gc多了,减少一些监控项看看)。不会像以前,开了代码植入型的Profiler,出来的性能测试结果差了一个数量级不说,热点完全可能是错误的,这是一个真实的故事,具体细节就不说了。
 

2.2 功能

JMC里可以看的东西太多了,自己觉得最有用的如下:

内存Tab:分配Tab里的按类、按线程、对象的创建调用栈来归类的对象创建情况,让对象创建无处躲藏。
内存Tab:GC Tab的GC详细情况,以及TLAB外的分配情况(每条线程在Heap里分了一个Thread Local Area,在TLAB里的内存分配不需要线程竞争,所以TLAB之外的分配是不好的)
代码Tab:热点方法类及它的调用栈,超有用的功能。调用树是从线程角度看的方法调用,而按包名分类可以看3PP包的问题。
线程Tab:热点线程,再换个姿势来看热点方法和调用树。
线程Tab:争用,等待时间,锁定实例等。

 

2.3 使用方法简述

JDK7在启动服务时加上-XX:+UnlockCommercialFeatures -XX:+FlightRecorder 。
如果是远程服务器,要开JMX:

 

“-Dcom.sun.management.jmxremote.port=7001 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=your ip”

JDK自带的jmc命令,文件->连接->设定JMX连接,启动飞行纪录,固定时间选1分钟或更多,事件设置选为profiling,然后进一步修改,自己查看下都Profile了哪些信息,觉得不够的再添加些(下次就直接用上次设定就好了),比如:

加上对象数量的统计:Java Virtual Machine->GC->Detail->Object Count/Object Count after GC
方法调用采样的间隔从10ms改为1ms(但不能低于1ms,否则会影响性能了): Java Virtual Machine->Profiling下的两个选项
Socket与File采样, 10ms太久,但即使改为1ms也未必能抓住什么,可以干脆取消掉: Java Application->File Read/FileWrite/Socket Read/Socket Write

然后就开始Profile,到时间后Profile结束,会自动把记录下载回来,在JMC中展示。

其他资料:

java-performance.info上的介绍文章
JMC作者的博客
JMC官网

 

3. BTrace

神器,在生产环境上,动态监控某些方法的执行时长及其他信息,不再需要自己手工打日志,发版本,部署与重启服务。

据说淘宝就是经常开着BTrace在线上找问题的,我们最近也在生产上试了几把,太爽利了。

使用方法网上一搜大把,就不重复了。

原理就是自己写一个类似AspectJ的,希望监控哪个方法,监控后做什么动作的脚本,然后动态执行btrace命令将这个脚本attach到某个JVM上就行

 
这是一篇严肃的文章,就不配图了。

Tomcat线程池,更符合大家想象的可扩展线程池

默认分类admin 发表了文章 • 0 个评论 • 127 次浏览 • 2017-01-17 11:35 • 来自相关话题

原文链接:http://calvin1978.blogcn.com/a ... .html
 因由

说起线程池,大家可能受连接池的印象影响,天然的认为,它应该是一开始有core条线程,忙不过来了就扩展到max条线程,闲的时候又回落到core条线程,如果还有更高的高峰,就放进一个缓冲队列里缓冲一下。

有些整天只和SSH打交道的同学,可能现在还是这样认为的。

无情的现实就是,JDK只有两种典型的线程池,FixedPool 与 CachedPool:

FixedPool固定线程数,忙不过来的全放到无限长的缓冲队列里。
CachedPool,忙不过来时无限的增加临时线程,闲时回落,没有缓冲队列。

在Java ThreadPool的正确打开方式里,建议了如何设置,避免上面两句吓人的“无限”。但无论怎么配,都无法用现成的东西,配出文章一开始的想象图来。

但不得不说,这幅想象图还是比较美好的,特别对于偶有卡顿,偶有不可预测高峰的业务线程池来说。

当然,也有人说请求积压在最后的缓冲队列里不好控制,看具体业务场景了,缓冲队列也有不同的玩法(后详)。

 

原理

我们的同事老王,研究了一番ThreadPool的机制后,提出了自己实现队列的方式。碰巧,Tomcat也正是这么做的,比起Jetty完全自己写线程池,Tomcat基于JDK的线程池稍作定制,要斯文一些。

JDK线程池的逻辑很简单( 更详细描述还是见Java ThreadPool的正确打开方式 )

- 前core个请求,来一个请求就创建一个线程。
- 之后,把请求插入缓冲队列里让所有的线程去抢;如果插入失败则创建新线程。
- 如果达到max条线程了,抛出拒绝异常。

貌似控制的枢纽都在第2句那里--队列插入的结果。JDK也是通过使用LinkedBlockingQueue 与 特殊的SynchronousQueue,实现自己的控制效果。

那我可不可以自己封装一个Queue,在插入时增加以下逻辑呢?

如果当前有空闲线程等待接客,则把任务加入队列让孩儿们去抢。
如果没有空闲的了,总线程数又没到达max,那就返回false,让Executor去创建线程。
如果总线程数已达到max,则继续把任务加入队列缓冲一下。
如果缓冲队列也满了,抛出拒绝异常。

说白了就是这么简单。

 

Tomcat的实现

Tomcat的TaskQueue实现:

 

public class TaskQueue extends LinkedBlockingQueue {

@Override

public boolean offer(Runnable o) {

if (parent . getPoolSize() == parent . getMaximumPoolSize()) return super . offer(o);


if (parent . getSubmittedCount() < parent.getPoolSize()) return super . offer(o);


if (parent . getPoolSize() < parent . getMaximumPoolSize()) return false;


return super.offer(o);


}

}

非常简单的代码,唯一要说明的是,如何判断当前有没有空闲的线程等待接客。JDK的CachedPool是靠特殊的零长度的SynchronousQueue实现。而Tomcat则靠扩展Executor ,增加一个当前请求数的计数器,在execute()方法前加1,再重载afterExecute()方法减1,然后判断当前线程总数是否大于当前请求总数就知道有咩有围观群众。

 

更进一步

因为相信Tomcat这种百年老店,我们就不自己写这个池了,把Tomcat实现里一些无关需求剥掉即用。

但Tomcat就完美了吗?

首先,TaskQueue的offer()里,调用了executor.getPoolSize(),这是个有锁的函数,这是最遗憾的地方,在大家都在嫌线程池里一条队列锁得太厉害,像ForkJoinPool或Netty的设计都是一个线程一个队列时,这个有锁的函数相当碍眼。而且,最过分的是,Tomcat居然一口气调了三次(在Tomcat9 M9依然如此)。反复看了下,不求那么精准的话貌似一次就够了,真的有并发的变化的情况,executor里还有个处理RejectException,把任务重新放回队列的保险。

最后,说说缓冲队列的两种玩法:

一种是队列相对比较长,比如4096,主线程把任务丢进去就立刻返回了,如果队列满了就直接报拒绝异常。

一种是队列相对比较短的,比如512,如果满了,主线程就以queue.force(command, timeout)等在那里等队列有空,等到超时才报拒绝异常。

Tomcat的机制支持这两种玩法,自己设置就好。

 
文章可能还要修改,转载请保留原文链接,否则视为侵权:
http://calvin1978.blogcn.com/a ... .html 查看全部
原文链接:http://calvin1978.blogcn.com/a ... .html
 因由

说起线程池,大家可能受连接池的印象影响,天然的认为,它应该是一开始有core条线程,忙不过来了就扩展到max条线程,闲的时候又回落到core条线程,如果还有更高的高峰,就放进一个缓冲队列里缓冲一下。

有些整天只和SSH打交道的同学,可能现在还是这样认为的。

无情的现实就是,JDK只有两种典型的线程池,FixedPool 与 CachedPool:

FixedPool固定线程数,忙不过来的全放到无限长的缓冲队列里。
CachedPool,忙不过来时无限的增加临时线程,闲时回落,没有缓冲队列。

在Java ThreadPool的正确打开方式里,建议了如何设置,避免上面两句吓人的“无限”。但无论怎么配,都无法用现成的东西,配出文章一开始的想象图来。

但不得不说,这幅想象图还是比较美好的,特别对于偶有卡顿,偶有不可预测高峰的业务线程池来说。

当然,也有人说请求积压在最后的缓冲队列里不好控制,看具体业务场景了,缓冲队列也有不同的玩法(后详)。

 

原理

我们的同事老王,研究了一番ThreadPool的机制后,提出了自己实现队列的方式。碰巧,Tomcat也正是这么做的,比起Jetty完全自己写线程池,Tomcat基于JDK的线程池稍作定制,要斯文一些。

JDK线程池的逻辑很简单( 更详细描述还是见Java ThreadPool的正确打开方式 )

- 前core个请求,来一个请求就创建一个线程。
- 之后,把请求插入缓冲队列里让所有的线程去抢;如果插入失败则创建新线程。
- 如果达到max条线程了,抛出拒绝异常。

貌似控制的枢纽都在第2句那里--队列插入的结果。JDK也是通过使用LinkedBlockingQueue 与 特殊的SynchronousQueue,实现自己的控制效果。

那我可不可以自己封装一个Queue,在插入时增加以下逻辑呢?

如果当前有空闲线程等待接客,则把任务加入队列让孩儿们去抢。
如果没有空闲的了,总线程数又没到达max,那就返回false,让Executor去创建线程。
如果总线程数已达到max,则继续把任务加入队列缓冲一下。
如果缓冲队列也满了,抛出拒绝异常。

说白了就是这么简单。

 

Tomcat的实现

Tomcat的TaskQueue实现:

 

public class TaskQueue extends LinkedBlockingQueue {

@Override

public boolean offer(Runnable o) {

if (parent . getPoolSize() == parent . getMaximumPoolSize()) return super . offer(o);


if (parent . getSubmittedCount() < parent.getPoolSize()) return super . offer(o);


if (parent . getPoolSize() < parent . getMaximumPoolSize()) return false;


return super.offer(o);


}

}

非常简单的代码,唯一要说明的是,如何判断当前有没有空闲的线程等待接客。JDK的CachedPool是靠特殊的零长度的SynchronousQueue实现。而Tomcat则靠扩展Executor ,增加一个当前请求数的计数器,在execute()方法前加1,再重载afterExecute()方法减1,然后判断当前线程总数是否大于当前请求总数就知道有咩有围观群众。

 

更进一步

因为相信Tomcat这种百年老店,我们就不自己写这个池了,把Tomcat实现里一些无关需求剥掉即用。

但Tomcat就完美了吗?

首先,TaskQueue的offer()里,调用了executor.getPoolSize(),这是个有锁的函数,这是最遗憾的地方,在大家都在嫌线程池里一条队列锁得太厉害,像ForkJoinPool或Netty的设计都是一个线程一个队列时,这个有锁的函数相当碍眼。而且,最过分的是,Tomcat居然一口气调了三次(在Tomcat9 M9依然如此)。反复看了下,不求那么精准的话貌似一次就够了,真的有并发的变化的情况,executor里还有个处理RejectException,把任务重新放回队列的保险。

最后,说说缓冲队列的两种玩法:

一种是队列相对比较长,比如4096,主线程把任务丢进去就立刻返回了,如果队列满了就直接报拒绝异常。

一种是队列相对比较短的,比如512,如果满了,主线程就以queue.force(command, timeout)等在那里等队列有空,等到超时才报拒绝异常。

Tomcat的机制支持这两种玩法,自己设置就好。

 
文章可能还要修改,转载请保留原文链接,否则视为侵权:
http://calvin1978.blogcn.com/a ... .html

Java应用调优之-总览导航

默认分类admin 发表了文章 • 0 个评论 • 141 次浏览 • 2017-01-17 11:34 • 来自相关话题

原文链接:http://calvin1978.blogcn.com/a ... .html
 1. 目标

调优之前,先认清自己的目标。

1. QPS
地球人都懂的指标。

2. 平均值,中值响应时间
其实,中值比平均值更好,因为平均值可能被少量极高的最高值拉高,而中值是所有结果值里位置排在中间的那个值。

3. 99 . 99%响应时间
99. 99%延时=30ms的意思就是,99 . 99%的请求,都能在30ms毫秒以内完成。
对于服务SLA的评估,毛刺率也很重要,一个服务可能平均响应时间是2ms,但遇上GC等Java绕不过去的坎,20ms也可以接受,但如果是200ms,就要找原因了。

4. CPU消耗
CPU能省就省,特别是docker时代,省了自己就方便了别人。
但CPU也要能压上去,否则就是哪里有锁了。

5. 内存、网络IO、磁盘IO消耗
特定场景特定需求。
 

2. 为什么说调优是艺术

说调优是艺术,因为它源于深厚的知识,丰富的经验和敏锐的直觉 -《Java性能权威指南》
所有网上的文章,都只能拓展你知识的深度。

《认清性能问题》 by 瞬息之间翻译

 

3. 文章一览
3.1 基本的准备

另一份Java应用调优指南之-工具篇
另一份Java应用调优指南 - 前菜
从dstat理解Linux性能监控体系
AWK处理日志入门
从Apache Kafka 重温文件高效读写

 

3.2 面向GC编程

StringBuilder在高性能场景下的正确用法
欠帐1:通用的节约内存的方法,《面向GC的Java编程珠玉》在前,再补充一些

 

3.3 并发与锁

欠账2:锁优化的一般技巧 -- 分散锁,缩短锁,读写锁,ThreadLocal,CMS
Tomcat线程池,更符合大家想象的可扩展线程池
Java ThreadPool的正确打开方式

 

3.4 JVM优化

关键业务系统的JVM启动参数推荐 2.0版
JVM的Stop The World,安全点,黑暗的地底世界
在你的代码之外,响应时间超时的三个追查方向(上)
在你的代码之外,响应时间超时的三个追查方向(下)
欠帐4: JIT编译优化,内联

 

3.5 其他技巧

Java ThreadPool的正确打开方式
Tomcat线程池,更符合大家想象的可扩展线程池
陌生但默默一统江湖的MurmurHash
高性能场景下,HashMap的优化使用建议
估计长期欠帐5: 静态异常,其他小技巧

 

4. 资料
4.1 书籍

《Java性能权威指南》
比起多年前那部调优圣经,讲得更加深入,也更加贴近现在的JDK。

《深入理解 Java 虚拟机 第2版》
理解虚拟机并不是那么难,Java程序员来说,很多知识其实是必须的。另外还有几本类似主题的书,忽然一下子都出来了。

《大话Java性能优化》
把书列于此不代表我完全同意书中的所有观点,仅供参考,辩证阅读。 查看全部
原文链接:http://calvin1978.blogcn.com/a ... .html
 1. 目标

调优之前,先认清自己的目标。

1. QPS
地球人都懂的指标。

2. 平均值,中值响应时间
其实,中值比平均值更好,因为平均值可能被少量极高的最高值拉高,而中值是所有结果值里位置排在中间的那个值。

3. 99 . 99%响应时间
99. 99%延时=30ms的意思就是,99 . 99%的请求,都能在30ms毫秒以内完成。
对于服务SLA的评估,毛刺率也很重要,一个服务可能平均响应时间是2ms,但遇上GC等Java绕不过去的坎,20ms也可以接受,但如果是200ms,就要找原因了。

4. CPU消耗
CPU能省就省,特别是docker时代,省了自己就方便了别人。
但CPU也要能压上去,否则就是哪里有锁了。

5. 内存、网络IO、磁盘IO消耗
特定场景特定需求。
 

2. 为什么说调优是艺术

说调优是艺术,因为它源于深厚的知识,丰富的经验和敏锐的直觉 -《Java性能权威指南》
所有网上的文章,都只能拓展你知识的深度。

《认清性能问题》 by 瞬息之间翻译

 

3. 文章一览
3.1 基本的准备

另一份Java应用调优指南之-工具篇
另一份Java应用调优指南 - 前菜
从dstat理解Linux性能监控体系
AWK处理日志入门
从Apache Kafka 重温文件高效读写

 

3.2 面向GC编程

StringBuilder在高性能场景下的正确用法
欠帐1:通用的节约内存的方法,《面向GC的Java编程珠玉》在前,再补充一些

 

3.3 并发与锁

欠账2:锁优化的一般技巧 -- 分散锁,缩短锁,读写锁,ThreadLocal,CMS
Tomcat线程池,更符合大家想象的可扩展线程池
Java ThreadPool的正确打开方式

 

3.4 JVM优化

关键业务系统的JVM启动参数推荐 2.0版
JVM的Stop The World,安全点,黑暗的地底世界
在你的代码之外,响应时间超时的三个追查方向(上)
在你的代码之外,响应时间超时的三个追查方向(下)
欠帐4: JIT编译优化,内联

 

3.5 其他技巧

Java ThreadPool的正确打开方式
Tomcat线程池,更符合大家想象的可扩展线程池
陌生但默默一统江湖的MurmurHash
高性能场景下,HashMap的优化使用建议
估计长期欠帐5: 静态异常,其他小技巧

 

4. 资料
4.1 书籍

《Java性能权威指南》
比起多年前那部调优圣经,讲得更加深入,也更加贴近现在的JDK。

《深入理解 Java 虚拟机 第2版》
理解虚拟机并不是那么难,Java程序员来说,很多知识其实是必须的。另外还有几本类似主题的书,忽然一下子都出来了。

《大话Java性能优化》
把书列于此不代表我完全同意书中的所有观点,仅供参考,辩证阅读。

在你的代码之外,服务时延过长的三个追查方向(上)

默认分类admin 发表了文章 • 0 个评论 • 126 次浏览 • 2017-01-17 11:29 • 来自相关话题

服务化体系里一般都有着严格的超时设定,为业务部门排查那些毛刺慢响应,也是基础架构部门的专家坐诊服务之一。

有时候,即使你的代码写的很努力了,但还是会出现慢响应,因为这本就是艰难的世界。 本文从三个方向上各举一些例子:

第一方面主要是热身,更加有趣的两方面见下集。

 

第一方面,操作系统篇

准备知识: 《从Apache Kafka 重温文件高效读写》中Swap 和 PageCache的部分。

1. 禁用swap

Linux有个很怪的癖好,当内存不足时,看心情,有很大机率不是把用作IO缓存的Page Cache收回,而是把冷的应用内存page out到磁盘上(具体算法看准备知识)。当这段内存重新要被访问时,再把它重新page in回内存(所谓的主缺页错误),这个过程进程是停顿的。增长缓慢的老生代,池化的堆外内存,都可能被认为是冷内存,用 cat /proc/[pid]/status 看看 VmSwap的大小, 再dstat里看看监控page in发生的时间。

在 /etc/sysctl.conf 放入下面一句,基本可以杜绝swap。设成0会导致OOM,案例在此,有些同学就设成1,喜欢就好。

 

vm.swappiness = 10

 

2. 加快Page Cache Flush的频率

又是一个Linux自己的奇怪设置,Linux的Page Cache机制说来话长(还是看看准备知识), 简单说IO其实都默认不是先写磁盘,而是写进Page Cache内存,inode脏了30秒,或脏数据达到了10%可用内存(Free+PageCache -mmap),才开始起flusher线程写盘。

我们的生产机内存起码都剩20G的样子,想想要以普通硬盘100MB/s级别的速度,一次写2G文件的速度....好在一般都达不到这个条件,一般是由几个日志文件轮流30秒触发,一次写几百M,花个三秒左右的时间。

文章里说,后台刷盘线程不会阻塞应用程序的write(2)。但是,

应用的write 过程是这样的:
锁inode -> 锁page -> 写入page -> 解锁page -> 解锁inode -> 锁inode page -> 写inode page -> 解锁inode page

而flusher的过程是这样的:
锁page -> 将page放进IO队列,等待IO调度写盘完成返回 -> 解锁page

可见,还是有锁,IO调度器也不是绝对公平,当IO繁忙,应用还是会发生阻塞 。

我们的做法是用一个100MB的绝对值来代替可用内存百分比来作为阀值。

在 /etc/sysctl.conf 里加入

 

vm.dirty_background_bytes = 104857600

在第二篇举的JVM停顿的例子,全和IO相关,即使不做后面JVM的调优,光降低这个阀值,也能大大缓解。

当然什么值是最优,必须根据机器配置,应用特性来具体分析。

 

3. 网络参数

太多可配置的地方,可以参考阿里云团队的一个很好的文章 Linux TCP队列相关参数的总结。 还是那句,不能看着文章就开始设,必须根据自己的情况。

比如我们自己设置网卡软中断队列的CPU亲和度:

平时网卡中断可能只用一个核来响应,在大流量下那个核会跑满。
运行irqbalance,也只是用到了1个CPU,12个核。
最后自己设定24条网卡中断队列对应24个核,效果最佳。。。。。。但你的情况就不一定一样啊。 查看全部
服务化体系里一般都有着严格的超时设定,为业务部门排查那些毛刺慢响应,也是基础架构部门的专家坐诊服务之一。

有时候,即使你的代码写的很努力了,但还是会出现慢响应,因为这本就是艰难的世界。 本文从三个方向上各举一些例子:

第一方面主要是热身,更加有趣的两方面见下集。

 

第一方面,操作系统篇

准备知识: 《从Apache Kafka 重温文件高效读写》中Swap 和 PageCache的部分。

1. 禁用swap

Linux有个很怪的癖好,当内存不足时,看心情,有很大机率不是把用作IO缓存的Page Cache收回,而是把冷的应用内存page out到磁盘上(具体算法看准备知识)。当这段内存重新要被访问时,再把它重新page in回内存(所谓的主缺页错误),这个过程进程是停顿的。增长缓慢的老生代,池化的堆外内存,都可能被认为是冷内存,用 cat /proc/[pid]/status 看看 VmSwap的大小, 再dstat里看看监控page in发生的时间。

在 /etc/sysctl.conf 放入下面一句,基本可以杜绝swap。设成0会导致OOM,案例在此,有些同学就设成1,喜欢就好。

 

vm.swappiness = 10

 

2. 加快Page Cache Flush的频率

又是一个Linux自己的奇怪设置,Linux的Page Cache机制说来话长(还是看看准备知识), 简单说IO其实都默认不是先写磁盘,而是写进Page Cache内存,inode脏了30秒,或脏数据达到了10%可用内存(Free+PageCache -mmap),才开始起flusher线程写盘。

我们的生产机内存起码都剩20G的样子,想想要以普通硬盘100MB/s级别的速度,一次写2G文件的速度....好在一般都达不到这个条件,一般是由几个日志文件轮流30秒触发,一次写几百M,花个三秒左右的时间。

文章里说,后台刷盘线程不会阻塞应用程序的write(2)。但是,

应用的write 过程是这样的:
锁inode -> 锁page -> 写入page -> 解锁page -> 解锁inode -> 锁inode page -> 写inode page -> 解锁inode page

而flusher的过程是这样的:
锁page -> 将page放进IO队列,等待IO调度写盘完成返回 -> 解锁page

可见,还是有锁,IO调度器也不是绝对公平,当IO繁忙,应用还是会发生阻塞 。

我们的做法是用一个100MB的绝对值来代替可用内存百分比来作为阀值。

在 /etc/sysctl.conf 里加入

 

vm.dirty_background_bytes = 104857600

在第二篇举的JVM停顿的例子,全和IO相关,即使不做后面JVM的调优,光降低这个阀值,也能大大缓解。

当然什么值是最优,必须根据机器配置,应用特性来具体分析。

 

3. 网络参数

太多可配置的地方,可以参考阿里云团队的一个很好的文章 Linux TCP队列相关参数的总结。 还是那句,不能看着文章就开始设,必须根据自己的情况。

比如我们自己设置网卡软中断队列的CPU亲和度:

平时网卡中断可能只用一个核来响应,在大流量下那个核会跑满。
运行irqbalance,也只是用到了1个CPU,12个核。
最后自己设定24条网卡中断队列对应24个核,效果最佳。。。。。。但你的情况就不一定一样啊。

在你的代码之外,服务时延过长的三个追查方向(下)

默认分类admin 发表了文章 • 0 个评论 • 134 次浏览 • 2017-01-17 11:28 • 来自相关话题

本文含一些比较有趣的地方,所以和上一篇截开,免得大家看着swap,page cache没劲就把文章关了。

 

第二方面 : JVM篇 — 高IO波动下的JVM停顿

准备知识:JVM的Stop The World,安全点,黑暗的地底世界

在压测中非常偶然的超时,但CPU正常,网络正常,只有IO在刷盘时偶然写个几秒。那,就继续怀疑IO吧。

第一步,日志都异步写了吗?而且,异步队列满时会选择丢弃吗?

Logback在异步日志队列满时,默认并不是丢弃的而是阻塞等待的,需要自己配置。

或者干脆自己重新写一个更高效的异步Appender。

 

第二步,探究高IO 如何造成JVM停顿

有时GC日志都很正常,只有十几毫秒GC停顿,加上-XX:+PrintGCApplicationStoppedTime 后,真正的停顿才现形。

又或者像下面这样的,user+sys很低,但real很高,因为是多线程并发GC,所以real本来的时间应该是user+sys的十分之一不到的。


[ParNew: 1767244K->134466K(1887488K), 0.0331500 secs] 1767244K->134466K(3984640K), 0.0332520 secs] [Times: user=1.54 sys=0.00, real=1.04 secs]

但日志都异步了呀,JVM和IO还有什么关系?

来吧,用lsof彻底检查一下,去掉那些网络的部分,jar和so的文件

 

lsof -p $pid|grep /|grep -v ".jar"|grep -v ".so"

撇开熟悉的日志文件,发现这两个文件:


/tmp/hsperfdata_calvin/44337
/**我是马赛克**/**公司安全部门别找我**/myapp/logs/gc.log


 

第三步,GC日志

一拍脑袋,对哦,GC日志,如果它被锁住,GC的安全点就出不去哦......

但也不能不写GC日志啊,怎么办?

好在,早有知识储备,详见 Linux下tempfs及/dev/shm原理与应用。 简单说,/dev/shm 就是个默认存在,权限开放的,拿内存当硬盘用的分区,最大上限是总内存的一半,当系统内存不足时会使用swap。板着指头算了一下,由于每次JVM重启时GC日志都会被重置(所以你在启动脚本里总会先把GC日志备份下来),那以一秒写一条日志的速度,一点内存就够写几年了,于是放心的把日志指向它。

 

-Xloggc:/dev/shm/gc-myapplication.log

怎么好像从来没有人关心过GC日志被锁住的? 后来才发现,原来网上还是有Linkedin的同学关注过的:Eliminating Large JVM GC Pauses Caused by Background IO Traffic

 

第四步,hsperfdata文件

改完之后继续运行,超时虽然少了,还是有啊~~~

等等,还有一个文件什么鬼?

/tmp/hsperfdata_$user/$pid

涨知识的时刻到了。原来JVM在安全点里会将statistics写入这个文件,给jps,jstats命令用的。它本来已经是MMAP将文件映射到内存了,读写文件本不会锁,但无奈还是要更新inode元信息,在这里被锁了。在安全点里被锁。。。整个JVM就被锁了。

真不知道,JVM里原来还默默做了这事情。。。

解决办法,又把它指向/dev/shm? 可惜不行。所以,咬咬牙,把它禁止了,反正生产上都靠JMX,用不上jstats和jps。 有两种方式禁用它,据说-XX:-UsePerfData 会影响JVM本身做的优化,所以:

 

-XX:+PerfDisableSharedMem

又是哪个疑问,为什么很少听到有人说的,就我们全碰上了?其实有的,比如Cassandra就禁止了它,见JVM statistics cause garbage collection pauses

高IO可以造成各种问题,如果参考上篇,将Linux的刷盘阀值降低,可以治标不治本的缓解很多症状。
 

第三方面 : 后台辅助程序篇

把前面都都改完了,在线下压测也很棒了,在生产环境里以30ms为目标,还是会有非GC时段的超时,这是为什么呢?

再换个思路,是不是被其他程序影响了?虽然生产上每台机只会部署一个应用,但还有不少监控程序,日志收集程序在跑呀。

运行pidstat 1监控,这些程序不会吃满所有CPU啊。等等,pidstat 以秒来单位运行,见 从dstat理解Linux性能监控体系 ,那毫秒级的峰值它是监控不到的。

又到了发挥想象力乱猜的时刻,主要是看着收集日志的Flueme怎么都不顺眼,看久了忽然发现,Flueme的启动参数几乎是裸奔的,没有配什么GC参数。。。那么按ParallelGCThreads=8+( Processor - 8 ) ( 5/8 )的公式,24核的服务器,flueme 在Young GC时会有18个比较高优先级的线程在狂抢CPU,会不会是它把CPU给抢得太多了?

反正Flueme是后台收集日志的,停久一点也没所谓,所以加上-XX: ParallelGCThreads=4把它的并发GC线程数降低

参数修改前 15分钟内, 大于30ms的业务调用173次
参数修改后 246分钟内, 大于30ms业务调用 41次

应用被后台老王害了的典型例子。。。。。。。

 
本文内容薛院长亦有贡献,他基于strace的分析方法更工程化,这里就偷懒不写了。

调优路漫漫,超时还没彻底消失,比如偶然会出现SYS time较高的GC,系统又没有Swap ,未完待续。 查看全部
本文含一些比较有趣的地方,所以和上一篇截开,免得大家看着swap,page cache没劲就把文章关了。

 

第二方面 : JVM篇 — 高IO波动下的JVM停顿

准备知识:JVM的Stop The World,安全点,黑暗的地底世界

在压测中非常偶然的超时,但CPU正常,网络正常,只有IO在刷盘时偶然写个几秒。那,就继续怀疑IO吧。

第一步,日志都异步写了吗?而且,异步队列满时会选择丢弃吗?

Logback在异步日志队列满时,默认并不是丢弃的而是阻塞等待的,需要自己配置。

或者干脆自己重新写一个更高效的异步Appender。

 

第二步,探究高IO 如何造成JVM停顿

有时GC日志都很正常,只有十几毫秒GC停顿,加上-XX:+PrintGCApplicationStoppedTime 后,真正的停顿才现形。

又或者像下面这样的,user+sys很低,但real很高,因为是多线程并发GC,所以real本来的时间应该是user+sys的十分之一不到的。


[ParNew: 1767244K->134466K(1887488K), 0.0331500 secs] 1767244K->134466K(3984640K), 0.0332520 secs] [Times: user=1.54 sys=0.00, real=1.04 secs]

但日志都异步了呀,JVM和IO还有什么关系?

来吧,用lsof彻底检查一下,去掉那些网络的部分,jar和so的文件

 

lsof -p $pid|grep /|grep -v ".jar"|grep -v ".so"

撇开熟悉的日志文件,发现这两个文件:


/tmp/hsperfdata_calvin/44337
/**我是马赛克**/**公司安全部门别找我**/myapp/logs/gc.log


 

第三步,GC日志

一拍脑袋,对哦,GC日志,如果它被锁住,GC的安全点就出不去哦......

但也不能不写GC日志啊,怎么办?

好在,早有知识储备,详见 Linux下tempfs及/dev/shm原理与应用。 简单说,/dev/shm 就是个默认存在,权限开放的,拿内存当硬盘用的分区,最大上限是总内存的一半,当系统内存不足时会使用swap。板着指头算了一下,由于每次JVM重启时GC日志都会被重置(所以你在启动脚本里总会先把GC日志备份下来),那以一秒写一条日志的速度,一点内存就够写几年了,于是放心的把日志指向它。

 

-Xloggc:/dev/shm/gc-myapplication.log

怎么好像从来没有人关心过GC日志被锁住的? 后来才发现,原来网上还是有Linkedin的同学关注过的:Eliminating Large JVM GC Pauses Caused by Background IO Traffic

 

第四步,hsperfdata文件

改完之后继续运行,超时虽然少了,还是有啊~~~

等等,还有一个文件什么鬼?

/tmp/hsperfdata_$user/$pid

涨知识的时刻到了。原来JVM在安全点里会将statistics写入这个文件,给jps,jstats命令用的。它本来已经是MMAP将文件映射到内存了,读写文件本不会锁,但无奈还是要更新inode元信息,在这里被锁了。在安全点里被锁。。。整个JVM就被锁了。

真不知道,JVM里原来还默默做了这事情。。。

解决办法,又把它指向/dev/shm? 可惜不行。所以,咬咬牙,把它禁止了,反正生产上都靠JMX,用不上jstats和jps。 有两种方式禁用它,据说-XX:-UsePerfData 会影响JVM本身做的优化,所以:

 

-XX:+PerfDisableSharedMem

又是哪个疑问,为什么很少听到有人说的,就我们全碰上了?其实有的,比如Cassandra就禁止了它,见JVM statistics cause garbage collection pauses

高IO可以造成各种问题,如果参考上篇,将Linux的刷盘阀值降低,可以治标不治本的缓解很多症状。
 

第三方面 : 后台辅助程序篇

把前面都都改完了,在线下压测也很棒了,在生产环境里以30ms为目标,还是会有非GC时段的超时,这是为什么呢?

再换个思路,是不是被其他程序影响了?虽然生产上每台机只会部署一个应用,但还有不少监控程序,日志收集程序在跑呀。

运行pidstat 1监控,这些程序不会吃满所有CPU啊。等等,pidstat 以秒来单位运行,见 从dstat理解Linux性能监控体系 ,那毫秒级的峰值它是监控不到的。

又到了发挥想象力乱猜的时刻,主要是看着收集日志的Flueme怎么都不顺眼,看久了忽然发现,Flueme的启动参数几乎是裸奔的,没有配什么GC参数。。。那么按ParallelGCThreads=8+( Processor - 8 ) ( 5/8 )的公式,24核的服务器,flueme 在Young GC时会有18个比较高优先级的线程在狂抢CPU,会不会是它把CPU给抢得太多了?

反正Flueme是后台收集日志的,停久一点也没所谓,所以加上-XX: ParallelGCThreads=4把它的并发GC线程数降低

参数修改前 15分钟内, 大于30ms的业务调用173次
参数修改后 246分钟内, 大于30ms业务调用 41次

应用被后台老王害了的典型例子。。。。。。。

 
本文内容薛院长亦有贡献,他基于strace的分析方法更工程化,这里就偷懒不写了。

调优路漫漫,超时还没彻底消失,比如偶然会出现SYS time较高的GC,系统又没有Swap ,未完待续。