java代码
专栏地址:http://gk.link/a/1018S
Java 和 C++ 在运行方式上的区别?
Java 代码有很多种不同的运行方式,比如在开发工具中运行、双击执行 jar 文件运行、在命令行中运行,甚至可以在网页中运行。
Java 的运行离不开 JRE(Java 运行时环境), JRE 仅包含运行 Java 程序的必需组件,包括 Java 虚拟机以及 Java 核心类库等。当然我们程序员更经常接触到的是 JDK(Java 开发工具包),包含了JRE,并且还附带了一系列开发、诊断工具。
运行 C++ 代码则无需额外的运行时,往往把代码直接编译成 cpu 所能理解的机器码即可。
为什么 Java 要在虚拟机里运行?
Java 作为一门高级程序语言,它的语法非常复杂,抽象程度也很高。因此,直接在硬件上运行这种复杂的程序并不现实。
所以可以在 Java 代码与机器码之间加一层虚拟机,先将 Java 代码转换成 Java 字节码,最后才转换成机器码,降低复杂度。
Java hello world 字节码格式
Java 虚拟机的好处
-
通过为各个平台(linux、windows、Mac os等)提供java虚拟机的软件实现,在各个平台上都可以将java代码转换成字节码来运行Java(可移植性),达到 “一次编写、到处运行” 的目的
-
Java虚拟机为我们带来了一个托管环境(Managed runtime),可以替我们处理一些冗长而且容易出错的代码,最重要的譬如自动内存管理与垃圾回收,同时还提供诸如数组越界、动态类型、安全权限等的动态检测,让我们可以专心的写业务代码
Java 虚拟机具体是怎样运行 Java 字节码的?
首先从虚拟机的角度来看:
- 首先将 Java 代码编译成的 class 文件
- 将 class 文件加载到 Java 虚拟机中(载入Java内存),加载后的 Java 类会被存放于方法区(Method Area)中
- 运行时,虚拟机执行方法区内的代码
Java 虚拟机会将栈细分为面向 Java 方法的 Java 方法栈,面向本地方法(用 C++ 写的 native 方法)的本地方法栈,以及存放各个线程执行位置的** PC 寄存器**。
本地方法,用关键字 native 修饰,基于JNI(Java Native Interface,Java本地接口),它允许 Java 代码和其他语言写的代码进行交互
在运行过程中,每当调用进入一个 Java 方法,Java 虚拟机会在当前线程的 Java 方法栈中生成
一个栈帧,用以存放局部变量以及字节码的操作数。(栈帧是提前计算好且不需连续分布)
当退出当前执行的方法时,不管是正常返回还是异常返回,Java 虚拟机均会弹出当前线程的当前栈帧,并将之舍弃。
Java 虚拟机将字节码翻译成机器码有两种形式:
- 解释执行:即逐条将字节码翻译成机器码并执行;
- 即时编译(Just-In-Time compilation,JIT),即将一个方法中包含的所有字节码编译成机器码后再执行。
解释执行的好处是无须等待编译,即时编译的好处是实际执行速度更快。
HotSpot 默认采用混合模式,综合了解释执行和即时编译两者的优点,它会先解释执行字节码,然后将其中反复执行的热点代码,以方法为单位进行即时编译。
关于即时编译
即时编译主要是为了提高 Java 虚拟机的启动性能以及峰值性能,它建立在程序符合二八定律的假设上,也就是百分之二十的代码占据了百分之八十的计算资源。
对于百分之八十(大部分)不常用的代码,我们无需耗费时间将其编译成机器码,而是采取解释执行的方式运行;对于百分之二十(小部分)的热点代码,我们则可以将其编译成机器码,以达到理想的运行速度。
为了满足不同用户场景的需要(在编译时间和生成代码的执行效率之间进行取舍),HotSpot 内置了多个即时编译器:C1、C2 和 Graal
- C1,又叫 Client 编译器,面向的是对启动性能有要求的客户端 GUI 程序,采用的优化手段相对简单,编译时间较短。
- C2,又叫 Server 编译器,面向的是对峰值性能有要求的服务器端程序,采用的优化手段相对复杂,编译时间较长,但同时生成代码的执行效率较高。
- Graal 是Java 10 正式引入的实验性即时编译器
从 Java 7 开始,HotSpot 默认采用分层编译的方式:热点方法首先会被 C1 编译,而后热点方法中的热点会进一步被 C2 编译。
小作业:观察两个条件判断语句的运行结果
通过观察两个条件判断语句的运行结果,来思考 Java 语言和 Java 虚拟机看待 boolean 类型的方式是否不同?
点击下载asmtools,并在命令行中运行下述指令
注:ASM 是一个 Java 字节码操控框架
$ echo '
public class Foo {
public static void main(String[] args) {
boolean flag = true;
if (flag) System.out.println("Hello, Java!");
if (flag == true) System.out.println("Hello, JVM!");
}
}' > Foo.java
$ javac Foo.java
$ java Foo
$ java -cp /path/to/asmtools.jar org.openjdk.asmtools.jdis.Main Foo.class > Foo.jasm.1
$ awk 'NR==1,/iconst_1/{sub(/iconst_1/, "iconst_2")} 1' Foo.jasm.1 > Foo.jasm
$ java -cp /path/to/asmtools.jar org.openjdk.asmtools.jasm.Main Foo.jasm
$ java Foo
结果分析:(来自评论区)
jvm 把 boolean 当做 int 来处理,flag = iconst_1 = true
awk 把 stackframe 中的 flag 改为 iconst_2
if(flag)比较时ifeq指令做是否为零判断,常数2仍为true,打印输出
if(true == flag)比较时if_cmpne做整数比较,iconst_1是否等于flag,比较失败,不再打
印输出
Hotspot 中的热点代码探测技术
怎样区分热点代码呢?主要有下面两种方法:
- 基于采样的热点探测
- 基于计数器的热点探测
计数器方法又可细分为方法调用计数器和汇编计数器
HotSpot的热点代码探测技术
-
方法调用计数器 统计一个相对的执行频率,即一段时间内方法被调用的次数,当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减,而这段时间就称为方法统计的半衰周期,进行热度衰减的动作在虚拟机进行垃圾收集时顺便进行了。一般采用的都是基于计数器的热点探测,基于计数器的热点探测又有两个计数器,方法调用计数器,回边计数器,他们在C1和C2又有不同的阈值
-
汇编计数器统计一个方法体重循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为汇编,显然,建立汇编计数器的目的就是为了触发OSR编译。没有计数热度衰减的过程,因此这个计数器统计的就是该方法执行循环的绝对次数,当计数器溢出的时候它还会把方法计数器的值也调整到溢出的状态,这样下去在再次进入该方法的时候就会执行标准编译过程。
扫码购买可加入专栏进行学习,一起成长
相关阅读
A5创业网(公众号:iadmin5)10月18日消息,今天滴滴APP迎来了新功能——黑名单,目前黑名单功能已经开始试运行,那么你知道这个
TOR是TheOnionRouter的缩写,直译就是洋葱路由。TOR官网上简单介绍了TOR的原理:TOR是一个三重代理,TOR客户端先与目录服务器通信
在命令行运行MonkeyRunner命令及通过MonkeyRunner运行
MonkeyRunner工具主要有三个类:MonkeyRunner、MonkeyDevice、MonkeyImage.可以直接使用一个代码文件运行monkeyrunner,抑或在交互式
一,概述 微机的工作过程就是不断地从内存中取出指令并执行指令的过程! 当开始运行程序时,首先应把第一条指令所在存储单元的地址赋
XCode iOS 12.3.1 (16F203) download 运行环境不支持
iOS DeviceSupport 12.3假的,暂时能用 https://github.com/XiaoHeHe1/iOS12.3- 参考:https://blog.csdn.net/qq_15509071/article/d