简单展示网站模板wordpress 自动提交
- 作者: 五速梦信息网
- 时间: 2026年04月20日 10:48
当前位置: 首页 > news >正文
简单展示网站模板,wordpress 自动提交,留电话咨询看房,网站改版怎样做301前端编译与后端编译 Java 程序的编译过程是分两个部分的。一个部分是从java文件编译成为class文件#xff0c;这一部分也称为前端编译。另一个部分则是这些class文件#xff0c;需要进入到 JVM 虚拟机#xff0c;将这些字节码指令编译成操作系统识别的具体机器指令。这一部…前端编译与后端编译 Java 程序的编译过程是分两个部分的。一个部分是从java文件编译成为class文件这一部分也称为前端编译。另一个部分则是这些class文件需要进入到 JVM 虚拟机将这些字节码指令编译成操作系统识别的具体机器指令。这一部分也称为后端编译。 其中前端编译是在 JVM 虚拟机之外执行所以与 JVM 虚拟机没有太大的关系。任何编程语言只要能够编译出满足 JVM 规范的 Class 文件就可以提交到 JVM 虚拟机执行。至于编译的过程如果你不是想要专门去研究语言那么就没有必要太过深入的去了解了。这里就暂时略过。我们更关注JVM 在后端编译过程中如何提升执行的效率。 字节码指令是如何执行的 解释执行与编译执行 Class 文件当中就已经保留了每一行 Java 代码对应的字节码指令也就是说执行引擎要如何执行一段 Java 代码其实早在 Class 文件当中就已经确定了。执行引擎要做的事情其实就是将这些Class文件中的字节码指令翻译成对应操作系统的机器码然后扔给服务器执行就行了。 本质上就相当于是一个翻译。 那么怎么做这个翻译工作呢最简单的方式当然就是来一个指令就翻译一次。就像是一个无脑的翻译机器不用管合不合理按字翻译就是了。没错早期的JVM执行引擎其实就是这么做的这种执行方式就称为解释执行。 但是这种方式需要在上层语言和机器码之间经过中间一层JVM字节码的转换显然执行效率是比不上 C 和 C那些直接面向本地机器指令编程的语言的这也是长久以前Java 被 C 和 C开发者吐槽执行速度慢的根源。 那么要怎么提升JAVA的执行效率呢 JAVA的基本思想就是维护一个缓存CodeCache将那些字节码指令提前编译出来放到缓存里。到执行的时候直接从缓存中查出来就好了。这种先编译后执行的方式就称为编译执行。 但是JAVA官方也不知道程序员会写出什么样稀奇古怪的代码所以自然没办法提前维护出一个完整的字节码缓存。那么就只能退而求其次将那些运行频率最高的热点代码提前编译出来放到缓存里。这样至少最常用的那些方法的调用效率能够提高。完成这个任务的编译器称为即时编译器 JIT(Just In Time Compiler)。 使用java -version就可以看到当前使用的是哪种执行模式。 从这里可以看到HotSpot虚拟机并没有直接选择执行效率更快的编译执行而是默认采用的一种混合执行的方式。 为什么JVM不直接采用性能明显更高的编译执行模式呢这是因为虽然编译执行可以将越来越多的代码编译成本地代码这样可以减少解释器的中间损耗获得更高的执行效率。但是这也意味着对内存有更多的资源限制在很多资源比较紧张的场景比如客户端应用嵌入式系统等使用解释执行就能更节约内存。 另外编译执行需要较长的预热过程。在 CodeCache 中的代码缓存维护好之前编译执行相比解释执行需要额外的性能消耗用来识别热点代码维护 CodeCache 。同时编译执行在识别热点代码的过程中还需要解释执行来帮助提供一些信息支持。在 HotSpot 中会默认使用混合执行模式而不是单纯的使用其中一种模式。 热点代码识别 使用 JIT 实时编译的前提就是需要识别出热点代码。要知道某段代码是不是热点代码是不是需要触发即时编译这个行为称为“热点探测”(Hot Spot Code Detection)。热点探测有很多种实现思路而在 HotSpot 虚拟机中采用的是一种基于计数器的热点探测方法。HotSpot 为每个方法准备了两类计数器 方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter)。当虚拟机运行参数确定的前提下这两个计数器都有一个明确的阈值计数器阈值一旦溢出就会触发即时编译。 首先来看看方法调用计数器。顾名思义这个计数器就是用于统计方法被调用的次数。每次调用一个方法时就记录一次这个方法的执行次数。当他的执行次数非常多超过了某一个阈值那么这个方法就可以认为是热点方法。这个方法对应的代码自然也就是热点代码了。这时就可以向JIT提交一个针对该方法的代码编译请求了。 方法调用技术器的默认阈值是10000次这个阈值可以通过虚拟机参数-XX:CompileThreshold来设定。当一个方法被调用时虚拟机会先检查该方法是否存在被即时编译过的版本如果存在则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本则将该方法的调用计数器值加一然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阈值。一旦已超过阈值的话将会向即时编译器提交一个该方法的代码编译请求。整体流程如下图 比如这个方法计数器的默认阈值就可以使用 java -XX:PrintFlagsInitial -version 指令查询。 方法计数器以方法为维度自然是不够精细的。所以要更精细的识别热点代码还需要配合接下来的回边计数器 回边计数器它的作用是统计一个方法中循环体代码执行的次数。在字节码中遇到控制流向后跳转的指令就称为“回边(Back Edge)”很显然建立回边计数器统计的目的是为了发现一个方法内部频繁的循环调用。回边计数器在服务端模式下默认的阈值是 10700 当解释器遇到一条回边指令时会先查找将要执行的代码片段是否有已经编译好的版本如果有的话它将会优先执行已编译的代码否则就把回边计数器的值加一然后判断方法调用计数器与回边计数器值之和是否超过回边计数器的阈值。当超过阈值的时候将会提交一个编译请求并且把回边计数器的值稍微降低一些以便继续在解释器中执行循环等待编译器输出编译结果整个执行过程如下图。 C1、C2与Graal编译器 在JDK1.8中 HotSpot 虚拟机中内置了两个 JIT分别为 C1 编译器和 C2 编译器。 C1编译器 C1 编译器是一个简单快速的编译器主要的关注点在于局部性的优化适用于执行时间较短或对启动性能有要求的程序例如GUI 应用对界面启动速度就有一定要求C1也被称为 Client Compiler。 C1编译器几乎不会对代码进行优化 C2编译器 C2 编译器是为长期运行的服务器端应用程序做性能调优的编译器适用于执行时间较长或对峰值性能有要求的程序。根据各自的适配性这种即时编译也被称为Server Compiler。 但是C2代码已超级复杂无人能维护所以才会开发Java编写的Graal编译器取代C2(JDK10开始) 分层编译 分层编译根据编译器编译、优化的规模与耗时划分出不同的编译层次 等级描述性能0程序纯解释执行并且解释器不开启性能监控功能(Profiling)11使用C1编译器将字节码编译为本地代码来运行进行简单可靠的稳定优化。不开启性能监控功能。42仍然使用C1编译器执行仅开启方法及回边次数统计等有限的性能监控功能。33仍然使用C1编译器执行开启全部性能监控除了第2层的统计信息外还会收集如分支跳转、虚方法调用版本等全部的统计信息。24使用C2编译器将字节码编译为本地代码相比起C1编译器C2编译器会启用更多编译耗时更长的优化还会根据性能监控信息进行一些不可靠的激进优化。5 后端编译优化技术 方法内联 Inline 方法内联的优化行为就是把目标方法的代码复制到发起调用的方法之中避免发生真实的方法调用。这样就可以减少频繁创建栈帧的性能开销。 例如以下方法 最终会被优化为 JVM 会自动识别热点方法并对它们使用方法内联进行优化。 我们可以通过 -XX:CompileThreshold 来设置热点方法的阈值。 但要强调一点热点方法不一定会被 JVM 做内联优化如果这个方法体太大了JVM 将不执行内联操作。 而方法体的大小阈值我们也可以通过参数设置来优化 经常执行的方法默认情况下方法体大小小于 325 字节的都会进行内联我们可以通过 -XX:FreqInlineSizeN 来设置大小值 不是经常执行的方法默认情况下方法大小小于 35 字节才会进行内联我们也可以通过 -XX:MaxInlineSizeN 来重置大小值。 逃逸分析 Escape Analysis 当一个对象在方法里面被定义后它可能被外部方法所引用例如作为调用参数传递到其他方法中这种称为方法逃逸甚至还有可能被外部线程访问到譬如赋值给可以在其他线程中访问的实例变量这种称为线程逃逸从不逃逸、方法逃逸到线程逃逸称为对象由低到高的不同逃逸程度。 左侧的代码中t对象不会被外部引用只会在方法中使用所以不会发生逃逸。而右侧的代码中t对象就很明显被其他方法使用了这就会产生逃逸。JDK8 中默认开启了逃逸分析可以添加参数 -XX:-DoEscapeAnalysis 主动关闭逃逸分析。 如果能证明一个对象不会逃逸到方法或线程之外那么 JIT 就可以为这个对象实例采取后续一系列的优化措施。 第一个是标量替换(Scalar Replacement)。若一个数据已经无法再分解成更小的数据来表示了Java虚拟机中的原始数据类型int、long等数值类型及reference类型等都不能再进一步分解了那么这些数据就可以被称为标量。如果把一个Java对象拆散根据程序访问的情况将其用到的成员变量恢复为原始类型来访问这个过程就称为标量替换。假如逃逸分析能够证明一个对象不会被方法外部访问并且这个对象可以被拆散那么程序真正执行的时候将可能不去创建这个对象而改为直接创建它的若干个被这个方法使用的成员变量来代替。将对象拆分后除了可以让对象的成员变量在栈上分配和读写之外还可以为后续进一步的优化手段创建条件。标量替换对逃逸程度的要求更高它不允许对象逃逸出方法范围内。JDK8 中默认开启了标量替换可以通过添加参数 -XX:-EliminateAllocations 主动关闭标量替换。 第二个是栈上分配( Stack Allocations)。正常情况下JVM 中所有对象都应该创建在堆上并由 GC 线程进行回收。如果确定一个对象不会逃逸出线程之外那让这个对象在栈上分配内存将会是一个很不错的主意对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中完全不会逃逸的局部对象和不会逃逸出线程的对象所占的比例是很大的如果能使用栈上分配那大量的对象就会随着方法的结束而自动销毁了垃圾收集子系统的压力将会下降很多。栈上分配可以支持方法逃逸但不能支持线程逃逸。 这三种优化措施中逃逸分析是基础。因为虚拟机栈是对应一个线程的而堆内存是对应整个Java进程的。如果发生了线程逃逸那么堆中的同一个对象可能隶属于多个线程这时要将堆中的对象挪到虚拟机栈中那就必须扫描所有的虚拟机栈看看在这个虚拟机栈对应的线程中是否引用了这个对象。这个性能开销是难以接受的。 而栈是一个非常小的内存结构他也不可能像堆中那么豪横的使用内存空间所以也必须要对对象进行最大程度的瘦身才能放到栈中。而瘦身的方式就是去掉对象的mark标志位中的补充信息拆分成最精简的标量。所以要开启栈上分配标量替换也是不可或缺的。 可以用下面示例方法进行一下验证 public class EscapeAnalysisTest {public static void main(String[] args) throws InterruptedException {long start System.currentTimeMillis();for (int i 0; i 10000000; i) {allocate();}System.out.println(运行耗时(System.currentTimeMillis()-start));Thread.sleep(6000000);}static void allocate(){MyObject myObject new MyObject(2024,2024.6);}static class MyObject {int a;double b;MyObject(int a,double b){this.a a;this.b b;}} } 以我的测试环境来看默认情况下 运行时间大概 2 毫秒而关闭逃逸分析或者关闭标量替换后运行时间就扩大到了 44毫秒左右。 锁消除 lock elision 这也是经过逃逸分析后可以直接进行的优化措施。 这个优化措施主要是针对 synchronized 关键字。当 JVM 检测到一个锁的代码不存在多线程竞争时会对这个对象的锁进行锁消除。 比如下面的示例代码 public class LockElisionDemo {public static String BufferString(String s1,String s2){StringBuffer sb new StringBuffer();sb.append(s1);sb.append(s2);return sb.toString();}public static String BuilderString(String s1, String s2){StringBuilder sb new StringBuilder();sb.append(s1);sb.append(s2);return sb.toString();}public static void main(String[] args) {long startTime System.currentTimeMillis();for (int i 0; i 100000000; i) {BufferString(aaaaa,bbbbbb);}System.out.println(StringBuffer耗时(System.currentTimeMillis()-startTime));long startTime2 System.currentTimeMillis();for (int i 0; i 100000000; i) {BuilderString(aaaaa,bbbbbb);}System.out.println(StringBuilder耗时(System.currentTimeMillis()-startTime2));} } 其中分别测试了 StringBuffer 和 StringBuilder 的字符串构建方法。这两个方法功能上没有什么区别最大的区别在于StringBuffer 是线程安全的他的append和toString都是加了 synchronized 同步锁的而 StringBuilder 则没有加。之前介绍过synchronized 关键字其实是在Class文件中添加了monitorenter和monitorexit两个字节码指令的所以StringBuffer显然要比 StringBuilder 更慢。 在当前代码中BufferString 方法只是在main这一个线程里调用不存在线程竞争所有这个synchronized 同步锁是没有作用的因此在触发了 JIT 后JVM 会在编译时就会将这个无用的锁消除掉。这样两个方法的耗时是差不多的。 StringBuffer耗时1521 StringBuilder耗时1039 与之形成对比如果给这个示例代码添加一个JVM 参数 -XX:-EliminateLocks 主动关闭锁清除后再执行这个案例两个方法的耗时差距就明显更大了。 StringBuffer耗时2461 StringBuilder耗时1049
- 上一篇: 简单详细搭建网站教程视频教程西安网络科技有限公司有哪些
- 下一篇: 简洁大方网站建设制作企业网站作业
相关文章
-
简单详细搭建网站教程视频教程西安网络科技有限公司有哪些
简单详细搭建网站教程视频教程西安网络科技有限公司有哪些
- 技术栈
- 2026年04月20日
-
简单网站制作怎么做p2p网站
简单网站制作怎么做p2p网站
- 技术栈
- 2026年04月20日
-
简单网站模板下载wordpress is tax
简单网站模板下载wordpress is tax
- 技术栈
- 2026年04月20日
-
简洁大方网站建设制作企业网站作业
简洁大方网站建设制作企业网站作业
- 技术栈
- 2026年04月20日
-
简洁大气国内企业网站重庆市企业网站建设
简洁大气国内企业网站重庆市企业网站建设
- 技术栈
- 2026年04月20日
-
简洁大气蓝色文章资讯网站非法网站开发是什么意思
简洁大气蓝色文章资讯网站非法网站开发是什么意思
- 技术栈
- 2026年04月20日
