能让java性能提升的JIT深度解析
在从事Java开始的一段时间,那时候经常可以听到什么C++的瞧不起写Java的,在一些群里也经常看到二个派的人经常互怼。Java能够这么流行与它的跨平台,语言无关性是分不开的,不管你是用Java,python还是Go,只要变成对应的标准字节码文件,那么JVM都是可以识别并执行的,但是那时候的Java之所以被C++吐槽主要还是因为Java 慢 ,为什么这么说呢。
我们写的程序虽然能被JVM识别,但是不能被机器识别,程序要运行起来,还是得让机器能够识别你的程序,所以JVM还需要一个 解释器 ,这个解释器就是将你的程序转换成机器能识别的指令,然后执行,如下图
对于一个长期运行的Java进程来说,每次执行都要经过 解释器 将程序翻译成机器指令去执行,那么这个效率就不是很好,这也是为什么Java被吐槽 慢 因为缘故,所以为了解决这个问题,才出现了 JIT 。
对于一些热点代码(经常被执行的,for循环)的一些代码,在运行时,JVM会将这些代码编译成机器可以执行的机器码,并缓存起来,这样下次执行这些代码的时候,就不需要再经过 解释器 去编译了,机器可以直接运行这段程序,提高性能,这个就被称为 即时编译器 ,简称 JIT编译器 JIT的种类
在JDK1.8中HotSpot虚拟机中,内置了二个JIT,分别为C1编译器和C2编译器
C1编译器:是一个简单快速的编译器,主要关注点在于局部性的优化,适用于执行时间较短或者对启动性能有要求的程序,C1编译器几乎不会对代码进行优化
C2编译器:是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序,根据各自的适配性,这种即时编译也被称为Server Compiler,但是由于C2代码超级复杂,无人维护,所以才会开发Java编写的Graal编译器代替C2 热点代码
JIT会将一些热点代码编译成机器能够识别的机器码然后缓存起来,比如一些经常被调用的代码,还有for循环中的代码,那么JIT如何识别出哪些是热点代码呢??
为什么说是一些热点代码缓存起来,而不是全部呢?因为缓存是需要空间存储的,可以通过以下命令查看该缓存的大小 java -XX:+PrintFlagsFinal –version 复制代码
JVM也提供了一个参数 -XX:ReservedCodeCacheSize 来限制该缓存的大小,如果空间满了,JIT就无法继续编译,编译执行就会变成解释执行,程序也就会通过解释器去执行 热点探测:
热点探测也就是检查出那些热点代码,然后进行编译,热点探测是基于计数器的热点探测,也就是会统计每个方法被调用的次数,当次数达到一个阈值的时候,就会被认为是热点代码
虚拟机为每个方法准备了两种计数器, 方法调用计数器 和 回忆计数器 ,在确定JVM的运行参数之后,这二个计数器都会有各自的一个阈值,达到阈值就会出发JIT编译 方法调用计数器:用于统计方法被调用的次数,客户端模式下默认是1500次,在服务端模式下默认是10000次(默认我们都是用服务端模式),我们可以使用以下命令查看 java -XX:+PrintFlagsFinal –version 复制代码
回边计数器:用于统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为 回边 ,该值在服务端模式下默认是10700次, 在JVM内存结构中是有一个程序计数器的,它表示的是字节码需要你执行的下一行指令的行号,当我们执行 第一次循环之后,程序又回调到for循环的第一行执行,这个就叫回边 for(int i = 0; i < 10000; i ++) { int a = i; int b = a + i; } 复制代码JIT是如何优化Java性能的方法内联
方法内联的优化是指将被调用方法的代码复制到发起调用方法中,避免发生真实的方法调用,举个例子 方法add1()要计算四个数加起来的和,然后又调用了 add2()方法,其实根本没必要有 add2()这个方法 所以JIT会进行优化 public int add1(int a,int b,int c,int d) { return add2(a,b) + add2(c,d); } public int add2(int a,int b) { return a + b; } 优化后就一个方法就行了,也就是把 add2()方法要执行的代码直接复制到 add1()方法中执行,就不要去 调用 add2()这个方法了,这样就不存在方法调用,就一个方法就可以了 public int add1(int a,int b,int c,int d) { return a + b + c + d; } 复制代码
为什么方法内联可以优化Java性能呢?我们知道一个方法的执行在JVM内存结构中虚拟机栈对应的就是入栈,方法结束就对应着出栈,出栈和入栈都是有性能消耗的,所以少一个方法执行就减少了一次对应的出栈和入栈,性能也就能够提升 锁消除
在非线程安全情况下,我们都会使用线程安全的容器,举个例子,比如字符串拼接的StringBuffer和StringBuilder,StringBuffer的方法被关键字synchornized修饰,所以性能会比StringBuilder差,但是在局部方法中二者的性能确实差不多的,因为在局部方法中是单线程访问的,不存在线程安全问题, // jdk8默认情况下开启了锁消除 public static void main(String[] args) { long start_sb = System.currentTimeMillis(); for(int i = 0; i < 10000000; i ++) { SBuilder("king","coco"); } long end_sb = System.currentTimeMillis(); System.out.println("StringBuilder话费的时间:" + (end_sb - start_sb)); long start_sf = System.currentTimeMillis(); for(int i = 0; i < 10000000; i ++) { SBuffer("king","coco"); } long end_sf = System.currentTimeMillis(); System.out.println("StringBuffer话费的时间:" + (end_sf - start_sf)); } public static void SBuilder(String str1,String str2) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(str1); stringBuilder.append(str2); } public static void SBuffer(String str1,String str2) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append(str1); stringBuffer.append(str2); } 复制代码
花费的时间差不多
然后我们可以通过启动参数 -XX:-EliminateLocks 关闭锁消除, -XX:+EliminateLocks 开启锁消除
关闭了锁消除之后,StringBuffer所话费的时间明显增加了很多,性能降低了,JIT在编译的时候发现如果使用了线程安全的容器,比如StringBUffer,但是发现程序不会存在线程并发问题,就会执行锁消除来提高程序的性能 逃逸分析
Java创建的大部分对象都是在堆中的,而不是全部的对象。逃逸分析技术就是在创建对象的时候判断这个对象是在保存在堆中还是保存在栈中,那么保存在栈中有什么好处呢??
首先说说保存在堆中吧,JVM垃圾收集的主要对象就是堆,创建的对象在堆中保存,那么当你这个对象不用 的时候就要被回收,我们知道垃圾回收是会消耗一定的性能的,但是如果你这个对象经过逃逸分析之后,发现这个对象可以在栈中分配,那么当你这个方法结束之后,也就是出栈,那么该对象自然就没了,也不需要垃圾收集器回收,这样就减少了垃圾收集器的工作,性能自然就能提升了
那么什么是逃逸分析呢? 我们创建了一个StringBuilder对象,但是这个对象只有在这个方法内部有效,该对象没有被返回出去 也就是说没有方法需要a()方法创建的这个StringBuilder对象,所以这个StringBuilder对象不会发生 逃逸 public String a() { StringBuilder sb = new StringBuilder(); sb.append("123"); return "123"; } 这个StringBuilder对象就发生逃逸了,因为有其它方法需要b()方法创建的StringBuilder对象 也就是说这个对象发生了逃逸 public StringBuilder b() { StringBuilder sb = new StringBuilder(); sb.append("123"); return sb; } 复制代码
逃逸分析性能测试
逃逸分析默认开启 public static void main(String[] args) { long start = System.currentTimeMillis(); for(int i = 0; i < 50000000; i ++) { createPeople(); } long end = System.currentTimeMillis(); System.out.println("花费的时间是:" + (end - start)); } public static void createPeople() { People people = new People(10,"coco"); } static class People{ Integer age; String name; public People(Integer age,String name) { this.age = age; this.name = name; } } 复制代码
添加打印GC收集信息
发现这么多对象很快就创建好了,并且没有垃圾收集日志的打印 关闭逃逸分析
关闭逃逸分析之后,所花费的时间显著上升
并且我们可以加伤GC打印日志
发现有GC的收集信息,因为对象都在堆中,所以才发生了GC,相反,开启逃逸分析技术的对象是随着栈的出入直接销毁的,不需要进行GC,所以性能会提升
原文 https://juejin.cn/post/7120030108076736542
夜听情深是你,归处还是你夜听情深是你,归处还是你我的生活也是头条网上看到这样的一段话写尽千山,落笔是你望尽星辰,梦里是你三分月色,七分是你书尽泛黄,扉页是你繁华落尽,枕畔是你行进万水,尽头是你千山万水,归
忘不了你,相思牵绊,一场相遇,一生想念我的生活也是头条时间,总是能改变了太多的东西,最爱的人,为何只能深藏在心里,在平淡的日子里,想念着你,期待着在某一天,还能有一场相遇。忘不了你,相思牵绊,一场相遇,一生想念,窗外炊
今年流行的运动鞋瑜伽裤,配羽绒服简直天生一对!时髦显瘦自然的美丽,需要我们迎接自己特别的一面。腿粗腿短,又或者有小肚子,这些穿衣的问题都不应该成为你变美的绊脚石。在自我造型提升的路上,每一天都可以尝试全新的设计,不断积累独属于自我的穿
光子引擎成驯龙高手,小米11升级MIUI14流畅度大幅提升!在过去两年时间,几乎每场发布会上都会有厂商自诩驯龙高手,可以完美压制发热问题,获得最极致的性能体验。本来以为这是自吹自擂,同一款芯片怎么调校都不会有太大差异,直到小米11升级MIU
德媒穆勒下半程须竞争首发位置高层已考虑了他明年退役的可能直播吧1月11日讯德媒sport1的报道称,穆勒在拜仁队中的主力位置将不再是不可撼动的,高层已经考虑到了他可能在明夏退役的情况。拜仁全队目前正在多哈进行冬季备战,上半赛季一直在与臀
目前最表里如一的4款手机,直线硬超友商,流畅丝滑五年无忧目前最表里如一的4款手机,直线硬超友商,流畅丝滑五年无忧第一款荣耀Magic4如果你还在发愁如何选购一款旗舰手机的话,那么你一定要看一看荣耀的这一款旗舰机型,荣耀Magic4。性能
小米13Ultra暗示存在?或二月底亮相,正面硬刚华为P60系列小米13Ultra暗示存在?或二月底亮相,正面硬刚华为P60系列原以为,此次小米13系列并不会推出超大杯Ultra。但近日,雷军在与网友的互动中,似乎确认了小米13Ultra的存在
积核MiniAir11评测做工至上的低功耗小主机因为英特尔JasperLake系列低功耗芯片在性能上的长足进步,千元级的迷你主机的实用性越来越高。Jupit3r在2022年测试了不少低功耗的小主机,搭载了赛扬N5095四核处理器
我不装了,我摊牌了3真的有必要使用洗面奶吗?(一)大家洗练时用不用洗面奶?估计很多人都会用假设有一次洗面奶恰好用完了你硬着头皮洗脸时你会感觉到脸部油真多呀没错!!!洗面奶可以把脸部的油去掉同时也打破了面部的油水油水平衡(二)
末日光景背后的地狱鬼岛在日本长崎县西南方向约15公里的地方,有一座形似军舰的小岛。从飞机上俯瞰这座小岛,你能看到参差不齐的楼房和校舍,但若是乘船登岛,你就会发现这些楼房早已人去楼空摇摇欲坠。这座荒无人烟
复合维生素不是人人都适合复合维生素能补充人体所需吗?复合维生素适宜所有人食用吗?记者专访营养学专家作出解答。是否需要补充维生素,首先要考虑的问题是我们自己是否真的缺乏维生素。南方医科大学第五附属医院营养科