java系统性能优化实战读书学习

一、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内置锁,使用时有如下缺点:

  • 线程获取锁的时候可能无限期待待,不能设置超时
  • 使用synchronized的代码块不能中断
  • 无法获取在等待锁的信息,比如等待锁的数量、等待的是哪些线程、各自等待了多长时间

5.ThreadPooExecutor使用BlockingQueue保存任务该队列和线程大小有如下交互:
如果当前线程数小于核心线程数,则Executor会创建线程用于执行任务,而不会把任务交给队列;如果当前线程数大于核心线程数,则Executor会把任务交给队列,而不会创建线程。最糟糕的情况是,如果任务无法进入队列,并且当前线程数小于最大线程数,则会创建线程用于执行任务;如果任务无法进入队列,且当前线程数大于最大线程数,则该任务会被拒绝

6.thenCombine方法会把两个CompletionStage的任务完成后,再把两个任务的结果一起交给thenCombine来处理,如:future1.thenCombine(future2, new BiFunction<future1结果类型,future2结果类型 ,返回结果类型 >),thenAcceptBoth与thenCombine类似,区别在天真无邪thenAcceptBoth不提供返回值,allOf接收多个CompletableFuture并返回一个新的CompletableFuture,只有当所有的CompletableFuture执行完毕后,CompletableFuture.get才有返回,如果其中有一个抛异常,则get方法返回异常,也可以调用join方法阻塞直到所有的CompletableFuture执行完毕,CompletableFuture.allOf(f1,f2).join();
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来进行优化

  • NullPointerException
  • ArithmeticException
  • ArrayIndexOutOfBoundsExeption
  • ArrayStoreException
  • ClassCastException
    这种优化提高了性能,但会导致异常栈丢失,定位不到错误的代码,避免这种优化可以通过虚拟机参数-XX:-OmitStackTraceInFastThrow

10.批处理:jdbc批处理操作

11.展开循环

12.静态方法调用,在Java中,实例方法需要维护虚方法表以支持多态,相比静态方法,调用实例方法会有额外的查询虚方清的开销,可以使用类名.静态方法进行静态方法调用

13.EnumMap是键值为枚举类型的专用Map实例,与HashMap相比,有较快的存取速度

14.位运算

15.通过Method是一个耗时操作,也可以预先保存Method,声明一个Map作为缓存

Map<Class, Map<String,Method>> 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.错误的优化策略,针对至今在网上出现的错误的优化策略纠正

  • final无法帮助内联 ,早期版本中final可以提醒虚拟机进行内联,但是现在是否内联已经不需要final
  • subString内存泄漏,较早的性能优化书中指出subString容易造成内存泄漏,主要原因是jdk的subString方法会复用String数组,并没有为新的字符串竹成一个新的char数组,如果原来的String特别长,那么新返回的String所占用的空间特别大,Jdk6以后已经取消了subString复用的char数组,而是改成重新生成一个数组
  • 循环优化,外小内大与外大内小由于有了JIT会做Dead-Code消除,消除了循环体,导致结果测试不准,实际使用JMH测试,会发现嵌套循环结果一样
  • 循环中捕捉异常,在循环体中捕捉异常实际上没有什么影响,捕获异常不需要考虑在循环体外还是体内

五 高性能工具
1.高速缓存Caffeine,高性能、高命中率,不是分布式缓存 、不支持持久化,三种淘汰策略:基于大小、基于时间、基于引用,statistics统计信息可以让用户实时监控缓存的健康状态cache.stats();

2.映射工具Selma
通过getset手动复制对象及属性容易忘记,通过jdk对象的序列化和反序列化性能糟糕,Selma是一款高效的对象复制和属性映射工具,会在编译时生成对象复制代码,不过已经习惯使用mapstruct了

3.Json工具jackson
springboot内置的Json序列化反序列化工具使用的是Jackson,三种使用方式

  • DataBind,将Pojo序列化成Json或反列化成Pojo
  • 采用树遍历,JSON被读入JsonNode对象,可以像操作XMLDOM那样读取JSON
  • 采用JsonParser来解析JSON,解析的结果是一串Tokens,采用JsonGenerator来生成Json
    对象如果是第三方提供的,无法在源码中添加注解,则可以为这些对象定义JsonSerializer并注册到ObjectMapper
    对于反序列化成集合,需要告诉JackSon集合类型如
JavaType type = getCollectionType(List.class, User.class)
List<User> listUser = objectMapper.readValue(jsonInput, type);

constructParametricType方法允许构造复杂的泛型类型描述。
List<Set<User>> 使用constructParametricType(List.class,Set.class,User.class);
Map<String,String>使用constructParametricType(Map.class,String.class,User.class);

4.HikariCP
使用了ConcurrentBag内部构造了sharedList和threadList,优先从threadList中查找可用的Connection,如果没有,那么再从sharedList中查找可用的Connection,如果还没有则等待handoffQueue提供一个

private final CopyOnWriteArrayList<T> sharedList;
private final ThreadLocal<List<Object>> thradList;
private final SynchronousQueue<T> handoffQueue;

使用了ThreadLocal和CopyOnWriteArrayList在很多情况下避免了锁竟争

5.MessagePack
通常用来代替JSON,可以高效地传输数据和存储数据,它比JSON更紧凑,是一种二进制序列格式,编码更精简高效主要用于

  • 存在于Redis、Memcache中,因为比JSON小,可以节省内存
  • 持久化到数据库(NoSQL)中

由于具备一定的压缩功能有一定的性能消耗

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)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值