计算机能识别的是机器指令码,简称机器码。机器码是二进制的,计算机可以直接识别,但与人类的语言差别太大,不容易被人理解和记忆。后来,就诞生了各种高级语言,人们用高级语言编写程序,然后通过把程序解释或编译成机器码。
比如python,就是一种解释型语言。python程序源码不需要编译,可以直接从源代码运行程序。python解释器将源代码转换为字节码,然后把编译好的字节码转发到python虚拟机(pvm)中进行执行。
而c语言就是典型的编译型语言,需要先用编译器编译成机器码,比如我们通常用gcc来编译c语言程序:
$ gcc hello.c # 编译$ ./a.out # 执行hello world!
那java是解释型语言还是编译型语言呢?
java是兼具编译型语言与解释型语言的特点的。程序员写好java程序后,需要先用javac编译成jvm可以使用的字节码class文件。然后jvm加载class文件,逐条解释执行。在运行过程中,部分热点代码会被即时编译器编译成机器码。
java语言的源代码是.java
为后缀的文件。当然现在有很多其它高级语言也架构在jvm上,比如groovy、kotlin等。源代码是给人看的,易于阅读、理解、维护。
源代码经过编译后得到字节码,字节码是给jvm用的,易于理解和识别。字节码是以.class
为后缀,其格式是jvm的一套规划,字节码人类对照文档也是勉强能看懂的,只是相对java代码来说要难以理解一些而已。
java与python不同,python不需要编译字节码文件(当然,python也提供了这种操作),编译是一个自动的过程,一般不会在意它的存在。而java会先编译好字节码文件,这样jvm直接读字节码文件,可以节省加载模块的时间,提高效率。同时字节码的形式也增加了反向工程的难度,可以保护源代码(当然,也可以被反编译)。
熟悉jvm的小伙伴都知道,它有一个“类加载过程”,可以说是老八股文了,经常会被面试官问到。类加载过程其实就是指的jvm从读取一个class文件到准备好这个类,以及最后销毁的整个过程。
所以class文件其实是以“类”为单位的,这跟java文件有一些不同。如果我们在一个java文件里面声明多个类,用javac编译出来会发现有多个class文件。比如我们声明一个one.java文件:
public class one { public class oneinner {} 用但造句private class oneprivateinner {} public static class onestaticinner {} private static class onepr河南农业大学排名ivatestaticinner {}}class two{}
用javac编译后,会出现6个class文件
$ ls'one$oneinner.class' 'one$onestaticinner.class' one.class two.class'one$oneprivateinner.class' 'one$oneprivatestaticinner.class' one.java
前面提到,jvm会加载class文件,然后加载后的java类会被存放于方法区(method area)中。从指定的类的main方法作为入口开始运行。实际运行时,虚拟机会执行方法区内的代码,jvm会使用堆和栈来存储运行时数据。
每当进入一个方法,java虚拟机会在当前线程的栈中生成一个栈帧,存放局部变量以及字节码的操作数,这个栈帧的大小是提前计算好的。
退出方法时,不管是正常返回还是异常返回,java虚拟机均会弹出当前线程的当前栈帧,并将之舍弃。
java虚拟机需要将字节码翻译成机器码,教师节精美图片才能让机器执行。这个过程有两种形式,一种是解释执行,即逐条将字节码翻译成机器码并执行;另一种是即时编译(just-in-time compilation,jit),即将一个方法中包含的所有字节码编译成机器码后再执行。
这两种编译方式是怎么协作的呢?
hotspot虚拟机包含多个即时编译器c1、c2和graal。其中,graal是一个实验性质的即时编译器,可以通过参数 -xx:+
unlockexperimentalvmoptions -xx:+ujvmcicompiler启用,并且替换c2。
c1和c2各有优劣,适用于不同的场景。在java 7以前,只能选择一种编译器。c1编译快,但徐州美女生成的代码执行效率一般,常用于对于执行时间较短的,或者对启动性能有要求的程序,常用于客户端;c2编译慢,但生成的代码执行效率快,适用于对于执行时间较长的,或者对峰值性能有要求的程序,常用于服务端。实际上,c1对应的参数是client,c2对应的参数是rver,也跟它们的应用场景比较匹配。
java7引入了分层编译的概念,综合了c1的启动性能优势和c2的峰值性能优势。c1和c2编译出的机器码是不同的。c2代码的执行效率要比c1代码高出30%以上。机器码越快,需要的编译时间就越长。分层编译是一种折衷的方式,既能够满足部分不那么热的代码能够在短时间内编译完成,也能满足很热的代码能够拥有最好的优化。
那怎么判定热点代码呢?
jvm会收集方法的运行时信息,主要包括调用次数和循环回边的次数。当方法的调用次数和循环回边的次数的和,超过指定阈值时,便会触发即时编译。
循环回边次数可以简单理解为方法内部代码的循环次数,比如方法内部有for循环或while循环。
在分层编译出现前,这个阈值是由参数-xx:compilethreshold
指定的,使用c1时,该值为1500;使用c2时,该值为10000。
当启用分层编译时,jvm使用另一套阈值系统。在这套系统中,阈值的大小是动态调整的。jvm将阈值与某个系数 s 相乘。该系数与当前待编译的方法数目成正相关,与编译线程的数目成负相关。
默认情况下编译线程的总数目是根据处理器数量来性格调整的。java 虚拟机会将这些编译线程按照1:2的比例分配给 c1和c2(至少各为1个)。举个例子,对于一个四核机器来说,总的编译线程数目为3,其中包含一个c1编译线程和两个c2编译线程。
机器资源太少的时候,也可能各1个线程。
用arthas可以看到编译线程:
可以看到,它们的id是-1,优先级也是-1。我们自己创建的线程优先级是0~10,所以编译线程的优先级会更高一些。
一句话来总结java程序是怎么在机器上运行的呢?首先java程序员编写java代码,然后java代码会被编译成class文件,多个class文件会被打包成jar包或者war包。然后jvm加载class文件,然后先解释执行为字节码。程序运行一段时间后,jvm会通过方法调用次数和循环持续判断一个方法是否为热点代码,如果是,会使用分层编译,通过编译线程编译成字节码,在机器上运行。
本文发布于:2023-04-05 03:22:32,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/3988175696b3f372368335d05f93649e.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:java源代码怎么运行(java源代码运行操作方法).doc
本文 PDF 下载地址:java源代码怎么运行(java源代码运行操作方法).pdf
留言与评论(共有 0 条评论) |