JDK1.6前后的subString方法实现
在JDK1.6 subString内部实现是子String仍然会保留父String的char数组引用,这在一定程度上会造成内存泄露,特别是当有特别长的字符串实际上只需要其特别短的字符串,但是由于引用依赖,GC无法回收,造成父String一直滞留在JVM内存中无法回收。
JDK1.6之后subString内部对其优化,调用拷贝父String的char数组中的子char数组形成一份新的副本,如此子String与父String之间不存在依赖关系,GC能够对不在使用的父String进行GC回收。
循环遍历中如果存在重复创建大量相同的字符串,建议创建缓存池进行对象缓存。
避免使用正则表达式,如必要使用至少要把Pattern进行缓存,避免反正创建Pattern编译。
当需要对一个基本数据类型进行字符串转换应该尽可能使用toString或者String.valueOf(obj) 替换 obj+""。
在进行大文本字符串拼接时,应该为StringBuffer,StringBuilder设置初始化容量值
***
JVM篇
将-Xms与-Xmx设置相同,避免每次垃圾回收完成后JVM重新分配内存。
原则上应该避免太大深度的递归,毕竟递归越深其中间栈帧所产生的数据引用仍然有效,无法被GC清除。
当虚拟机栈中需要存储基本数据或对象引用时需要调整-Xss来避免发生StackOverflowError异常
在虚拟机栈中随着栈帧的pop会对栈帧内的内存进行及时的清理,所以在局部方法内部中其中间变量应该尽可能使用8大基本数据类型才能够随着栈帧结束而立即内存回收。
为了避免频繁触发JVM对基本数据类型进行拆包与装包操作,其中间变量应该尽量使用基本数据类型而不是其包装类型。
JDK默认只缓存-128~+127的Integer和Long 如果超出该范围则会创建出新的对象,如果对计算数据敏感可是适当通过-XX:AutoBoxCacheMax增大缓存范围。
G1与CMS收集器的选择,G1收集器会对堆内内存进行划分成Region,其堆越小则划分的Region数也会越少,在小堆的表现并不会比CMS突出。笔者认为8G以下采用CMS比较合适。目前比较期待Java 11 新加入的ZGC号称可以达到10ms 以下的 GC 停顿。
通过-XX:+AlwaysPreTouch提前初始化好真正的物理内存,而不是需要才进行内存申请初始化。默认未添加该参数时而-Xms、-Xmx只是告诉告诉操作系统需要多少内存,从而避免被其他进程使用,而只有当正直使用时才会进行内存逐渐申请。比如在堆中Eden区进行对象创建又或者Young区转Old区的内存空间。不过该参数也会影响启动时长,随着堆内存越大启动时间也会增大。
JDK监控工具 Jconsole,jProfile,VisualVM 可以通过可视化界面查看JAVA堆内存使用情况进行判断是否需要进行代码优化。
***
线程篇
如何为线程池选择合适线程数?
线程任务一般可以分为计算密集型和IO密集型。
计算密集型CPU处于忙碌中,此时需要做内存数据读写计算,没有任务的阻塞状态。而IO密集型任务,在执行IO时堵塞,CPU处于等待状态,等待过程中系统会将时间片分给其他线程处理。
计算密集型任务线程数最好与系统核心数挂钩,毕竟4核单线程主机在某一时刻只能同时跑4个线程,如果过多的线程数反而会因为切换上下文而耗费更多的任务时间,可以通过调用JDK自带的方法Runtime.availableProcessors获得系统支持的可以核心数。N+1。
对于IO密集型,合适的线程数可以获得良好的性能支持,系统通过将IO阻塞的任务线程的时间片交给未被阻塞的任务线程,通过合适的调度发挥出比单线程更好的性能支持。在选择线程数应该考虑内存支持程度,避免过多的线程数导致内存激增产生OOM。2N+1。
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
一个完整的系统不应只有一个线程池,应该对线程任务进行梳理分类,划分出各自的任务类型以及工作负载来提供多个线程池。
如果需要加锁竟可能使用加锁代码块将锁范围控制在最小范围中,而不是在方法体中加锁。
尽量避免嵌套取锁,容易造成死锁问题。
合理时候使用读写锁来替换synchronized独占锁。
JDK1.8流处理对于大量需要计算的数据时可采用parallelStream进行并发数据处理,可提高处理速度。
多了解各类对象对并发支持性例如HashMap、SimpleDateFormat等。
合理使用ThreadLocal来实现数据在线程本地化。
如果对执行过程没有严格的串行顺序可以采用FutureTask对计算结果统一取值拼装。
***
杂谈篇
为避免循环之间切换,尽量采用小循环嵌套大循环。
避免在循环中大概率抛出异常的代码块进行TryCatch,尽可能放在循环外层TryCatch。
代码块中应尽量使用懒加载,即需要时才进行加载,不需要则不加载。
在JVM级别做适当的缓存级别,可以使用EhCache、Guava Cache。Ehcache适合支持持久化功能,有集群解决方案,而Guava Cache只是一个支持LRU的concurrentHashMap,没有Ehcache那么多特性,只支持增删改查,刷新规则和时效规则设定等最基本的设定。
设置日志输出级别,避免大量无关紧要的日志输出,影响业务系统性能。
SQL调优可以通过索引分析,减少查询字段,限制表读数据等进行分析调优。
数据库瓶颈可以通过读写分离减少单服务器压力,以及通过垂直拆分或水平拆分将数据表数据合理治理。
引入缓存架构减轻数据库压力。
哪些是性能瓶颈的关键点
有些任务需要大量的计算,需要不停地占用CPU资源,导致其他任务抢占CPU的能力变弱而导致响应速度下降,而带来的性能问题。比如任务内过多的重复计算,无限的自循环计算,JVM频繁的FULL GC,多线程做大量的上下文切换等。
一般来说说内存的读写速度非常快,一般不存在性能问题,但是由于内存成本比硬盘高即内存空间是有限的,应该注意内存的范围避免应内存耗尽而导致OOM等问题。
磁盘IO是引起系统性能的一大因素。涉及到数据落地等问题应尽量使用硬盘顺序读写而不是随机读写,顺序读写的读写能力比随机读写能力强大太多。
数据库方面除了必要的SQL优化减少查询时间外如有必要需要引入缓存层减轻数据库压力
合理使用锁减少并发时造成性能损耗
防止瞬间时抛出大量的异常,抛出异常会不停从堆栈内拔取异常信息,非常耗性能。
衡量性能的指标
系统响应时间
从发送请求到接收到数据时所需要的时间
系统吞吐量
单位时间内成功地传送数据的数量大小
负载承受能力
随着并发量的增加最终导致系统抛出大量的异常,整个系统处于不可用的时候。