Java性能调优

2021-09-21  本文已影响0人  知而乐者

String对象是如何实现的?

编译器会对,str+str进行优化,改成StringBuilder,每次+都会new一个新的StringBuilder的对象,所以,建议直接使用StringBuilder来优化

String的内存分配

正则表达式

目前实现正则表达式引擎的方式有两种,DFA(确定有限状态自动机)和NFA(非确定有限状态自动机),对比来看,构造DFA自动机的代价远大于NFA自动机,但DFA自动机执行效率高于NFA,NFA自动机的优势时支持更多的功能,例如,捕获group,环视,占有优先量词等高级功能,在编程语言里使用的正则表达式引擎都是基于NFA实现的

NFA自动机的回溯,NFA实现会引起大量回溯问题,比如ab{1,3}c,source=abbc,匹配b之后会不断的回溯来看是否满足,贪婪模式就是回溯的导火索
贪婪模式就是,如果单独使用+、?、*或者{minx,max}等量词,正则表达式会匹配尽可能多的内容
懒惰模式,在这种情况下正则表达式会尽可能少的重复匹配字符串,如果匹配成功则会继续匹配剩余的字符串,开启懒惰模式可以在量词后面拼接?,如"ab{1,3}c",如果匹配的结果是abc那么一次匹配即可成功
独占模式,独占模式和贪婪模式一样会最大限度的匹配更多的内容,不同的是,在独占模式下,匹配失败就会结束匹配,不会发生回溯的问题,开始独占模式在字符串后面增加个+,如"ab{1,3}+bc"

正则表达式的优化

I/O

DirectBuffer申请的是非JVM的物理内存,所以创建和销毁的代价很高,DirectBuffer申请的内存并不是直接由JVM负责垃圾回收,但在DirectBuffer包装类回收时,会通过JavaReference机智来释放盖内存块

DirectBuffer只优化了用户空间内部的拷贝,MappedByteBuffer时通过本地类调用mmap进行文件内存映射的,map()系统方法会直接将文件从硬盘拷贝到用户空间,只进行一次数据拷贝,从何减少了read方法从硬盘拷贝到内核的这一步

零拷贝,DirectBuffer和MappedByteBuffer

偏向锁

锁状态流转


image.png

上下文切换

上下文切换是线程之间切换的时候保存之前线程的状态,加载新线程的数据,再垃圾回收的时候就可能导致Stop-the-world,就是线程暂停的行为
Linux可以使用 vmstat命令来查看切换的频率(vmstat -pid),如果监视某个应用上下文的切换,就可以使用pidstat命令监控指定进程的Context Switch上下文切换(pidstat -w -l -p <pid> 1 100)

什么时候会导致上下文切换

所以,在多线程中,锁其实不是性能的开销,竞争锁才是为了提高性能,优化点有

线程模型

实现线程的主要有三种方式:

1:1线程模型,通过fork函数创建一个子进程来代表一个内核中的线程,一个进程调用fork函数后系统会给新的进程分配资源,然后把原进程中所有的值都复制到新的进程中,只有少数的与原来不同,如:PID,由于每次都需要复制一摸一样的数据,LWP进行了优化使用clone函数来创建线程,没有复制的资源可以通过指针共享给子进程

N:1线程模型,由于1:1线程模型是和内核一对一映射,所以创建,切换都存在用户态和内核态的切换,开销比较大,同时由于系统资源有限,不能支持创建大量的LWP,但是N:1可以很好的解决这些问题,一个内核线程管理映射多个用户线程,所以用户线程切换的时候不会产生用户态和内核态的切换

N:M线程模型,主要解决了N:1线程模型如果一个线程阻塞,就会导致整个进程被阻塞,N:M通过LWP与内核线程链接,用户态的线程数量和内核态的LWP数量是N:M的关系

Java使用了1:1的线程模型,而Go协程实现了N:M的线程模型,所以Go语言并发行支持的更好,不需要上下文之间的切换(协程实现的本质就是在程序总实现函数的调度)

JAVA虚拟机

Java8为什么使用元空间替代永久代(方法区的实现)

类加载执行过程


image.png
上一篇 下一篇

猜你喜欢

热点阅读