精华内容
下载资源
问答
  • 2,字节码编译成本地机器码(符合本地系统专属的指令) 解释执行也包括两种情况: 1,源码解释执行 2,字节码解释执行 解释和编译执行的区别是:是否产生中间本地机器码。 一、编译过程: 大部分的程序代码从开始...

    编译包括两种情况:
    1,源码编译成字节码
    2,字节码编译成本地机器码(符合本地系统专属的指令)
    解释执行也包括两种情况:
    1,源码解释执行
    2,字节码解释执行

    解释和编译执行的区别是:是否产生中间本地机器码。

    一、编译过程:

    大部分的程序代码从开始编译到最终转化成物理机的目标代码或虚拟机能执行的指令集之前,都会按照如下图所示的各个步骤进行:

    其中绿色的模块可以选择性实现。

    • 上图中间的那条分支是解释执行的过程(即一条字节码一条字节码地解释执行,如JavaScript),
    • 而下面的那条分支就是传统编译原理中从源代码到目标机器代码的生成过程。

    二、现代经典编译原理的思路:

    在执行前先对程序源码进行词法解析和语法解析处理,把源码转化为抽象语法树。

    对于一门具体语言的实现来说:

    • 词法和语法分析乃至后面的优化器和目标代码生成器都可以选择独立于执行引擎,形成一个完整意义的编译器去实现,这类代表是C/C++语言。
    • 也可以把抽象语法树或指令流之前的步骤实现一个半独立的编译器,这类代表是Java语言。
    • 又或者可以把这些步骤和执行引擎全部集中在一起实现,如大多数的JavaScript执行器。

    三、Javac编译

    在Java中提到“编译”,自然很容易想到Javac编译器将*.java文件编译成为*.class文件的过程,

    1,这里的Javac编译器称为前端编译器,其他的前端编译器还有诸如Eclipse JDT中的增量式编译器ECJ等。
    2,相对应的还有后端编译器,它在程序运行期间将字节码转变成机器码(现在的Java程序在运行时基本都是解释执行加编译执行),
       如HotSpot虚拟机自带的JIT(Just In Time Compiler)编译器(分Client端和Server端)。
    3,有时候还有静态提前编译器(AOT,Ahead Of Time Compiler)直接把*.java文件编译成本地机器代码,如GCJ、Excelsior JET等,这类编译器我们应该比较少遇到。
    

     Javac编译(前端编译)的过程:

    1,词法分析

    词法分析是将源代码的字符流转变为标记(Token)集合。

    单个字符是程序编写过程中的的最小元素,而标记则是编译过程的最小元素,关键字、变量名、字面量、运算符等都可以成为标记,
    比如整型标志int由三个字符构成,但是它只是一个标记,不可拆分。

     2、语法分析

    语法分析是根据Token序列来构造抽象语法树的过程。

    抽象语法树是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构,如类型、修饰符、运算符等。

    经过这个步骤后,编译器就基本不会再对源码文件进行操作了,后续的操作都建立在抽象语法树之上。

    3、填充符号表

    完成了语法分析和词法分析之后,下一步就是填充符号表的过程。

    符号表是由一组符号地址和符号信息构成的表格。

    符号表中所登记的信息在编译的不同阶段都要用到,在语义分析(后面的步骤)中,符号表所登记的内容将用于语义检查和产生中间代码,在目标代码生成阶段,对符号名进行地址分配时,符号表是地址分配的依据。

    比如:默认构造器的添加。

    4、语义分析

    语法树能表示一个结构正确的源程序的抽象,但无法保证源程序是符合逻辑的。

    而语义分析的主要任务是:

    对结构上正确的源程序进行上下文有关性的审查。

     语义分析过程分为标注检查和数据及控制流分析两个步骤:

    1,标注检查的内容包括诸如:变量使用前是否已被声明、变量和赋值之间的数据类型是否匹配等。
    2,数据及控制流分析是对程序上下文逻辑更进一步的验证,检查出诸如:程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等问题。

    5、字节码生成 

    字节码生成阶段不仅仅是把前面各个步骤所生成的信息转化成字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作。 

    • 实例构造器<init>()方法和类构造器<clinit>()方法就是在这个阶段添加到语法树之中的
    • 这里的实例构造器并不是指默认的构造函数,而是指我们自己重载的构造函数,

    如果用户代码中没有提供任何构造函数,那编译器会自动添加一个没有参数、访问权限与当前类一致的默认构造函数,这个工作在填充符号表阶段就已经完成了。

    四、JIT编译

    1、即时编译的产生:

    Java程序最初是仅仅通过解释器解释执行的,即对字节码逐条解释执行,这种方式的执行速度相对会比较慢,尤其当某个方法或代码块运行的特别频繁时,这种方式的执行效率就显得很低。

    于是后来在虚拟机中引入了JIT编译器(即时编译器),
    当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“Hot Spot Code”(热点代码),
    为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,完成这项任务的正是JIT编译器。

    PS:区别是:即时编译生成机器相关的中间码,可重复执行缓存效率高。解释执行直接执行字节码,重复执行需要重复解释。

    现在主流的商用虚拟机(如Sun HotSpot、IBM J9)中几乎都同时包含解释器和编译器

    (三大商用虚拟机之一的JRockit是个例外,它内部没有解释器,因此会有启动相应时间长,但它主要是面向服务端的应用,这类应用一般不会重点关注启动时间)。

    二者各有优势:

    • 当程序需要迅速启动和执行时,解释器可以首先发挥作用,省去编译的时间,立即执行;
    • 当程序运行后,随着时间的推移,编译器逐渐会失去作用,把越来越多的代码编译成本地代码后,可以获取更高的执行效率。

    解释执行可以节约内存,而编译执行可以提升效率。

    目前主流的HotSpot虚拟机中默认是采用解释器与其中一个编译器直接配合的方式工作。

    2、运行过程中会被即时编译器编译的“热点代码”有两类:

    被多次调用的方法。
    被多次调用的循环体。

    两种情况,编译器都是以整个方法作为编译对象,这种编译也是虚拟机中标准的编译方式。

    一段代码或方法是不是热点代码,是不是需要触发即时编译,需要进行Hot Spot Detection(热点探测)。

    3、目前主要的热点 判定方式有以下两种:

    3.1、基于采样的热点探测:

    虚拟机会周期性地检查各个线程的栈顶,如果发现某些方法经常出现在栈顶,那这段方法代码就是“热点代码”。
    • 好处是:实现简单高效,还可以很容易地获取方法调用关系,
    • 缺点是:很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测。

    3.2、基于计数器的热点探测: 

    虚拟机会为每个方法,甚至是代码块建立计数器,统计方法的执行次数,
    如果执行次数超过一定的阀值,就认为它是“热点方法”。

    这种统计方法实现复杂一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系,但是它的统计结果相对更加精确严谨。

    4、在HotSpot虚拟机中使用的是第二种——基于计数器的热点探测方法两个计数器

    方法调用计数器和回边计数器。

    4.1、方法调用计数器:

    用来统计方法调用的次数,在默认设置下,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间内方法被调用的次数。

    4.2、回边计数器:

    用于统计一个方法中循环体代码执行的次数.
    (准确地说,应该是回边的次数,因为并非所有的循环都是回边),
    在字节码中遇到控制流向后跳转的指令就称为“回边”。

    在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阀值,当计数器的值超过了阀值,就会触发JIT编译。

    5、即时编译和解释执行的执行顺序:

    • 触发了JIT编译后,在默认设置下,执行引擎并不会同步等待编译请求完成,
    • 而是继续进入解释器按照解释方式执行字节码,直到提交的请求被编译器编译完成为止(编译工作在后台线程中进行)。
    • 当编译工作完成后,下一次调用该方法或代码时,就会使用已编译的版本。

    6、方法调用计数器触发即时编译的流程:

    方法计数器触发即时编译的过程与回边计数器触发即时编译的过程类似

    展开全文
  • Java字节码编译工具 jclasslib

    千次阅读 2018-09-01 18:21:38
    在对 Java 代码执行过程效率分析过程中会很经常查看代码编译后的字节码,或者将字节码编译后,查看Java底层对于原来代码的优化结果,当然 JDK 本身已经提供了 javap 反编译工具可以完成这一过程, 也有很多第三方...

    Java 字节码反编译工具

     

    在对 Java 代码执行过程效率分析过程中会很经常查看代码编译后的字节码,或者将字节码反编译后,查看Java底层对于原来代码的优化结果,当然 JDK 本身已经提供了 javap 反编译工具可以完成这一过程, 也有很多第三方工具可以提供更加方便的可视化功能,如 JD-GUI,jclasslib 等,以下介绍 IDEA(Intellij 大法好) 的 jclasslib 插件;

     

    比如对于以下代码,我想查看 JDK8 对于 Map 的 lambda 初始化特性,Stream 管道 API 的代码底层调用;

    public class HelloWorld {
        public static void main(String[] args) {
            Map<Integer,String> map = new HashMap<Integer,String>(){{
                put(1,"are");
                put(2,"you");
                put(3,"ok");
            }};
            map.entrySet().forEach(System.out::println);
        }
    }

     

    1. JDK Javap 工具

    从控制台查看 class 文件字节码指令只要运行 javap -c yourClassName 即可,可以通过 javap -help 查看详细的使用参数列表;

     

    2. IDEA-jclasslib 插件

    IDEA 只需要在插件市场安装“jclasslib Bytecode viewer”插件即可,这是 jclasslib 的 IDEA 版本,当然 jclasslib 也有 native 版本,见:https://github.com/ingokegel/jclasslib/releases

     

    查看一个 java 文件的编译字节码文件,只需选择改文件,点击【View -> Show ByteCode】即可,如下:

     

    展开全文
  • 这篇文章的素材来自周志明的《深入理解Java虚拟机》。 作为Java开发人员,一定...Java字节码编译生成 我们讨论完了字节码的结构和活化字节码在执行引擎下的执行之后要回到字节码的原点:java的字节码是怎么形成...
        

    这篇文章的素材来自周志明的《深入理解Java虚拟机》。

    作为Java开发人员,一定程度了解JVM虚拟机的的运作方式非常重要,本文就一些简单的虚拟机的相关概念和运作机制展开我自己的学习过程,是这个系列的第四篇。

    Java字节码的编译生成

    我们讨论完了字节码的结构和活化字节码在执行引擎下的执行之后要回到字节码的原点:java的字节码是怎么形成的呢?

    我们这里讨论的仅仅是从程序员编写的java源代码的编译得到的字节码,但是要知道的事,字节码不仅仅可以从源文件编译生成,字节码可以通过直接用二进制的字节拼接产生,这个拼接的起点除了间接通过编译期生成,也可以通过直接写进内存,比如通过动态代理构造的临时代理类就是通过直接写入内存的二进制字节码形成的,再比如jsp通过jsp转换器可以转变为一个对应的请求处理类,等等。总之,我们在这里讨论的仅仅是通过编译期将静态的java源代码文件编译成二进制字节码。

    使用javac编写的java命令是编译过程的执行者,这个命令的使命就是把java源文件转换成为java二进制字节码,javac完成这一使命的步骤主要包括如下的子过程:

    1. 解析与填充符号表

    2. 插入式注解处理器的注解处理过程

    3. 分析与字节码生成

    这个过程的详细数据流和控制流如下:

    clipboard.png

    这些过程的目的和一般的传统的编译过程类似,因为和传统编译过程的源文件到机器代码的目的相比,java源代码到虚拟机二进制字节码的编译过程只是最终运行的平台是虚拟机,除此之外大致是一样的处理办法。

    解析

    这个过程是以源代码为输入流,词法分析器和语法分析器为控制器,抽象语法树为输出流,最终生成的语法树是一个以各种语法节点(接口、包等)为顶层节点的树结构,词法分析器对输入流转换成词法元单位Token的序列,语法分析器对Token序列进行分析得到最终的语法树,顺着这个语法树的各个顶层节点,可以找到程序中所有的变量、方法甚至是注释的各种信息。语法树是后期语义分析的基础。
    一个语法树的实例:

    clipboard.png

    填充符号表

    在解析过后会分析生成的语法树中的各类符号,包括程序中的各类符号的信息都将存储在这个符号表里。在经历完这一步之后符号表将成为一个包含了语法树顶层节点的表,顺着这个表可以分类地寻到每个符号的信息。

    注解处理过程

    在引入注解之后加入了对插入式注解处理器的编译过程,注解是形如”@XX”的语法结构,这个语法结构的目的当然不是简单的标记,而是对应到了一个对应的注解处理器之上,注解完成的任务是注解处理器定义的,因此在这个过程里注解处理器定义的任务将会以修改语法树的方式起作用。每次处理一个注解后都可能会改变语法树的结构,然后再启用符号表的填充,这个循环(round)将会是一次小规模的重建语法树和符号表,当扫描完所有的注解后语法树的结构在这个阶段将会稳定下来,然后给出一个为下面过程提供信息的To do List。实际上注解处理过程是程序员在编译过程中控制程序的很少的机会,因为其他过程大都是是编译器以无人为控制(没有程序员编写程序的指导)的情况下的处理。

    语义分析

    能通过词法语法分析并不意味着语义上是成立的,因此这个过程是处理语义的过程,语义分析器通过对符号表索引的语法树的分析,对程序表达的语义进行分析。它包括几个字过程:

    • 标注检查:主要是类型对应变量声明以及常量折叠的检查;

    • 数据控制流检查:对程序上下文逻辑的检验,包括局部变量赋值、返回值和异常处理等;

    • 解语法糖:语法糖是程序员友好的语法规则,这些友好的规则还是要在这个过程中解开成为真正需要表达的意思的(装箱拆箱、泛型、遍历循环等的语法都会在底层替换称为“复杂”的实现)。

    字节码生成

    把语法树定义的抽象的语法结构按照class二进制字节码的规则排布成class字节码,最终我们可以看到满足虚拟机运行要求的二进制字节码被转换出来。在这个过程中还会有特定的代码添加和初级的优化,比如默认的类构造器<init>()和实例构造器<clinit>()。

    注意不是构造函数,构造函数是在填充符号表的阶段完成的,构造函数用于完成new操作,而构造器是在内存中构造出该类的基本结构,而构造函数是语法层级较高的操作,同时还会将静态代码块static{}加入类构造器,将构造代码块{}加入到实例构造器中,包括实例变量和类变量的初始化、父类构造器的调用等过程都会加入到构造器中去。

    上面的过程完成后,javac命令扮演的编译器就将源代码转成了结构化的二进制字节码。

    Java字节码的运行优化

    解释执行

    字节码的运行过程我们在第三篇的时候已经解释过了:

    Java虚拟机 :Java字节码指令的执行

    当时我们看到的是逐一把二进制命令执行,也就是说执行引擎每取一条二进制指令就执行一次,这种执行方式称为解释执行(interpreted mode),我们其实可以看出解释执行的优点在于每次执行的时候都会确知当前程序的状态,但是每次执行都要从方法区里取命令,然后再能够在堆栈中执行操作,每次都去取指令无疑是会减慢执行速度的,即便把马上要执行的命令置于高速缓冲上。

    即时编译执行

    基于这个弱点就有了另一种执行模式,编译模式(compiled mode),这个模式中非常重要的参与者就是JIT即时编译器(Just Intime),编译模式的原理其实就像是C一样的编译型语言一样把源代码直接编译成机器语言然后一口气运行完,省去了每次取指令的时间(只不过C是直接把源代码编译成机器码,而java是把二进制字节码通过虚拟机的JIT即时编译器编成本地机器码)。

    JIT触发的条件:

    不是所有的代码被以编译模式执行都是好的,因为JIT编译本身也是费时的,所以必须在非常有必要进行编译的部分才应该去编译,这些地方就是需要反复使用的部分,因为反复使用的部分是需要进行进行最大化优化的,而只用几次的代码可能使用的时间还不及JIT编译的时间,这样做就没有“性价比”了。所以我们来看看被称为hotspot的这些反复使用的代码被编译模式执行的特点:
    如果是多次调用的方法或者是多次执行的循环体就是hotspot的。

    一般虚拟机会为每个方法添加一个计数器,这个计数器用于计量方法执行的次数,当这个计数器计量这个方法调用超过某个阈值时就会触发JIT编译器对这个方法的编译,即时编译后的代码会成为本地机器码,执行速度会大大加快,同时由于这个方法使用次数非常多,所以将会大大加速程序的运行。当然这个过程不是仅仅这么简单,因为如果这样的话程序运行时间足够长的话会有很多并不那么“热”的代码也会成为hotspot的,比如某段代码运行了一段时间后陷入了“冷”状态,那么这段代码就算不上是hotspot的,因此默认情况下虚拟机查看的更是代码在一个时间内的调用频率,如果一段时间内的使用次数足够多才会说明这段代码是hotspot的。

    同样的,循环体会被虚拟机加入一个回边计数器用以统计循环体的使用频率。
    下面展示的就是在JIT这套机制下的编译模式的执行流程:

    clipboard.png

    clipboard.png

    值得注意的是,JIT编译的时候并不是说线程就停在这里一直等待编译的本地机器码的结果出现,而是继续以解释模式执行,这能充分利用执行时间,等到下次执行到这里的时候再看看是否JIT编译已经有了结果,如果有了就去执行本地代码,否则还得解释执行以继续等待。

    JIT即时编译器在后台执行的编译任务时也会首先对字节码进行优化,包括方法内联和常量传播等策略,然后转换成高级中间代码表示,再进行一次优化,然后转为平台相关的低级中间代码表示,再进行一次优化,最后变成平台相关的机器代码。这个底层的优化过程属于相对机器层级的优化。

    这里所提的还有几个编译过程中的比较典型的优化技术:

    • 公共子表达式消除:用于消除重复计算带来的性能损失;

    • 数组边界检查消除:编译期确定的数组范围将不必要的边界检查条件去除;

    • 方法内联:避免方法调用的时候产生的栈切换和现场恢复等过程带来的损耗,由于java的因为虚方法的重载重写等问题带来的方法分派问题,内联的结果不能确定一定正确,所以才用的一般是激进优化失败退回的策略;

    • 逃逸分析:如果一个方法中的局部变量不会通过调用函数作为参数传出被外部方法或线程使用的时候,可以采用更加高效的办法优化:

    • 栈上分配:在栈上直接为变量对象分配空间,因为知道了这个对象不会发生逃逸被外部访问到,所以某种程度上来讲这就是一个“临时封闭在方法里”的对象,所以这种栈上分配的办法不会造成问题。使用完毕后就将它直接释放,也减小了gc的压力。

    • 同步消除:同理的,不会被外部线程访问到的“临时封闭在方法里”的对象是不会发生共享的,所以可以消除它的同步标记。

    • 标量替换:如果一个局部变量对象是“临时封闭在方法里”的对象,那么就完全没有必要建立一个完整的对象,只需要在栈上创建它的相关字段就可以了,这样做可以加速对真正被访问的变量的速度。

    展开全文
  • 运行时把Class文件字节码编译成本地机器码 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在《Java三种编译方式:前端编译 JIT编译 AOT编译》中了解到了它们各有什么优点和缺点,以及...

    Java编译(三)Java即时编译(JIT编译):

    运行时把Class文件字节码编译成本地机器码


            在《Java三种编译方式:前端编译 JIT编译 AOT编译》中了解到了它们各有什么优点和缺点,以及前端编译+JIT编译方式的运作过程;在《Java前端编译:Java源代码编译成Class文件的过程》了解到javac编译的大体过程。

            下面我们详细了解JIT编译;从官方JDK中的HotSpot虚拟机的JIT编译器入手,先介绍解释器与JIT编译器是如何配合工作的,认识JIT编译器C1/C2;再看看JIT编译的是什么,以及触发条件是什么;而后再简单介绍JIT编译过程,认识几种编译技术;最后对比Java与C/C++的编译器。

    1、解释器与JIT编译器

            即时编译功能与虚拟机具体实现有关,下文的编译器、即时编译器指HotSpot虚拟机内的即时编译器。

            HotSpot源码及相关调试可以参考前文《CentOS上编译OpenJDK8源码 以及 在eclipse上调试HotSpot虚拟机源码》。

    1-1、解释器与JIT编译器的结合方式

            前面文章介绍过HotSpot是采用前端编译+JIT编译的方式,所以需要解释器来解释执行加载进来的Class字节码,解释器与JIT编译器的结合方式(即后面说明的混合模式)如下

    1、解释器

            程序启动时首先发挥作用,解释执行Class字节码;

           省去编译时间,加快启动速度;

            但执行效率较低;

    2、JIT编译器

            程序解释运行后,JIT编译器逐渐发挥作用

           编译成本地代码,提高执行效率;    

            但占用程序运行时间、内存等资源;

    3、激进优化的"逃生门"

            解释器还可以作JIT编译器激进优化的一个"逃生门";

            激进优化不成立时,可以通过逆优化(Deoptimization)退回到解释状态编译执行

    1-2、JIT编译器:Client Compiler与Server Comiler

            HotSpot虚拟机内置两个即时编译器,分别Client Compiler和Server Comiler,如下:

    1、Client Compiler

            简称C1编译器

    (A)、应用特点

           较为轻量,只做少量性能开销比较高的优化,它占用内存较少,适合于桌面交互式应用;

    (B)、优化技术

            它是一个简单快速的三段式编译器;

            主要关注点在于局部性的优化,而放弃了许多耗时较长的全局优化;

            在寄存器分配策略上,JDK6以后采用的为线性扫描寄存器分配算法,其他方面的优化,主要有方法内联、去虚拟化、冗余消除等;

    (C)、设置参数

            可以使用"-client"参数强制选择运行在Client模式(Client VM);

    2、Server Compiler

            简称C2编译器,也叫Opto编译器;

    (A)、应用特点

           较为重量,采用了大量传统编译优化的技巧来进行优化,占用内存相对多一些,适合服务器端的应用;

    (B)、优化技术

            它会执行所有经典的优化动作,如无用代码消除、循环展开、循环表达式外提、消除公表达式、常量传播、基本块重排序等;

            还会一些与Java语言特性密切相关的优化技术,如范围检查消除、空值检查消除等;

            另外,还进行一些不稳定的激进优化,如守护内联、分支频率预测等;

    (C)、收集性能信息

            由于C2会收集程序运行信息,因此其优化范围更多在于全局优化,不仅仅是一个方块的优化;

            收集的信息主要有:分支的跳转/不跳转的频率、某条指令上出现过的类型、是否出现过空值、是否出现过异常等。

    (D)、与C1的不同点

            和C1的不同主要在于寄存器分配策略及优化范围,寄存器分配策略上C2采用传统的全局图着色寄存器分配算法;

            C2编译速度较为缓慢,但远远超过传统的静态优化编译器;

            而且编译输出的代码质量高,可以减少本地代码的执行时间;

    (E)、设置参数

            可以使用"-server"参数强制选择运行在Server模式(Server VM);

    3、C1与C2的默认选择

           默认根据机器的硬件性能自动选择运行模式,称为Ergonomics机制;

            JDK 6开始定义服务器级别的机器是至少有两个CPU和2GB的物理内存,才开启C2

            更多关于自适应选择说明请参考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/server-class.html

    1-3、JIT编译器与解释器的工作模式

                  可以通过“-version”参数显示当前的工作模式,各工作模式说明如下:

    1、混合模式(Mixed Mode)

           JIT编译器(无论C1还是C2)与解释器配合工作的方式;

           这是默认的方式,也可通过“-Xmixed”参数设定;

    2、解释模式(Interpreted Mode)

           全部代码由解释器解释执行,JIT编译器不介入工作;

           可以通过“-Xint”参数设定;

    3、编译模式(Compiler Mode)

           优先采用编译方式执行程序,但解释器仍要在编译无法时行时介入执行过程;

           可以通过“-Xcomp”参数设定;

           该参数强调的是首次调用方法时执行编译,并不是不用解释器

           一般情况下(不开启分层编译),一个方法需要解释执行一定次数后才编译(详见后面“热点探测“);

            但JDK7/8作为默认开启分层编译策略

    1-4、分层编译

           为了在程序启动响应速度与运行效率之间达到最佳平衡,会启用分层编译(Tiered Compilation)策略;

    1、编译层次

           根据编译器编译、优化的规模与耗时,划分出不同的编译层次,包括:

    (I)、第0层

           程序解释执行,解释器不开启性能监控功能(Profiling),可触发第1层编译;

    (II)、第1层

           也称为C1编译,将字节码编译为本地代码,进行简单、可靠的优化,如有必要加入性能监控的逻辑;

    (III)、第2层

           也称为C2编译,也是将字节码编译为本地代码,但进行一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化;

    2、优点

           这时C1和C2同时进行工作,许多代码都可能被编译多次;

           用C1获取更高的编译速度,用C2获取更好的编译质量;

           解释器执行时也无须再承担收集性能监控信息的任务(如果不开启分层编译,又工作在Server模式,解释器提供监控信息给C2使用);

           最终在程序启动响应速度与运行效率之间达到最佳平衡;

    3、设置参数

           JDK6开始出现,需要“-XX:+TieredCompilation”指定开启;

           JDK7/8作为默认的策略,可以通过“-XX:-TieredCompilation”关闭策略

           注意,只能在Server模式下使用;

           关于分层编译的一些信息:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/performance-enhancements-7.html#tieredcompilation

    4、源码分析

           源码中对“TieredCompilation”参数的解析过程,如下:

           可以看到设置了"AdvancedThresholdPolicy"对象作为分层编译策略的实现,"AdvancedThresholdPolicy.cpp"中有关于分层编译的更详细的说明,如下:

           可以看到它里面更详细的分为了5层(上面的第1层C1编译包括了里面的1、2、3层);

           另外,不同层有一些不同参数可以设置,如下:

    2、JIT编译对象与触发条件

    2-1、热点代码

           JIT编译对象为"热点代码",包括两类:

    1、被多次调用的方法

           由方法调用触发的编译,以整个方法体为编译对象;

           JVM中标准的的JIT编译方式

    2、被多次执行的循环体

           由循环体触发,仍然以整个个方法体为编译对象;

           发生在方法执行过程中,方法栈帧还在栈上,方法就被替换;

           称为栈上替换(On Stack Replacement),简称OSR编译

    2-2、热点探测(Hot Spot Deection)方法

           判断一段代码是不是热点代码,是不是需要编译,目前主要有两种方法:

    1、基于采样的热点探测(Sample Base Hot Spot Detection)

           JVM周期性检查各个线程的栈顶,看某些方法是否经常出现在栈顶;

           优点:简单高效,且容易获得方法调用关系;

           缺点:不精确,容易受到线程阻塞或外界因素影响;

    2、基于计数器的热点探测(Counter Base Hot Spot Detection)

           为每方法(代码块)建立计数器,统计执行次数,超过一定阈值就认为是"热点代码";

           优点:更加精确和严谨;

           缺点:比较复杂;                

           当然还有其他的方法,如Android Dalvik中的JIT编译器使用的基于"踪迹"(Trace)的热点探测;

    HotSpot虚拟机使用第二种--基于计数器的热点探测。

    2-3、基于计数器的热点探测

           注意,下面的交互过程都是以Client模式JIT编译为例子说明,如果Server模式比较复杂一些,而启用分层编译模式则更复杂。

           HotSpot虚拟机为每个方法准备了两类计数器,来统计执行次数,如下:

    1、方法调用计数器(Invocation Counter)

    (1)、交互过程

           Client模式时的交互过程,如图:

    (A)、当方法执行,先检查是否存在被JIT编译的版本

           如果存在,则用编译的本地代码执行;

           如果不存在,则计数器值加一;

    (B)、判断两个计数器(加上回边计数器)之和是否超过阈值

           如果没超过,则以解释方式执行方法;

           如果超过,则向编译器提交编译请求;

    (C)、提交编译请求后的执行方式

           如果以默认设置,提交编译请求后,继续以解释方式执行方法;

           如果通过"-Xbatch "或"-XX:-BackgroundCompilation",设置成同步等待方式,则等待编译完成,以编译后代码执行

    (2)、阈值设置

           默认C1时为1500次(sparc平台才是1000),C2时为10000次;

           可以通过"-XX:CompileThreshold"参数设定

           启用分层编译时将忽略此选项,请参阅选项"-XX:+ TieredCompilation"

    (3)、阈值表示

    (A)、执行频率

           默认设置下,方法调用计数器不是统计调用的绝对次数,而是执行频率:一段时间内方法被调用的次数

           如果一段时间内没达到阈值触发编译请求,就会在JVM进行垃圾回收时,把计数器值减半,这个过程称为方法调用计数器热度的衰减(Counter Decay),这段时间称为方法调用计数的半衰周期(Counter Half Life Time);

           可以通过"-XX:CounterHalfLifeTime"参数设置半衰周期的时间(秒)

    (B)、绝对次数

           可以通过"-XX:-UseCounterDecay"参数关闭热度衰减

           这时方法调用计数器统计的就是方法调用的绝对次数

    2、回边计数器(Back Edge Counter)

           字节码中遇到控制流向后中转的指令,称为"回边"(Back Edge)

           回边计数器作用:统计一个方法中循环体代码执行次数,为触发OSR编译;

    (1)、交互过程

           Client模式时的交互过程,如图:

    (A)、当执行中遇到回边指令,先检查将要执行的代码片段是否存在被JIT编译的版本:

           如果存在,则用编译的本地代码执行;

           如果不存在,则计数器值加一;

    (B)、判断两个计数器(加上方法调用计数器)之和是否超过阈值:

           如果没超过,则以解释方式执行方法;

           如果超过,则向编译器提交编译请求,调整减少回边计数器值;

    (C)、提交编译请求后的执行方式

           如果以默认设置,提交OSR编译请求后,继续以解释方式执行方法;

           如果通过"-Xbatch "或"-XX:-BackgroundCompilation",设置成同步等待方式,则等待编译完成,以编译后代码执行

    (2)、阈值设置

           简单策略下,并没有使用"-XX:BackEdgeThreshold"参数设置阈值;

           而是使用OnStackReplacePercentage,该值参与计算是否触发OSR编译的阈值;

           可以通过"-XX:OnStackReplacePercentage"来设置,然后通过一定规则计算,如下;

    (i)、Client模式                    

           计算规则方法调用计数器阈值(CompileThreshold)*OSR比率(OnStackReplacePercentage)/100

           默认:OnStackReplacePercentage=933, CompileThreshold=1500,计算阈值为14895

    (ii)、Server模式

           前面介绍分层编译时曾说:如果不开启分层编译,又工作在Server模式,解释器提供监控信息给C2使用,所以多了个解释器监控比率(InterpreterProfilePercentage);

           计算规则CompileThreshold*(OnStackReplacePercentage-InterpreterProfilePercentage)/100;

           默认:OnStackReplacePercentage=140, CompileThreshold=10000,InterpreterProfilePercentage=33,计算阈值为10700

           可以看到HotSpot源码定义的计算规则,如下:

    1. if (ProfileInterpreter) {
    2. //Server模式
    3. InterpreterBackwardBranchLimit = (CompileThreshold * (OnStackReplacePercentage - InterpreterProfilePercentage)) / 100;
    4. } else {
    5. //Client模式
    6. InterpreterBackwardBranchLimit = ((CompileThreshold * OnStackReplacePercentage) / 100) << number_of_noncount_bits;
    7. }

    (3)、阈值表示

           统计的就是方法中循环体代码执行的绝对次数

           没有执行频率、热度衰减的概念

    3、JIT编译过程

           下面分别对C1、C2编译器的编译过程进行简单介绍。

    3-1、C1编译过程

           它是一个简单快速的三段式编译器,主要关注点在于局部性的优化,而放弃了许多耗时较长的全局优化;

           三段式编译过程如下:

    (A)、在字节码上进行一些基础优化,如方法内联、常量传播等;

           然后将字节码构造成一种高级中间代码表示(High-Level Intermediate Representaion,HIR);

           HIR使用静态单分配(SSA)的形式表示代码值;

    (B)、在HIR基础上再次进行一些优化,空值检查消除、范围检查消除等;

           然后将HIR转换为LIR(低级中间代码表示)

    (C)、在LIR基础上分配寄存器、做窥孔优化,然后生成机器码;

    3-2、C2编译过程

           C2的编译过程较为复杂,从前面对C2的介绍可以知道,C2编译采用了很多优化技术,后面再对一些优化技术进行介绍。

    4、查看及分析JIT编译结果

           如何从外部观察JVM的JIT编译行为?

           最好自己编译Debug版本OpenJDK,有一些参数需要Debug或FastDebug版JVM的支持(Product版本不支持), 可以参考《CentOS上编译OpenJDK8源码 以及 在eclipse上调试HotSpot虚拟机源码》。

           相关参数如下:

           "-XX:+PrintCompilation":要求JVM在JIT编译时将衩编译本地代码的方法名称打印出来;

           "-XX:+PrintInlining":要求JVM输出方法内联信息(Product版本需要"-XX:+UnlockDiagnosticVMOptions"选项,打开JVM诊断模式);

           "-XX:+PrintAssembly":JVM安装反汇编适配器后,该参数使得JVM输出编译方法的汇编代码(Product版本需要"-XX:+UnlockDiagnosticVMOptions"选项,打开JVM诊断模式);

           "-XX:+PrintLIR":输出比较接近最终结果的中间代码表示,包含一些注释信息(用于C1,Debug版本);

           "-XX:+PrintOptoAssembly":输出比较接近最终结果的中间代码表示,包含一些注释信息(用于C2,非Product版本);

           "-XX:+PrintCFGToFile":将JVM编译过程中各个阶段的数据输出到文件中,而后用工具C1 Visualizer分析(用于C1,Debug版本);

           "-XX:+PrintIdealGraphFile":将JVM编译过程中各个阶段的数据输出到文件中,而后用工具IdealGraphVisualizer分析(用于C2,非Product版本);

    5、JIT编译优化技术

           从前面对C1、C2编译器的介绍可以知道,它们在编译过程中采用了很多优化技术,HotSpot虚拟机JIT编译采用的优化技术可参考:

           PerformanceTechniques:https://wiki.openjdk.java.net/display/HotSpot/PerformanceTechniques

           PerformanceTacticIndex:https://wiki.openjdk.java.net/display/HotSpot/PerformanceTacticIndex

           下面介绍几种最具代表性的优化技术:

           1、语言无关的经典优化技术之一:公共子表达式消除;

           2、语言相关的经典优化技术之一:数组范围检查消除;

           3、最重要的优化技术之一:方法内联;

           4、最前沿的经典优化技术之一:逃逸分析;

    5-1、公共子表达式消除

           公共子表达式消除(Common Subexpression Elimination)是语言无关的经典优化技术之一,普遍应用于各种编译器。

    1、概述理解

           如果一个表达式E已经被计算过了,并且从先前的计算到现在E中所有变量的值都没有发生变化,那么E的这次出现就称为了公共子表达式

           对于这种表达式,没有必要花时间再对它进行计算,只需要直接用前面计算过的表达式结果代替E就可以了;

           对于不同范围,可分为局部公共子表达式消除全局公共子表达式消除

    2、实例说明

           int d = (c*b)*12+a+(a+b*c);

           javac不会优化,JIT编译优化如下:

           1、公共子表达式消除:int d = E*12+a+(a+E);

           2、代数化简(Algebraic Simplification):int d = E*13+a*2;

    5-2、数组边界检查消除

           数组范围检查消除(Array Bounds Checking Elimination)是JIT编译器中的一项语言相关的经典优化技术。

    1、概念理解

           Java访问数组的时候系统将会自动进行上下界的范围检查;

           这对于虚拟机的执行子系统来说,每次数组元素的读写都带有一次隐含的条件判定操作,对于拥有大量数组访问的程序代码,这无疑也是一种性能负担;

           数组边界检查是必须做的,但数组边界检查在某些情况下可以简化;

    2、实例说明

    (A)、数组下标是常量

           foo[3];

           编译器根据数据流分析确定foo.length的值,并判断下标"3"没有越界,执行的时候就无须判断了;

    (B)、数组下标是循环变量

           for(int i …){

       foo[i];

           }

           如果编译器通过数据流分析就可以判定循环变量"0<=i< foo.length",那在整个循环中就可以把数组的上下界检查消除掉,这可以节省很多次的条件判断操作。

    3、隐式异常处理

           大量的安全检查很可能成为一个Java语言比C/C++更慢的因素;

           要消除这些隐式开销,可以:

    (A)、尽可能把运行期检查提到JIT编译期完成,如前面的数组边界检查消除

    (B)、隐式异常处理如空指针检查和算术运算中的除数为零的检查

    (i)、实例说明

    1. if(foo != null){
    2. return foo.value;
    3. }else {
    4. throw new NullPointException();
    5. }

           隐式异常处理后,伪代码变为:

    1. try{
    2. return foo.value;
    3. }catch(seqment_fault){
    4. uncommon_trap();
    5. }

           不会进行空值判断,JVM注册异常处理器,为空的时候进入处理器恢复并抛出异常

    (ii)、隐式异常处理前后比较

           隐式异常处理不会消耗一次空值判断的开销,但空值时处理经过用户空间->内核空间->用户空间,速度更慢,所以适用于很少为空值的情况

           如果经常为空,还是不用隐式异常处理的好;

           HotSpot运行时会收集Profile信息,自动选择最优方案;

           与语言相关的其他消除操作还有自动装箱消除(Autobox Elimination)、安全点消除(Safepoint Elimination)、消除反射(Dereflection)等。

    5-3、方法内联

           方法内联(Method Inlining)是编译器最重要的优化手段之一,普遍适用于各种编译器。

    1、概念解理    

           编译器将程序中较小的、多次出现被调用的函数,用函数的函数体来直接进行替换函数调用表达式。

           优点:

           (A)、去除方法调用成本(如建立栈帧);

           (B)、为其他优化建立良好的优化效果,方法内联膨胀后便于在更大范围进行优化;

    2、实例说明

    1. static class B {
    2. int value;
    3. final int get() {
    4. return value;
    5. }
    6. }
    7. public void foo() {
    8. y = b.get();
    9. ……
    10. z = b.get();
    11. sum = y + z;
    12. }

    (A)、方法内联后

    1. public void foo() {
    2. y = b.value;
    3. ……
    4. z = b.value;
    5. sum = y + z;
    6. }

    (B)、冗余访问消除(Redundant Loads Elimination)

    1. public void foo() {
    2. y = b.value;
    3. ……
    4. z = y;
    5. sum = y + z;
    6. }

    (C)、复写传播(Copy Propagation)

    1. public void foo() {
    2. y = b.value;
    3. ……
    4. y = y;
    5. sum = y + y;
    6. }

    (D)、无用代码消除(Dead Code Elimination)

    1. public void foo() {
    2. y = b.value;
    3. ……
    4. sum = y + y;
    5. }

           实例揭示了方法内联对其他优化手段的意义;

           如果不做内联,后续即使进行了无用代码消除的优化,也无法发现任何"Dead Code";

    3、虚方法的内联问题

           Java中只有4种方法可以在编译期进行解析:invokespecial指令调用的私有方法、实例构造器、父类方法以及invokestatic指令调用的静态方法;

           而对于一个虚方法,编译期做内联时无法确定应该使用哪个方法版本,需要在运行时进行方法接收者的多态选择;

           Java程序存在大量虚方法,如默认的实例方法(非fianl修饰),所以为解决虚方法的内联问题,首先引用一种名为"类型继承关系分析"(Class Hierarchy Analysis,CHA)的技术

    (A)、如果是非虚方法,则直接进行内联;

    (B)、如果是虚方法,则向CHA查询;

    (i)、如果查询结果只有一个版本,也可以进行内联;

           不过这属于激进优化,需要预留一个"逃生门",称为守护内联(Guarded Inlining);

           因为运行加载类可能导致继承关系发生变化,需要退回解释执行,或重新编译;

    (ii)、如果查询结果有多个版本目标,使用内联缓存(Inline Cache)来完成方法内联;

           当缓存第一次调用方法接收者信息,以后每次调用都比较,不一致时取消内联;

           所以方法内联是一种激进优化;

           激进优化在商用虚拟机中很常见,需要预留"逃生门",可以退回解释执行

    5-4、逃逸分析

           逃逸分析(Escape Analysis)是JVM中比较前沿的优化技术;

           并不是直接优化代码的手段,而是为其他优化手段提供依据的分析技术;

    1、概念理解

           逃逸分析的基本行为就是分析对象动态作用域:

    (A)、方法逃逸

           当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,这种行为称为方法逃逸;

    (B)、线程逃逸

           甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,这种行为称为线程逃逸;

    2、优化说明

           如果能证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,则可能为这个变量进行一些高效的优化,如:

    (A)、栈上分配(Stack Allocations)

           如果确定一个对象不会逃逸出方法之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁;

           一般应用中,大多局部对象都可以使用栈上分配,这样垃圾收集器的压力就会小很多;

    (B)、同步消除(Synchronization Elimination)

           线程同步本身就是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那这个变量的读写肯定就不会有竞争,对这个变量实施的同步措施就可以消除掉。

    (C)、标量替换(Scalar Replacement)

    标量(Scalar):

           是指一个数据已经无法再分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int、long等 数值类型及reference类型等)都不能再进一步分解,它们就可以被称为标量;

    聚合量 (Aggregate):

           相对的,如果一个数据可以继续分解,那它就被称作聚合量 (Aggregate),Java中的对象就是最典型的聚合量;

    标量替换:

           如果把一个Java对象拆散,根据程序访问的情况,将其使用到的成员变量恢复原始类型来访问就叫做标量替换;

           如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。

           将对象拆分后,除了可以让对象的成员变量在栈上(栈上存储的数据,很大机会会被虚拟机分配至物理机器的高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。

    3、技术实现

           JDK1.6才实现逃逸分析,但至今都尚未足够成熟,因为:

           如果要完全准确判断一个对象是否会逃逸,需要进行数据流敏感的一系列复杂分析;

           这是一个相对高耗时的过程,不能保证逃逸分析的性能必定高于它的消耗;

           所以现在只能采用不是太准确,但时间压力相对较小的算法来完成

           如栈上分配实现起来比较复杂,HotSpot中暂时还没有做这项优化;

    4、相关参数

           JDK6 u23的C2才开始默认开启逃逸分析,相关参数如下:

           "-XX:+/-DoEscapeAnalysis":开启/关闭逃逸分析(只有Server VM支持);

           "-XX:+PrintEscapeAnalysis":查看逃逸分析结果(Server VM 非Product版本支持);

           "-XX:+/-EliminateAllocations":在开启逃逸分析情况下,开启/关闭标量替换(只有Server VM支持);

           "-XX:+PrintEliminateAllocations":查看标量替换结果(Server VM 非Product版本支持);

    6、Java与C/C++的编译器对比

           Java与C/C++的编译器对比实际上代表了最经典的JIT编译器与静态编译器的对比,很大程度上也决定了Java与C/C++的性能对比的结果;

           Java虚拟机的JIT编译器输出的本地代码质量可能有一些劣势,因为:

           1、JIT编译占用的是用户程序的运行时间,具有很大的时间压力,不敢随便引入大规模的优化技术;

           2、Java语言是动态的类型安全语言,需要由虚拟机来确保程序不会违反语言的语义或访问非结构化内存;虚拟机必须频繁地进行动态检查,消耗时间,如实例方法访问时检查空指针、数组元素访问时检查上下界范围、类型转换时检查继承关系,等等;

           3、Java程序使用虚方法的频率却远远大于C/C++语言,运行时需要对方法接收者进行多态选择,这也意味着即时编译器在进行一些优化时的难度要远远大于C/C++的静态优化编译器;

           4、Java是可以动态扩展的语言,运行时加载新的类可能改变程序类型的继承关系,这使得很多全局的优化都难以进行,只能以激进优化的方式来完成,编译器不得不时刻注意并随着类型的变化而在运行时撤销或重新进行一些优化;

           5、Java语言中对象分配都在堆上,需要垃圾收集器自动回收管理,占用资源;而C/C++的对象可能在栈上分配,并且主要由用户程序代码来回收分配的内存,将减轻内存回收的压力。

           总得来说,Java在性能上劣势都是为换取开发效率上的优势而付出的代价

           动态安全,动态扩展,垃圾回收这些"拖后腿"的特性都是为Java的开发效率做出了很大的贡献;

           何况Java即时编译器能做的,C/C++的静态优化编译器不一定能够做:

           由于C/C++的静态编译,以运行性能监控为基础的优化措施它都无法进行,如调用频率预测,分支频率预测,裁剪未使用分支等,这些都是称为java语言独有的性能优势。

     

           到这里,我们大体了解Java的即时编译技术,更多实现细节可以参考HotSpot源码,更多编译技术原理可以参考《编译原理》第二版(龙书)、《现代编译原理》(虎书)、《高级编译器设计与实现》(鲸书)。

           后面我们将去了解Java内存回收--垃圾收集算法及垃圾收集器……

     

    【参考资料】

    1、HotSpot源码

    2、《编译原理》第二版

    3、《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版 第11章

    4、《The Java Virtual Machine Specification》Java SE 8 Edition:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html

    5、《Java Platform, Standard Edition HotSpotVirtual Machine Garbage Collection Tuning Guide》:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html

    6、HotSpot虚拟机参数官方说明:http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html


    展开全文
  • 先占个坑,以后写。
  • 关于Java字节码

    千次阅读 2018-09-24 10:25:23
    关于Java字节码 概述 从写Java文件到编译成字节码文件(也就是.class文件)的过程也就是Java文件编译过程,我们所写的是Java文件而Java虚拟机编译的是字节码文件 class文件格式 ...
  • 下面详细了解JIT编译;从官方JDK中的HotSpot虚拟机的JIT编译器入手,先介绍解释器与JIT编译器是如何配合工作的,认识JIT编译器C1/C2;...而后再简单介绍JIT编译过程,认识几种编译技术;最后对比Java与C/C++的编译器。
  • JVM 字节码指令手册 - 查看 Java 字节码

    千次阅读 多人点赞 2019-08-15 21:26:27
    JVM 字节码指令手册 - 查看 Java 字节码 jdk 进行的编译生成的 .class 是 16 进制数据文件,不利于学习分析。通过下命令 javap -c Demo.class > Demo.txt 或者其他方式可反汇编,得到字节码文件 一、JVM 指令...
  • Java号称是一门“一次编译到处运行”的语言,从我们写的java文件到通过编译器编译成java字节码文件(.class文件),这个过程是java编译过程;而我们的java虚拟机执行的就是字节码文件。不论该字节码文件来自何方,由...
  • Java字节码

    2020-12-02 15:36:13
    意义 字节码是 用来实现在不同的操作系统,不同的硬件平台上均可以不修改...jvm也可以将字节码编译执行,如果是热点代码(运行时编译),会通过JIT动态的编译为机器代码,提高执行效率。 方法调用和JTL解释 ...
  • Java字节码理解

    2020-08-01 19:36:10
    Java字节码其实就是 .class 文件. 我们写出的Java源代码经过编译后就会变成 class文件。 编译过程 源代码是程序员写的。JVM识别不了,因此需要编译器主要对源码代码做编译处理,大致如下: 词法分析器 => ...
  • 如何查看java字节码

    2020-01-10 22:12:34
    在我们工作、学习、以及研究 JVM 过程当中,不可避免的要查看 Java 字节码,通过查看字节码可以了解一个类的编译结果,也能通过编译器层面来分析一个类的性能。 字节码文件是不能直接打开的,下面栈长教大家几种...
  • 什么是Java字节码

    千次阅读 2018-11-18 22:10:14
    Java号称是一门“一次编译到处运行”的语言,从我们写的java文件到通过编译器编译成java字节码文件(.class文件),这个过程是java编译过程;而我们的java虚拟机执行的就是字节码文件。不论该字节码文件来自何方,由...
  • java字节码速查笔记

    2018-02-07 15:30:00
    java文件到通过编译器编译成java字节码文件(也就是.class文件),这个过程是java编译过程;而我们的java虚拟机执行的就是字节码文件。不论该字节码文件来自何方,由哪种编译器编译,甚至是手写字节码文件,只要符合...
  • 本文图片以及部分内容来自Java字节码增强探秘。 Java字节码的介绍 字节码基础 .java文件通过javac编译后将得到一个.class文件,如下图所示,class文件中都是16进制数。 java字节码主要包括以下几部分。 (1)魔数...
  • Java字节码(一)

    2021-03-04 23:36:40
    二、java字节码文件编码过程 在了解了字节码的含义之后,那么你是否想过一个问题,我们平时编写的是java文件,JVM运行的class文件,也就是字节码。一般这个转化IDE工具帮我们完成了,或者在命令行的情况下使用Javac...
  • Java字节码是Java源文件编译产生的中间文件java虚拟机是可运行java字节码的假想计算机 java的跨平台性也是相对与其他编程语言而言的先介绍一下c语言的编译过程吧先是C语言源程序 也就是c的文件经过C编译程序编译后,...
  • Java字节码—ByteCode,在编译后产生,与目标代码无关从而实现了平台的可移植性 我们知道 Java的编译与执行过程 分别经历的过程为: 编写 Hello.java 文件 javac 编译Hello.java文件 生成Hello.class文件 java ...
  • Java字节码介绍及动态修改类

    千次阅读 2018-09-06 18:36:30
    对于Java字节码,它是在Java类的编译过程产生的,即由.java源文件到.class二进制字节码文件的过程。而Java类的加载又是通过类的名字获取二进制字节流,然后在内存中将字节流生成类对象。所以动态修改类的时机在于...
  • Java 字节码阅读

    2019-05-17 10:16:31
    很多时候我们通过代码无法了解字节码执行过程,比如Try Catch Finally的执行过程,只有通过debug或者阅读字节码才能搞懂JVM是如何编译和设计执行流程的。 public class TestTryCatch { public String inside() { ...
  • JAVA编译过程会对代码进行优化,如果某一行代码永远不可能被执行到, 这行代码不会被编译到字节码文件中去。 如下面的例子,flag是一个可以在编译阶段就可以确定的值, javac编译的时候不会把System.out....
  • 深入理解java编译后的字节码文件

    千次阅读 2018-04-19 17:15:53
    从我们写的java文件到通过编译器编译成java字节码文件(也就是.class文件),这个过程是java编译过程;而我们的java虚拟机执行的就是字节码文件。不论该字节码文件来自何方,由哪种编译器编译...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,297
精华内容 1,718
关键字:

java字节码编译过程

java 订阅