Java性能调优

String对象是如何实现的?

  • Java6以及之前的版本,String是对char数组进行了封装实现的对象,主要有,char数组,偏移量offset,字符串数量count,哈希值
  • Java7到8String类中不再有offset和count两个变量了,String占用的内存稍微小些,同时,String.substring方法不再共享char,解决了可能导致内存泄漏的问题
  • java9开始,char[]字段改为了byte[]字段,维护了一个新的coder,它是编码格式的标识

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

String的内存分配

  • String str = "abc" 代码编译时,会在常量池中创建常量abc,运行时返回常量池中的字符串的饮用
  • String str = new String("abc"),代码在加载时会在常量池中创建常量abc,在调用new时,会在堆中创建String对象,并且引用常量池中字符串对象char[]数组,并且返回String对象的引用
  • public class Test{String test1,String test2}(String 为成员变量),在运行时,创建的String对象会在堆中,不会在常量池中创建
    注意:使用intern方法需要注意一点,一定要结合实际场景,因为常量池实现类似于一个HashTable的实现方式,存储的数据越大,遍历的时间复杂度就会提升

正则表达式

目前实现正则表达式引擎的方式有两种,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"

正则表达式的优化

  • 少用贪婪模式多用独占模式
  • 减少分支的选择,如"abcd|abef"替换为"ab(cd|ef)"
  • 减少捕获嵌套

I/O

  • 传统I/O和NIO的最大区别就是传统I/O是面向流的,NIO是面向buffer的,Buffer可以将文件一次性读入内存再做后续的处理,而传统的方式是边读文件边处理数据,虽然传统I/O后面也是用了缓存快,如:BufferedInputStream,但是仍不嫩隔阂NIO媲美
  • NIO的Buffer除了做缓存快块优化外,还提供了一个可以直接访问物理内存的类,DirectBuffer,普通的Buffer分配的是JVM堆内存,而DirectBuffer是直接分配的物理内存,
    数据要输出到外部设备,必须先从用户空间复制到内核空间,再复制到输出设备,而在Java中,在用户空间中又存在一个拷贝,那就是从java堆内存拷贝到临时的直接内存中去,通过临时的直接内存拷贝到内存空间中去,此时的直接内存和堆内存就是属于用户空间(为什么Java要通过一个脸是非堆内存来复制数据?如果单纯使用Java堆内存进行数据拷贝,当拷贝的数据量比较大的情况,Java堆的GC压力会比较大,而使用非堆内存可以简低GC压力),DirectBuffer则是直接将步骤简化为直接保存数据到非堆内存中,从而减少一次拷贝

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)

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

  • 系统原因,如分配的时间片到了,I/O中断等
  • 程序中调用slepp,wait,park等导致线程状态的改变

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

  • 减少锁持有的时间,只在关键竞争处增加锁
  • 降低锁的粒度,如锁分离,锁分段(ConcurrentHashMap)
  • 乐观锁代替竞争锁
  • wait和notify的优化,建议使用Lock锁结合Condition接口代替
  • 合理设置线程池的大小,避免创建过多的线程,根据自己的业务场景,一般在N+1和2N两个公式中选择一个合适的
  • 使用协程实现非阻塞等待
  • 减少JAVA垃圾回收

线程模型

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

  • 轻量级进程和内核线程一对一互相映射实现1:1线程模型(JAVA在LINUX下的实现)
  • 用户线程和内核线程实现N:1线程模型
  • 用户线程和轻量级进程混合实现N:M线程模型

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为什么使用元空间替代永久代(方法区的实现)

  • 为了融合HotSpot JVM与JRockit VM,因为JRockit没有永久代
  • 永久代经常不够用或发生内存溢出,每次PermGen区FullGC的时候回收率偏低

类加载执行过程


image.png
  • 类编译,把.java文件编译成.class文件
  • 类加载,当类被创建实例或者被其他对象引用时,虚拟机在没有加载过该类的情况下,会通过类加载将字节码文件加载到内存中,加载完成后class类文件常量池信息以及其他数据会被保存到JVM内存的方法区中
  • 类连接,验证:验证是否符合规范,准备:为类的静态变量分配内存,初始化值,如int常量初始化0(此时还没有真正赋值),解析:将符号引用转为直接引用,包括类和接口的全限定名,类引用,方法引用以及成员变量引用等
  • 类初始化,执行构造器的<clinit>方法,为变量赋值
  • 即时编译(JIT),初始化完成后,类在调用执行过程中,执行引擎会把字节码转成机器吗,然后操作系统才能执行,字节码转机器码的过程中虚拟机还存在一道编译,那就是即时编译,HotSpot虚拟机中内置了两个JIT,分别为C1和C2编译器,这两个编译器过程是不一样的,C1编译器是一个简单快速的编译器,主要关注在局部性优化(如GUI程序),C2编译器是为长期运行的服务器程序做的性能调优
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容