当前位置: 首页 > article >正文

JVM之Java编译到执行(1)--引

Java语言特点

一次编写,到处运行。也就是跨平台。 因为这个跨平台的实现原理,而导致Java的编译流程,与以往的C++之类语言有不同。

各个操作系统的底层实现,资源的协调,和硬件操作各有各的不同。就意味着,如果按照早期使用C++这类语言写,每个系统都有不一样的API,不仅存在大量的API调用区分,而且那就要为每一个操作系统都要编译一次。说来简单,楼主做过这种工作,出bug的时候,会编译很多次,光编译这里,就要消耗不少精力。真的比较烦。

想要做到一次编写到处运行的话,Java采取的方案是,将程序员编写的代码首先优化为一个字节码文件,然后为每一个操作系统提供虚拟机,也就是JVM。 这个虚拟机里可以解释这个字节码文件,并将其生成相应系统可以识别的机器码。所以最简化的路子是下图所示。

这样的话有什么好处呢?

  • 只要字节码文件一样,JVM虚拟机又是专业的人提供的,可以保证上层调用的API是一致的。也可以保证得到良好的解析,最终生成各自系统可识别的机器码,并执行。
  • JVM虚拟机是被人提供的,就意味着,他们对这块的操作权是很大的,可以用顶级的智慧,顶级的开发人员,对各个系统的虚拟机之后进行不断的优化。不用程序员管。
  • 对于语言的支持方面,需要提供良好的API,这就意味着,API的开发操作权也在专业的公司手里。他们足可以按照比较先进的思想,提供比C++好用多得多的API。减少下游程序员的学习难度。

编译过程

Java语言的编译,因为上述的设计方案。会导致编译过程分为两个过程

  • 1 从源代码编译到字节码的环节 (称为前端编译)
  • 2 从字节码到可执行程序的环节 (称为后端编译,运行时做的工作)

其中前端编译,就是那种在计算机中输入一个javac指令,然后就编译成了class文件。在浅层的理解上,认为这就是编译完成了。事实上class文件它不能直接执行的,离执行还差的远。

这些class文件,在真正执行的时候,会让JVM去处理,这个时候属于运行时了已经。在运行时,JVM会拿到已经生成的class文件集,将这些文件里的一条条指令解释为相应平台可以识别的机器码。这样两种配合的情况下,我们写的代码才得以运行。

“解释”

解释型语言简述

解释型语言指的是程序不需要程序员进行编译,程序在运行时才会被翻译成机器码的那类语言。这就意味着,每次执行都得解释一次。因此这类语言有个普遍的缺陷,是效率较低。但是其优势就是跨平台性比较好,我们这里的跨平台烦恼,主要就是编译不同平台所带来的工作工时还有失误的消耗,这个是比较折磨开发者的。解释型是可以解决这种问题的。代表语言为:Python/JavaScript / Perl /Shell 等。 Java也算是。但又没那么彻底。

编译型语言简述

编译型语言的特点是,写的程序执行之前,需要一个专门的编译过程,把程序编译成为机器语言的文件,比如exe文件,以后要运行的话就不用重新翻译了,直接使用编译的结果就行了(exe文件),因为翻译只做了一次,运行时不需要翻译,所以编译型语言的程序执行效率高。缺点就是,开发和维护成本高。其代表语言为 C C++。

解释器VS编译器

上图中有个环节,有一个叫解释器的部分。这块最开始是令我比较迷惑的。原因是我认为Java作为一个解释型语言,那直接解释就好喽。解释不就是编译么?为什么我看到书上,解释和编译要区分呢?为什么这个流程甚至是一边解释一边翻译呢?

原来这里面存在一个区别,解释器和编译器,是实现同一种目标的两种不同的工作方式。

解释器

解释器是直接执行用编程语言编写的指令的程序。它逐行读取源代码,将其转换为机器语言(或某种中间形式),然后立即执行。这意味着解释器在执行程序时,需要逐行解释代码,因此执行速度通常比编译器慢。

编译器

编译器则是将源代码整体转换(或“编译”)为机器语言或低级语言(如汇编语言)的程序。它首先会对源代码进行词法分析、语法分析、语义分析等,然后生成中间代码,并最终转换为机器代码。这个过程通常称为“编译”,生成的机器代码可以独立运行,无需解释器的支持。

前端编译

javac就是Java的前端编译器,是个编译工具,负责把程序员写的java文件,经过初步的分析,转换为class文件。 class文件就是字节码文件。二者可以一块理解。

Java的前端编译基本就是做了这个工作。它的编译流程不做赘述,相当于C++编译或者编译原理的打折版。什么词法,语法,语义分析。这个知识点探下去没完没了,是一个比较深的话题。略过。流程上图已经给出了可以翻上去看一些图片。

Java从编写代码到执行涉及到的编译器概览

我们重点注意的是下半部分,主要由JVM承担的,发生在运行时的后端编译工作!

Java严格的说是一个编译 + 解释混合的语言。这使得我们讨论Java编译到执行这个话题时,不止要讲静态的编译,还要深入了解Java是怎么执行代码的,比如,解释器一条条指令解释,那指令是哪儿来的?存哪里了?类加载环节是不是和这里有关呢?类加载究竟做了什么事?Class除了反射时用,JVM到底用它做什么了?为什么需要它?class文件里面好像有操作指令,这些指令是不是相当于C++可执行程序里的代码段呢?new一个对象JVM划分内存,那个垃圾回收机制是怎么管理这些对象的? 逐渐铺开发现相当于讲1/3JVM机制,一片文章根本讲不完。我会拆分下来一块一块讲。

JVM后端编译

后端编译流程图

对于Java解释字节码很粗略的流程,在上图已经画出来了。总共拆分为两个阶段。

  • 类加载 and 二进制转换
  • 解释器解释 和即时编译器优化同时进行。(为什么这样之后解释)
需求分析

在了解一个方案的解决办法前,有时候思考问题,要把事件重置到还未发生时考虑。有没有想过为什么JVM的后端编译器为什么要把流程搞成这样子?还什么类加载,还要解释器,那个JIT是啥?为什么要有这个?

我们不妨以一个实现者的视角来聊聊。假设你就是这个编译器的实现者

  • 你们的这个大工程中,你负责开发后端编译器这块的组件。你组件的输入内容已经确定了,是前端编译机器的产出--字节码文件,即字节码文件。
  • 你这个组件的产出结果是,生成机器码,能上机跑的那种。(我们排除掉一切更为复杂的需求,如内存回收等等机制)

那你会怎么解决呢?

我想是个人都得先分解输入内容和输出内容这两大关键点的样子吧。 于是乎

  • 1 你必须要从java语言设计的思想,和所有语言通用思想方面来考虑java语言的组成结构。并能做到很好的拆分转换优化。另外,有些代码调用到中级逃不过C语言或C++,他们的接口入口地址,您要怎么找,怎么正确的连接。等等等等一堆的问题。
  • 2 你必须要十分熟悉操作系统原理,尤其是一个程序要跑起来,计算机内部的状态究竟是什么样子。你需要将你输入的那些二进制代码,落实到运行时内存的数据里!
  • 3 锦上添花的其余功能,例如垃圾回收机制。

CPU如何执行程序

粗略的讲。CPU本质上是一个没有主见的, 沿着代码块内存一条一条机械执行的工具, 我们对代码块做了很多的编程, 保证操作指令和被操作数是正确的, 这样cpu识别这些指令,也能一条接一条执行.基本没有错误.

您编译代码的目的就是,把代码变成最终CPU识别的样子。

JVM的思路

JVM的思路就是,模拟这种场景。只不过“代码块内存”里的指令是class文件中描述的字节码指令。JVM模拟CPU的操作流程,也是一条一条往下“读”,像极了CPU执行指令那般。在“读”的过程中,JVM会通过编译器或者解释器,将这些字节码指令转换为真正的机器码。这样就达成了与CPU协议的一致。

JVM的整体流程开始于类加载,我们知道了JVM宏观的思路后,再研究每个环节,下篇文章将先从类加载讲起, 类加载的输入是class文件(字节码文件),楼主将贯彻费曼学习法,知无不言,将他们写出来。


http://www.kler.cn/a/516692.html

相关文章:

  • H3C-无线WLAN配置案例(二层隧道转发)
  • 算法随笔_19: 数组中的最长山脉
  • WIN11 UEFI漏洞被发现, 可以绕过安全启动机制
  • 优选算法——哈希表
  • AIGC视频生成模型:ByteDance的PixelDance模型
  • SSM开发(二) MyBatis简介
  • 教育培训微信小程序ssm+论文源码调试讲解
  • CBAM-2018学习笔记
  • 如何使 LLaMA-Factory 支持 google/gemma-2-2b-jpn-it 的微调
  • 网络(二)协议
  • GIT的常规使用
  • 【MySQL — 数据库增删改查操作】深入解析MySQL的create insert 操作
  • docker 启动镜像命令集合
  • Java 大视界 -- Java 大数据中的异常检测技术与应用(61)
  • ESP8266 OTA固件启动日志里分区解析【2M flash】
  • 【Java实现 通过Easy Excel完成对excel文本数据的读写】
  • 递归的本质
  • Rman还原
  • Yii框架中的Cart组件:实现购物车功能
  • GC(垃圾回收)的分类
  • 使用 Elasticsearch 导航检索增强生成图表
  • linux-centosubuntu本地源配置
  • 蓝桥杯练习日常|c/c++竞赛常用库函数
  • 使用Python爬虫获取1688店铺所有商品信息的完整指南
  • C#高级:常用的扩展方法大全
  • ubuntu系统docker环境搭建