Java代码是怎么运行的「建议收藏」

Java代码是怎么运行的「建议收藏」Java代码有很多运行方式。在开发工具中运行双击jar文件运行在命令行中运行在网页中运行当然,上述运行方式都离不开JRE, 也就是Java运行时环境。JRE仅包含Java程序的必须组件,包括Java虚拟机以及Java核心类库…

大家好,又见面了,我是你们的朋友全栈君。

Java代码有很多运行方式。

  1. 在开发工具中运行
  2. 双击jar文件运行
  3. 在命令行中运行
  4. 在网页中运行

当然,上述运行方式都离不开JRE, 也就是Java运行时环境。

JRE仅包含Java程序的必须组件,包括Java虚拟机以及Java核心类库等。

而我们Java程序员经常接触到的JDK(Java开发工具包)同样包含了JRE, 并且还附带了一系列开发、诊断工具。

然而,运行C++程序则无需额外的运行时环境,C++编译器往往把C++代码编译成CPU能够理解的机器码。

那么,既然C++的运行方式如此成熟,我们为什么要在JVM里运行Java代码呢?

为什么Java要在虚拟机里运行?

Java作为一门高级程序语言,它的语法复杂,抽象程度也很高。因此在硬件上运行Java代码并不现实,所以运行Java程序之前,我们需要对其进行一番转换。

当前进行转换的主要思路是:设计一个面向Java语言特性的虚拟机,并通过编译器将Java程序转换层该虚拟机所能识别的指令序列(Java字节码)。之所以这么取名,是因为Java字节码指令的操作码被固定成一个字节。

Java虚拟机可以由硬件实现

https://en.wikipedia.org/wiki/Java_processor

当然,更多时候还是在各个现有平台(Windows_x64,Linux_aarch64)上提供软件实现。这么做的目的在于,一旦一个程序被编译成Java字节码,那么它变可以在不同平台上的虚拟机实现里运行。这也就是平时我们所说的Java的跨平台特性。

虚拟机的另外一个好处是它带来了一个托管环境(Managed Runtime)。这个托管环境能够代替我们处理一些代码中冗长而且容易出错的部分。其中最广为人知的当属自动内存管理与垃圾回收,这部分内容甚至催生了一波垃圾回收调优的业务。

除此之外,托管环境还提供了诸如数组越界,动态类型、安全权限等等的动态监测,使我们免于书写这些无关业务逻辑的代码。

Java虚拟机具体是怎么运行Java字节码的?

以标准JDK中的HotSpot虚拟机为例,从虚拟机和底层硬件两个角度,剖析该问题。

从虚拟机的角度来看,执行Java代码首先需要将它编译而成的class文件加载到Java虚拟机中。加载后的Java类会被存放于方法区(Method Area)中。实际运行时,虚拟机会执行方法区内的代码。

如果你熟悉X86的话,你会发现这和段式存储管理中的代码段类似。而且,Java虚拟机同样也会在内存中划分出堆和栈来存储运行时的数据。不同的是,Java虚拟机会将栈细分为面向Java方法的Java方法栈,面向本地方法(用C++写的native方法)的本地方法栈,以及存放各个线程执行位置的PC寄存器。

Java代码是怎么运行的「建议收藏」

 

在运行过程中,每当调用进入一个Java方法,Java虚拟机会在当前线程的Java方法栈中生成一个栈帧,用以存放局部变量以及字节码的操作数。这个栈帧的大小是提前计算好的,而且Java虚拟机不要求栈帧在内存空间里连续分布。

当退出当前执行的方法时,不管是正常返回还是异常返回,Java虚拟机均会弹出当前线程的栈帧,并将之舍弃。

在HotSpot里面,上述翻译过程有两种形式

  1. 解释执行,即逐条将字节码翻译成机器码并执行。
  2. 即时编译(Just-In-Time compilation, JIT), 即将一个方法中包含的所有字节码编译成机器码后再执行。

Java代码是怎么运行的「建议收藏」

前者的优势是无需等待编译,而后者的优势在于实际的运行速度更快。

HotSpot默认采用混合模式,综合了解释执行和即时编译两者的优点。

它会首先解释字节码。然后将其中反复执行的热点代码,以方法为单位即时编译。

Java虚拟机的运行效率究竟是怎么样的?

HotSpot采用了多种技术来提升峰值性能,上文提到的即时编译技术便是其中最重要的技术之一。

即时编译建立在程序符合二八定律的假设上。

二八定律:20%的代码占用了程序执行过程中80%的资源。

对于占据大部分的不常用的代码,我们无需耗费时间将其编译成机器码,而是采用解释执行的方式。

另一方面,对于仅占据小部分的热点代码,我们则可以将其编译成机器码,打到理想的运行速度。

理论上讲,即时编译后的Java程序的执行效率,是可以超过C++程序的。这是因为与静态编译相比,即时编译拥有程序的运行时信息,并且能够根据这个信息做出相应的优化。(实际上,编译时会插入一些有关jvm的代码)

举个例子,我们知道虚方法是用来实现面向对象语言多态性的。对于一个虚方法调用,尽管它有很多个目标方法,但在实际运行过程中他可能只调用了其中的一个,这个信息便可以被即时编译器所利用,来规避虚方法调用的开销从而达到比静态编译的C++程序更高的性能。

为了满足不同用户场景的需要,HotSpot内置了多个即时编译器:C1、C2和Graal。 Graal是Java 10正式引入的实验性即时编译器。

之所以引入多个即时编译器,是为了在编译时间和生成代码的执行效率之间做取舍。 C1又叫做Client编译器,面向的是对启动性能有要求的客户端GUI程序,采用的优化手段相对简单,因此编译时间较短。C2又叫做Server编译器,面向的是对峰值性能有要求的服务端程序,采用的优化手段相对复杂,因此编译时间较长,但同时生成代码的执行效率较高。

从Java 7开始,HotSpot默认采用分层编译的方式:热点方法首先被C1编译,而后热点方法中的热点会进一步被C2编译。

为了不干扰应用的正常运行,HotSpot的即时编译是放在额外的编译线程中进行的。HotSpot会根据CPU的数量设置编译线程的数目,并且按1:2的比例配置给C1及C2编译器。

在计算资源充足的情况下,字节码的解释执行和即时编译可同时进行。编译完成后的机器码后再下次调用时启用,以替换原本的解释执行。

我们来完成老师布置的作业:了解Java语言和Java虚拟机看待boolean类型的方式是否不同。

首先,撰写代码Foo.java


 
 
 
  1. public class Foo {
  2. public static void main(String[] args){
  3. boolean flag = true;
  4. if(flag)
  5. System.out.println( "Hello, Java!!");
  6. if(flag == true)
  7. System.out.println( "Hello, JVM!!!");
  8. }
  9. }

 
 
 
  1. javac Foo.java
  2. java Foo

显然,它的执行结果是:

Hello, Java!!
Hello, JVM!!!

我们使用asmtools.jar对其进行反汇编(此命令JDK7无法运行, 需要升级到JDK8)

下载地址:https://download.csdn.net/download/ti_an_di/10555815

java -cp ./asmtools.jar org.openjdk.asmtools.jdis.Main Foo.class > Foo.jasm.1
 
 
 

我们得到它的反汇编代码(在Foo.jasm.1 中)


 
 
 
  1. super public class Foo
  2. version 52:0
  3. {
  4. public Method "<init>": "()V"
  5. stack 1 locals 1
  6. {
  7. aload_0;
  8. invokespecial Method java/lang/Object. "<init>": "()V";
  9. return;
  10. }
  11. public static Method main: "([Ljava/lang/String;)V"
  12. stack 2 locals 2
  13. {
  14. iconst_1; //看这里
  15. istore_1;
  16. iload_1;
  17. ifeq L14;
  18. getstatic Field java/lang/System.out: "Ljava/io/PrintStream;";
  19. ldc String "Hello, Java!!";
  20. invokevirtual Method java/io/PrintStream.println: "(Ljava/lang/String;)V";
  21. L14: stack_frame_type append;
  22. locals_map int;
  23. iload_1;
  24. iconst_1;
  25. if_icmpne L27;
  26. getstatic Field java/lang/System.out: "Ljava/io/PrintStream;";
  27. ldc String "Hello, JVM!!!";
  28. invokevirtual Method java/io/PrintStream.println: "(Ljava/lang/String;)V";
  29. L27: stack_frame_type same;
  30. return;
  31. }
  32. } // end Class Foo

在运行指令

awk 'NR==1,/iconst_1/{sub(/iconst_1/, "iconst_2")} 1' Foo.jasm.1 > Foo.jasm
 
 
 

其作用是将Foo.jasm.1文件中第一个iconst_1 替换为iconst_2, 输出到文件Foo.jasm中


 
 
 
  1. super public class Foo
  2. version 52: 0
  3. {
  4. public Method "<init>": "()V"
  5. stack 1 locals 1
  6. {
  7. aload_0;
  8. invokespecial Method java/lang/Object. "<init>": "()V";
  9. return;
  10. }
  11. public static Method main: "([Ljava/lang/String;)V"
  12. stack 2 locals 2
  13. {
  14. iconst_2; //看这里
  15. istore_1;
  16. iload_1;
  17. ifeq L14;
  18. getstatic Field java/lang/System. out: "Ljava/io/PrintStream;";
  19. ldc String "Hello, Java!!";
  20. invokevirtual Method java/io/PrintStream.println: "(Ljava/lang/String;)V";
  21. L14: stack_frame_type append;
  22. locals_map int;
  23. iload_1;
  24. iconst_1;
  25. if_icmpne L27;
  26. getstatic Field java/lang/System. out: "Ljava/io/PrintStream;";
  27. ldc String "Hello, JVM!!!";
  28. invokevirtual Method java/io/PrintStream.println: "(Ljava/lang/String;)V";
  29. L27: stack_frame_type same;
  30. return;
  31. }
  32. } // end Class Foo

我们现在将flag的值由1改为了2, 将修改后的代码汇编到Foo.class文件中

java -cp ./asmtools.jar org.openjdk.asmtools.jasm.Main Foo.jasm
 
 
 

再次运行Foo类


 
 
 
  1. java Foo
  2. Hello, Java!!

可见JVM将true视为1, 不等于修改为2的flag,使用if_icmpne指令判断他们不相等,直接跳到L27执行,所以Hello, JVM!!!不会输出。而第一次判断是使用ifeq判断flag的值是否为0,所以Hello,Java!!会输出。

此文从极客时间专栏《深入理解Java虚拟机》搬运而来,撰写此文的目的:

  1. 对自己的学习总结归纳

  2. 此篇文章对想深入理解Java虚拟机的人来说是非常不错的文章,希望大家支持一下郑老师。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/156796.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • NSGA2_python

    NSGA2_pythonimportrandomimportnumpyasnpfrommatplotlib.tickerimport MultipleLocatorimportmatplotlib.pyplotaspltclassNSGA2():   def__init__(self,dim,pop,max_iter):    #维度,群体数量,迭代次数       self.pc=…

    2022年5月19日
  • jQuery的下载以及使用[通俗易懂]

    jQuery的下载以及使用[通俗易懂]一、版本选择jQuery的版本有很多,大家在选择版本的时候,一般原则是越新越好,但其实不然,jQuery版本是在不断进步和发展的,最新版是当时最高技术水平,也是最先进的技术理念。如何选择jQuery版本是个值得思考的问题目前jQuery有三个大版本:1.x:兼容ie678,使用最为广泛的,官方只做BUG维护,功能不再新增。因此一般项目来说,使用1.x版本就可以了,最终版本:1.12.4(2…

    2022年5月22日
  • switch中的continue和break区别[通俗易懂]

    switch中的continue和break区别[通俗易懂]今天c程设期末考试,突然有一道选择题是关于switch中的break和continue问题。若switch外部没有循环,则break和continue没有区别。若switch外部还有循环,{一.若break,continue在switch外部,则二者作用的是外部循环。二.若break,continue在switch内部,则break作用于switch,continue作用于外部循环。…

    2022年9月12日
  • 数据库基础知识一(MySQL)[通俗易懂]

    数据库基础知识一(MySQL)[通俗易懂]数据库是研究数据管理的技术。即如何妥善地保存和科学地管理数据。数据管理是指对数据进行分类、组织、编码、存储、检索和维护等操作。数据管理技术好坏评判的标准:(1)数据冗余(2)数据共享(3)数据独立性(4)数据统一集中管理数据库:按一定结构组织存储的、集成的、可共享的数据的集合。数据库有两种类型:关系型数据库与非关系型数据库。关系型数据库:存储格式能直观地反映实体间的关系,和创…

    2022年4月19日
  • mysql 日志文件_mysql日志文件在哪「建议收藏」

    mysql 日志文件_mysql日志文件在哪「建议收藏」本篇文章将介绍mysql中的几种日志文件位置,如何能够找到。mysql日志文件的查询方法:查找错误日志文件路径showvariableslike‘log_error’;在mysql数据库中,错误日志功能是默认开启的。并且,错误日志无法被禁止。默认情况下,错误日志存储在mysql数据库的数据文件中。错误日志文件通常的名称为hostname.err。其中,hostname表示服务器主机名。错误日…

    2022年10月15日
  • ArrayList扩容机制以及线程安全性

    ArrayList扩容机制以及线程安全性List扩容实现步骤总的来说就是分两步:1、扩容​ 把原来的数组复制到另一个内存空间更大的数组中2、添加元素​ 把新元素添加到扩容以后的数组中性能分析ArrayList作为动态数组,其内部元素以数组形式顺序存储的,所以非常适合随机访问的场合。除了尾部插入和删除元素,往往性能会相对较差,比如我们在中间位置插入一个元素,需要移动后续所有元素。源码分析先把ArrayList中定义的…

    2022年6月12日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号