前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >猿思考系列1——一文搞懂java代码的执行套路

猿思考系列1——一文搞懂java代码的执行套路

作者头像
山旮旯的胖子
发布2020-07-28 16:49:27
2380
发布2020-07-28 16:49:27
举报
文章被收录于专栏:猿人工厂

看完猿进化系列文章,相信你已经具备比较熟练的进行简单的web开发的能力了,不过还不够,要想快速的成长为一个合格的猿人,必须先学会猿人的思考方式。真的很感谢大家的支持,和巨兽的斗争仍然在进行,关于为什么我还在写文章,我想说,能写点儿是点儿,也许是最后一课,猿人工厂君已经说了,虽千万人,吾往矣。程序员更多的时候还是需要正义,坚持,勇敢和不退缩。如果觉得文章还凑活,也希望你关注和转发本文,好文章大家分享嘛。

程序是一组计算机能够识别和执行的二进制指令。计算机能够识别和执行的永远都是二进制语言,这样子讲可能有点抽象了。举个简单的例子,比如我们用电脑打开图片,当图片在磁盘上时,它只是一个数据。当图片被打开了,图片会被转化为二进制数据装在到内存中,此时内存中的图片数据,其实就是机器可以识别和执行的了,确切来说,内存中的二进制数据,都是程序。

我们回到java程序这个话题上来,java程序也是程序。我们编写的xxx.java文件,是什么呢?我们称它为源代码。计算机是不能直接执行源代码的,因为机器不认识它啊。那怎么办呢?只好将源代码翻译成机器能识别的二进制代码了!这个翻译的过程,我们把它称之为“编译”。可是这种直接能运行的二进制代码,在不同的操作系统的运行方式是不同。JAVA程序为了达到所谓的“一次编写,到处运行”,那么想了一个中间一点的办法,先把源代码翻译为java字节码,也就是我们看到的.class文件啦,然后针对不同的操作系统搞了不同的JVM,这个JVM再负责把java字节码转化为计算机能够识别的二进制代码。

JVM实际上是一种抽象化的计算机体系结构,通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。不同的操作系统上有不同的JVM实现,所谓的跨平台,一次编写,到处编写就是这么来的。

我们都知道,源代码就是一段有规则的字符串,我们要把源代码,转换为JVM能够识别的字节码。肯定需要编译器来做这个事情。开动你OO的大脑,跟上节奏,一起分析分析。

你看哈,所有的源代码都是有规则的。比如java语言的源代码,有很多关键字,比如int, long,float…这些都代表了数据类型,这些关键字是不可分割的,是一种标记我们首先需要讲这些东西提取出来吧。这些都是不能分割的东西,是最小的元素,除了关键字,还有修饰符、变量名、运算符、字面量等等,这些东西比较多,我们可以考虑使用集合来存储它,一般这样的集合我们称之为token集合。这个提取token的过程我们叫做词法分析,最后啊,把源代码转化为一个树型结构的集合,我们称之为抽象语法树。

然后呢,java程序还有一些默认的事情需要做,比如一个类没有编写构造函数,这个时候,还要去填充默认的构造函数,再比如,对于一些annotation的处理等等,这一类操作都是建立在之前的抽象语法树之上噢。这个过程被叫做符号表填充,符号表是一种用于语言翻译器(例如编译器和解释器)中的数据结构。在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型、作用域以及内存地址。符号表在编译程序工作的过程中需要不断收集、记录和使用源程序中一些语法符号的类型和特征等相关信息。这些信息一般以表格形式存储于系统中。如常数表、变量名表、数组名表、过程名表、标号表等等,统称为符号表。对符号名进行地址分配时,符号表是地址分配的依据。

源代码的信息经过收集和填充之后,是需要做一些检查的事情的,比如变量使用前是否已被声明、变量和赋值之间的数据类型是否匹配,这类事情被叫做标注检查。还有一些事情,比如去检查方法体的每条途径是否都有返回值,返回值是否正确,异常是否都正确处理等等这一类的事情,被叫做数据及控制流分析,总之就是检查语法是否满足语言特性。还有一些支持语言特性的事情,比如支持泛型、支持枚举、自动装箱与拆箱、支持可变参数等等,被做解语法糖,以上这些过程都可以被称为语义分析。

做完了这些事情自然就是生成java字节码了。这个过程其实就是将之前各个阶段产生的信息,写入到磁盘中,另外还要进行少量的代码添加和转换工作。

我们可以简单的画个图,java代码的编译过程如下:

我们先来搞懂什么是环境变量吧,环境变量一般是指在操作系统中,用来指定操作系统运行环境的一些参数,比如系统文件夹位置等等。配置JAVA程序的环境变量,等于是告诉操作系统,“我”在哪个位置。这个“我”就是JVM的位置。如果不做配置,操作系统是找不到我的,java程序的执行也就无从谈起了。

在思考字节码的加载过程前,我们不妨看看,一个类什么时候会被加载JVM规范要求,一个类或接口在初次使用时,才会被初始化。也就是在第一次才会被初始化。那么我们什么时候才会使用到一个类呢?创建实例、静态方法调用、调用main方法、初始化一个子类必先初始化其父类也算吧、比如使用某个静态常量(非final的那种)、使用反射调用方法……这些行为属于主动去触发的,算主动使用了,除此之外的行为,都属于被动使用了。比如编写main方法,直接访问子类从父类继承而来的静态属性,这种情况下,父类会被初始化,而子类不会,子类只会被加载。

除了加载之外,还会干什么事情呢?开动你OO的大脑想想肯定需要验证吧?要不数据对不对还不知道呢。在使用之前,总还是需要准备一下吧比如为变量分配点内存什么的。Java代码的引用是不是也需要换为内存地址或者偏移量呢?还是需要做些初始化的事情吧?比如静态代码变量,静态代码块什么的。具体步骤嘛,大概率长下面这样:

嗯,我们先想想,java程序中都包含哪些东西吧,具体步骤会不会先不说,我们可以利用OO的思想考虑一下吧。首先呢,java源代码需要被执行,那么它就是java字节码吧。Java字节码需要被执行,肯定需要一个加载字节码的系统,因为我们加载的是一个又一个类,那么姑且叫做类加载系统吧,字节码被加载了,肯定需要被执行,那么这个执行代码的东西,我们就叫它执行引擎好了。我们知道,每一个操作系统都提供了系统函数用于调用,但是每一个操作系统都有所不同。所以呢,我们可以考虑对这些系统函数做一个封装,这些被封装的系统函数又好多的,我们就叫做本地方法库吧。考虑到操作系统也可能不断升级,我们考虑利用面向对象的特性去解决它,嗯,先封装为接口吧,这样把接口与执行引擎和本地方法库进行对接,如果操作系统发生变化,接口不用变化了,改本地方法库就好了。这些接口当然是叫本地接口了。

执行层面的事情考虑好了,我们想一想,java程序都包含了哪些东西?java程序会创建对象吧?把这些对象集中起来管理,这个地方就叫堆。我们运行的java程序,都是要调用方法才能执行的,一个对象中,比如方法,常量,有静态变量,这些东西我们就把它放到方法区吧,他们和程序的运行是相关的。可是好多时候,java还需要调用系统函数,还可能执行其他语言编写的代码啊,这个怎么办呢?也好办,再搞一个本地方法区去存放它就好了。

这些东西都想好了,可是程序依然没能运行起来鸭,那么每一个方法又有什么东西?有方法变量,有返回值,有我们编写的基本语句,有其他方法的调用,方法怎么才能被调用到?肯定是需要地址的啊。好嘛,这堆东西我们一股脑儿的放一起吧,交给执行引擎去执行吧。可是这个地方还没有名字鸭,就叫虚拟机栈吧。嗯,好像还没遇到本地方法,遇到了怎么办?再搞个本地方法栈吧。

接下来我们要考虑程序的具体执行了。一个程序虽然被编译了,但是程序与程序之间还有一些调用关系,这些调用关系需要装配成到一起,程序才能正确执行。这个装配的过程叫做链接。考虑到库函数是很多的,如果在程序运行前需要全部装配好的话,会占用大量的资源。那么我们可以考虑在程序加载时,在来做这个装配。这个过程我们叫做动态链接吧。

那么这样一来,我们可以把每个方法的执行,放到虚拟机栈中,为其分配一个栈帧,每个栈帧都装着方法变量,操作栈数,动态链接,和方法出口就好了。

嗯,当然,程序想要连续的运行下去,必须让CPU知道下一条指令的地址在哪儿,那么这个自然是程序计数器的功能啦。Java是一个多线程的语言,为了让程序更快的执行,我们每一个线程分配虚拟机栈,本地方法栈,程序计数器就好啦。到此为止,为了方便理解和记忆,我们得到了下面这个草图:

缓存是什么?是计算结果。什么场景下需要缓存?多次需要结果的地方需要缓存。字节码被解释执行一次,就可以得到相关机器码的执行结果,为什么不把这个相关的机器码“缓存起来”,将多次执行的字节码直接转换为机器码,下次就不解释了,直接执行机器码就好了。

JDK的 HotSpot虚拟机就搞定了这个问题。提供了两种代码的执行方式:

1.解释执行,即逐条将字节码翻译成机器码并执行。

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

HotSpot是一种热点探测技术,它又是怎么知道哪些代码是热点呢?当然是需要计数器了。没有统计就没有准确嘛。当方法执行超过一定阈值,就认为是热点,就把这段字节码转换为机器码就好了鸭。要统计方法的执行热点怎么办鸭?自然是看方法被了用了多少次了,方法里还有循环体,是不是也应该统计下呢?

HotSpot提供了两种计数器,一种是方法调用计数器,用于统计方法的调用次数,一种是回边计数器,用于统计方法体中循环体代码的执行次数。那你说代码怎么执行呢?画个流程图吧,大概率是这样的:

我还是会坚持写技术文章,再难也要坚持,只是速度可能没以前快了,但是很多内容是需要你看完认真思考的,知识的掌握不是学来的,敲视频代码敲会的,学会思考,学会真正的OO,慢慢的你就会快速进入一个高手的境界。

想要更好的了解JVM的相关知识,明白JAVA的真正原理,给你推荐本书,买下来,时常翻翻就好,一年下来功力大涨。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 猿人工厂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档