一、java代码优化
1.方返参数与返回值不使用Map结构,很难理解,后期参数变化很容易发生故障
2.Integer类型与字符串拼接时容易产生性能损耗,如area.getProvinceId+"#",编译后的代码为StringBuilder拼接,拼接Integer值时进行toString操作需要转换
成char数组,需要知道数组的长度进行stringSize进行循环操作,另外在append的时候也有一些性能损耗如:ensureCapacityInternal方法对buf进行扩容操作
在使用字符串拼接标识key时比使用对象作为key性能会差好几倍
3.计时使用System.nanoTime()用纳秒计是,比使用System.currentTimeMillis毫秒更准确,执行时对系统影响更小
4.使用jvisualvm了解应用运行时虚拟机内部的情况
5.使用jmh进行基准测试,jmh针对细粒度具体的代码块算法等进行压力测试
二、字符串和数字操作
1.String不可变、针对字符串进行subString、concat、replace都是通过char[]数组构造新的字符串
2.通过字符串构造字符串性能>char[]>byte[],通过byte构造字符串涉及到转码对cpu进行消耗,在涉及传输字段时尽量使用long,int等能不用字符串不用字符串
3.字符串拼接优化
String a="hello";
String b="world";
String str = a+b;
虚拟机会编译成如下代码:
String str=new StringBuilder().append(a).append(b).toString();
但是以下写法不会进行jit优化,性能低于上面代码
StringBuilder sb = new StringBuilder();
sb.append(a);
sb.append(b);
4.字符串格式化如:
Formatter有强大的功能,每次都需要对输入的类型为字符串的format参数进行预编译操作,预编译成某个中间格式,再进行输出,效率非常低
String formatString = "hello %s, nice to meet you";
String value = String.format(formatString, para);
//字符串拼接的性能要高于格式化,另外使用日志框架如slf4j使用了类似messageFormat方式性能较差
//sl4j影响性能的另外一个地方在于当输入参数是3个以上时,会使用可变数组,构造完毕后才会调用debug方法,性能也略有下降
logger.debug("参数 {}", "abc");
5.String.indexOf(‘t’)性能好于String.indexOf(“t”)即接收char性能高于接收String ,contains方法比matcher性能高
6.使用正则预先编译后再使用能提高一些性能:
Pattern pattern = Pattern.compile("120");
String str = pattern.matcher(this.str).replaceAll("199")
7.intern方法调用是一个耗时的操作,使用-xx:+UseG1GC -XX:+UseStringDeduplication,虚拟机将尝试在垃圾收集过程中消除重复的字符串
8.使用前缀树对输入的内容进行过滤比使用replace替换效率高
9.List list = new ArrayList<>(),list.add(1);int自动装箱成Integer,装箱对性能影响并不是很大,但创建过多的对象会加大垃圾回收的负担,有很多开源工具提供了避免自动装箱的int专有集合类,如开源的工具jodd提供了IntHashMap、IntArrayList类,JDK提供了IntSTream
10.浮点型变量在进行计算的时候会出现丢失精度问题。通常有两个方法解决,一是使用Long表示帐户余额(以分为单位),二是使用BigDecimal来解决这类问题,注意使用BigDecimal要使用字符串来构造才能保证精度不丢失,在跨服务访问中最好使用long
三、并发编程和异步编程
1.SimpleDateFormat线程不安全,如format中calendar.setTime(date),因为calendar为类变量,所以在多线程情况下会出问题,线程1设置date为2020-11-11,线程2设置date为2020-12-22,并发情况下线程1和线程二的结果错乱
2.意指令重排序问题,另外需要考虑内存可见性、多核系统,线程对变量的修改是在工作缓存中操作的,并没有刷新到内存,因为其他线程对变量讯取不一定是修改后的。
3.主内存与工作内存是抽象概念,对java来说,主内存指的是堆内存,工作内存指的是CPU的寄存器或高速缓存
4.synchronized是java内置锁,使用时有如下缺点:
5.ThreadPooExecutor使用BlockingQueue保存任务该队列和线程大小有如下交互:
如果当前线程数小于核心线程数,则Executor会创建线程用于执行任务,而不会把任务交给队列;如果当前线程数大于核心线程数,则Executor会把任务交给队列,而不会创建线程。最糟糕的情况是,如果任务无法进入队列,并且当前线程数小于最大线程数,则会创建线程用于执行任务;如果任务无法进入队列,且当前线程数大于最大线程数,则该任务会被拒绝
6.thenCombine方法会把两个CompletionStage的任务完成后,再把两个任务的结果一起交给thenCombine来处理,如:future1.thenCombine(future2, new BiFunction
thenAccept系列方法可以用于接收CompletableFuture任务的处理结果
thenRun与thenAccept一样,但不关心上一个任务的执行结果
四、代码性能优化
1.int转string是一个耗时操作,可以在代码中实现一个CommonUtil使用数组按int值作为下标缓存1024个转好的String的字符串类型数字
2.native方法System.arraycopy,IdentityHashMap(判断Map中的两个key是否相等时,只通过==来判断,而不通过equals,也就是说,如果两个key相同,那么这两个key必须是同一个对象。)
3.SimpleDateFormat是线程不安全的,可以写一个CommonUtil 使用ThreadLocal缓存格式化模板
jdk8提供了线程安全的DateTimeFormatter可以使用
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd")
LocalDateTime now = LocalDateTime.now();
String str = format.format(now)
4.在条件判断中,如果有较多分支的判断,switch语句通常比if语句效率更高,if语句每次取出变量进行比较,而switch语句只需要取出一次变量,通过tableswitch即按数组的方式进行索引,如果变量范围过大则使用lookup switch取代即会逐个寻找分支或使用二分法查找
5.优先使用局部变量,当存取类变量的时候,java使用虚拟机指令GETFIELD获取类变量,如果存取方法的变量,则通过出栈操作获取变量,GETFIELD从Heap中取值,速度较慢,而出栈速度快
6.预处理指对于反复调用的代码,可以尝试提取出公共的只读代码块,处理一次并保留处理结果,反复调用的时候,直接引用处理结果即可,这样避免每次都处理公共内容
7.预分配,如:Arrays.copys等提前分配数组长度等内存
8.预编译,如日志框架中使用的{}占位符
9.谨慎使用Exception,返回异常码比抛出异常性能高出四个数量级,主要是消耗在构造异常栈的fillInStackTrace是一个Native方法,虚拟机会对某个方法频繁抛出某些异常做FastThrow异常栈信息不会被填写,以下异常会使用FastThrow来进行优化
10.批处理:jdbc批处理操作
11.展开循环
12.静态方法调用,在Java中,实例方法需要维护虚方法表以支持多态,相比静态方法,调用实例方法会有额外的查询虚方清的开销,可以使用类名.静态方法进行静态方法调用
13.EnumMap是键值为枚举类型的专用Map实例,与HashMap相比,有较快的存取速度
14.位运算
15.通过Method是一个耗时操作,也可以预先保存Method,声明一个Map作为缓存
Map> caches = new ConcurrentHashMap<>();
另一种高效方式使用LambdaMetafactory进行反射访问对象属性值
16.压缩,微服务之间传数据最好使用压缩后的数据,可以对key压缩或值使用int代替,另外可以使用zip、gzip等方式压缩
17.可变数组,针对可变参数字节码实际上构造了一个新的数组,对性能有一定的影响
18.System.currentTimeMills();返回的精度与指定操作系统有关,比如在windows中可能有10ms差距,在精确计时的情况下使用System.nanoTime()方法
19.在Jdk7之前使用java.util.Random生成伪随机数,如果设置固定种子,那么生成的随机数也是固定的,Random是线程安全的,多线程同时计算随机会数会出现线程竞争,JDK7提供了ThreadRandom在多线程场景下提供了高性能伪随机数的生成
20.错误的优化策略,针对至今在网上出现的错误的优化策略纠正
五 高性能工具
1.高速缓存Caffeine,高性能、高命中率,不是分布式缓存 、不支持持久化,三种淘汰策略:基于大小、基于时间、基于引用,statistics统计信息可以让用户实时监控缓存的健康状态cache.stats();
2.映射工具Selma
通过getset手动复制对象及属性容易忘记,通过jdk对象的序列化和反序列化性能糟糕,Selma是一款高效的对象复制和属性映射工具,会在编译时生成对象复制代码,不过已经习惯使用mapstruct了
3.Json工具jackson
springboot内置的Json序列化反序列化工具使用的是Jackson,三种使用方式
JavaType type = getCollectionType(List.class, User.class)
List listUser = objectMapper.readValue(jsonInput, type);
constructParametricType方法允许构造复杂的泛型类型描述。
List> 使用constructParametricType(List.class,Set.class,User.class);
Map使用constructParametricType(Map.class,String.class,User.class);
4.HikariCP
使用了ConcurrentBag内部构造了sharedList和threadList,优先从threadList中查找可用的Connection,如果没有,那么再从sharedList中查找可用的Connection,如果还没有则等待handoffQueue提供一个
private final CopyOnWriteArrayList sharedList;
private final ThreadLocal> thradList;
private final SynchronousQueue handoffQueue;
使用了ThreadLocal和CopyOnWriteArrayList在很多情况下避免了锁竟争
5.MessagePack
通常用来代替JSON,可以高效地传输数据和存储数据,它比JSON更紧凑,是一种二进制序列格式,编码更精简高效主要用于
由于具备一定的压缩功能有一定的性能消耗
6.ReflectASM
一个非常小的ReflectASM,通过代码生成来提供高性能的反射处理,自动为get/set字段提供访问类,访问类使用字节码操作而不是java的反射技术,因此速度非常快,为了获得更好的性能推荐使用方法名对应的下标来访问方法:
int methodIndex = methodAccess.getIndex("getName");
name = (String) methodAccess.invoke(animal, methodIndex)
FieldAccess只能访问public protected package访问权限字段 ,如果想访问私用字段 ,则可以使用反射功能先放开权限
Field field = Animal.class.getDelcaredField("id");
//开放访问权限
field.setAccessible(true)
//创建一个Animal对象
Animal animal = new Animal();
//设置属性(int)
field.set(animal, 12)