Java代码RUN起来的背后

Java面试必问!run() 和 start() 方法到底有啥区别? start()方法实际上是一个底层的操作,它会告诉JVM创建一个新的线程,并调用run()方法。而run()方法仅仅是线程执行的入口,它不会自动启动新线程。简单来说, 阅读详情


前言

提示:对于java代码运行的全流程你心里有清晰的脉络吗?

  • 大家会不会跟我最开始一样,觉得在IDE里点一下RUN按钮,我们写的代码就直接直接跑起来了吧?

在这里插入图片描述

  • 其实只是因为有人在为你负重前行,编译器和虚拟机默默的承受了这一切。
    在这里插入图片描述

一、计算机真的能读懂我们写的代码么

  1. Java是一门"一次编写,到处运行"的语言,即它的跨平台性
  2. Java实现跨平台性,它的原理也很简答,就是利用中间格式来进行过渡,也就是我们常说的字节码,通过将Java源代码转换成字节码,保证JVM(Java虚拟机)读取到的一定是自己能够识别的字节码格式。
    在这里插入图片描述

一个通俗的解释:你不会说法语,法国人不会讲中文,但是你们或多或少都会点英语,把英语作为你们的中间格式,保证双方都能明白对方的意思,这就是所谓的跨平台。

  1. 说的更通俗一点,Java源码首先被编译成字节码,而这个字节码就是实现平台无关性的关键,无论你是什么类型的平台,只要你安装了能够识别字节码的JVM(Java虚拟机),通过JVM对字节码文件进行解析,把字节码转换成具体平台上的机器指令,就可以实现跨平台的运行了。
  2. 操作系统不一定欣赏到我们写的代码,我们所写的每一行代码,都会被编译成计算机能看懂的指令。

二、编译器在Java体系中的位置

1.JDK与JRE的关系

  1. 我就想装个Java环境,怎么有两个奇奇怪怪的安装包,一个叫JDK,一个叫JRE,这两个安装包跟俗称的”Java“又有什么关系?

下图是JDK8的体系架构图

在这里插入图片描述

JDK全称是Java开发工具包(Java Development Kit),它包含了Java从开发到运行的各种工具。
JRE指的则是Java运行环境(Java Runtime Environment),它包含了基础类库和JVM虚拟机。

  1. 既然安装JRE就能运行JAVA代码,但要需要完整的JDK才能完成开发,那他们之间的差集肯定跟开发的过程有关。所以接下来,我们来探讨一下为什么缺少这一块内容就只能成为运行环境,而不能承担开发功能呢?
    在这里插入图片描述

这一块我们可以看到几个很熟悉的命令:

  • javac:用于编译java源代码,生成class文件;
  • javap:用于反编译,根据class文件,反解析出其中的汇编指令和其他信息;
  • javadoc:用于生成java文档的命令。

我们用一个简单的例子看看,开发者编写好的java代码在完整的JDK架构下,经过JDK、JRE以及JVM的运行过程。
在这里插入图片描述

  • 可以看到,通过JDK中的Javac命令,我们才能将java源代码变异成class文件,class文件最终会放到jvm中运行
  • 我们把java源码到class文件的过程称之为编译阶段,把class文件到JVM中运行得到结果的阶段称为运行阶段。

可以看得出来,其实JRE缺少JDK是可以运行的,只不过是确实开发环境而已,但是要提前编译好class文件,所以说JVM并不关心是什么语言,只要生成JVM能够识别字节码文件就行了
在这里插入图片描述像我们熟悉的lombok,就能够根据我们编写的注解生成字节码,实现字节码的修改增强(但lombok也是利用了编译器的一些特性,是在编译阶段触发操作的)。
类似的还有诸如ASM等一些字节码增强技术,也是通过直接操作字节码来实现的。
通过字节码增强技术可以实现热部署等操作,让你修改代码之后无需重启服务就能生效;也可以实现日志注入等功能,在不需要改变客户端调用方式情况下完成对指定方法增加缓存或日志的功能。

2.编译阶段

编:将java源代码的结构组织成合适的格式,包括编译过程中的抽象语法树和符号表等,并在最终将源码编码成为class文件。

译:对源代码中的语义进行解析,并准确地翻译成另一种形式(字节码)。这一步既要确保原格式正确(Java源代码中的语法正确),又要确保翻译后的字节码跟源代码表达的意思一致。

接下来,我们循序渐进地告诉大家编译的具体步骤,以及编译过程的各个阶段抛出的不同编译异常。
在这里插入图片描述

2.1. 词法分析&语法分析

例如,你用这样一段代码去编译:

public class Hello {
    public static void main(String[] args) {
        String enum = "world";
        System.out.println("Hello world");
    }
}

会报如下的错误:

error: as of release 5, ‘enum’ is a keyword, and may not be used as an identifier

  • 因为enum是关键字,构建语法树的时候发现堂堂一个关键字居然出现在了标识符的位置,这可使不得啊!

  • 词法分析&语法分析是对源代码中文本的抽象,将.java源代码中的文本结构按照编译器特定的规则拆分、解析,为后续的编译工作铺平了道路,后面的操作都离不开这个AST。

2.2. 填充符号表

  • 符号表就是由符号地址(位置)和符号信息构成的”表格“,它存储的是标识所对应的类型、作用域等。

这里说它是”表格“可能会对读者产生一定的误解,实际上它不是像我们想象的那种二维的表格,而是更接近hashTable那样的键值对结构,符号表可以由数组、树状结构或者栈等各种结构来实现。

填充符号表的过程可以描述为:

  • 将每个AST的顶层节点都放到待处理的列表中,并逐个处理;
  • 将所有的类符号(类的声明,名称)都输出到外层的作用域的符号表中;
  • 如果发现有package-info.java文件(描述整个包的信息和包内的常量),将其顶层节点放到待处理的列表中;
  • 明确泛型类型的真实类型;
  • 如果类中没有任何构造器,则添加默认的无参构造器;
  • 将类中符号输入到类自身的符号表中。

2.3. 注解处理

  • 自从JDK 5以来,Java提供了对注解的支持,现在程序中使用注解已经是非常常规的操作

  • 然而要注意的是,并不是所有的注解都是在编译期起作用的,我们平时用反射处理的注解主要是指运行时注解,运行时注解在编译期不受影响,在编译之后的class文件中还是会保留,最终要在class文件到JVM运行的过程中才生效。

  • 而编译期注解是指以@Retention(RetentionPolicy.SOURCE)定义的,在编译期就处理了的注解,这一类注解不会保留到class文件中。

  • 听起来很懵,但其实编译过程中这一步注解处理其实大家在无意中已经接触过很多次了,比如大家常用的lombok,就是在这一步起作用的。

  • lombok采用的就是编译期注解处理的方法,因此当我们编译好用了lombok注解的.java文件后,打开生成的class文件就可以看到lombok相关的注解已经消失,而相应的getter、setter方法则已经被注入到class文件中。
    在这里插入图片描述2.4. 语义分析

我们用周志明老师书中的一个例子来说明: 假设有如下3个变量定义的语句:

public static void main(String[] args) {
	int a = 1; 
	boolean b = false; 
	char c= 2; 
	
	int d =a + c; 
	int e = b + c; 
	char f = a + c; 
}

这一段代码能够通过第一步的词法分析和语法分析,并构成正确的AST,但是在语义分析中会报错。因为编译器发现变量e和f的运算都是不符合规范的,参与运算的两个值的类型不匹配该运算符的逻辑。

语义分析更进一步检查上下文中变量的规范性,例如变量是否已经声明,变量的数据类型与其参与的运算是否匹配等等。

2.5. 解语法糖

  1. 语法糖就是方便程序员编写的便捷写法,这种语法不会对最终的结果产生实际影响,但能够减少程序编写者的工作量。
  2. java中的自动拆箱装箱功能、foreach循环功能等,都是为了程序员能够更写出更简洁流程的代码而封装的语法糖。
  3. 但是到了程序运行阶段,这样的语法糖对计算机来说是不可识别的。因此需要在编译阶段先解语法糖,将语法还原为它本来”笨拙“的样子。将包装类型拆成普通类型,将增强for循环替换为普通的for循环。

2.6. 生成Class文件

终于到了生成最终需要的class文件的一步了,前面所构建的语法树、符号表等信息,在这一步被转换成字节码指令写到class文件中

由于类加载过程优先于对象实例化过程,因此它们完整的执行顺序就是

  1. 父类静态变量初始化
  2. 父类静态语句块
  3. 子类静态变量初始化
  4. 子类静态语句块
  5. 父类变量初始化
  6. 父类语句块
  7. 父类构造函数
  8. 子类变量初始化
  9. 子类语句块
  10. 子类构造函数

除了生成构造器之外,生成class文件时还会优化某些代码逻辑的实现方式,比如,将字符串的+运算操作,替换为StringBuffer或者StringBuilder的append()方法。

总结

很多人会认为class文件 = 字节码,这是不对的,class文件并不等于字节码。我们从class文件的结构中可以窥见端倪,class文件中记录了如下的一些信息:

  1. 结构信息:class文件格式版本号;
  2. 元数据:主要对应的是Java源代码中”声明“和”常量“对应的信息,包括类的声明信息、类中属性域与方法的声明信息、常量池等;
  3. 方法信息:主要对应Java源代码中”语句“和”表达式“对应的信息,包括 字节码、异常处理器表、操作数栈和局部变量区的大小等;
    这下就很清晰了,字节码是Class文件的一个子集,只是class文件中众多组成部分的其中之一。
runJava.bat 本人在Sublime Text3 配置java环境时对照网上的 输入的runJava.bat总有错 终于发现了空格也要输先改对了提供遇到同样问题的同学下载 立即下载

相关推荐

Simulink实战:5步搞定固定翼飞行器六自由度动力学建模(附模型下载)

本文提供了一份详细的Simulink实战指南,通过5个核心步骤手把手教你构建固定翼飞行器的六自由度动力学模型。内容涵盖从顶层架构设计、核心方程手动实现,到模型集成、测试调试及进阶优化的完整流程,旨在帮助航空航天学生和工程师将抽象理论转化为可运行的仿真模型,并附有模型下载。

perl8的博客 634

windows下tomcat指定命令窗口标题

背景: windows操作系统,启动多个tomcat,多个命令窗口标题都是同名的“Tomcat”,难以区分。 解决: 为每一个tomcat命令窗口自定义标题,区分各个tomcat。 编辑tomcat所在目录中的bin路径下的catalina.bat文件,找到如下行: set _EXECJAVA=start "Tomcat" %_RUNJAVA% 将"Tomcat"修改为自定

张文健的专栏 1761

从零到一:基于Docker与OSM数据构建Valhalla路径规划服务及Mapbox风格可视化

本文详细介绍了如何从零开始,基于Docker容器技术快速部署开源的Valhalla路径规划服务。通过获取免费的OSM地图数据并构建路网,最终实现服务调用与Mapbox风格的地图可视化,为出行应用原型开发或本地化路径规划服务提供了一套完整、高效的实践方案。

weixin_29297127的博客 161

java run方法完美写法_Java线程中start和run方法全面解析

自定义线程两种方法自定义一个runnable接口的实现类,然后构造一个thread,即对thread传入一个runnable接口类。new一个thread或者写个thread子类,覆盖它的run方法。(new 一个thread并覆盖run方法实际上是匿名内部类的一种方式)示例代码public static void main(String[] args) {new Thread(new Runna...

weixin_29066781的博客 959

全球、中国各省市县等行政区划(含十段线)shape及geosjon数据免费下载

免费下载全球、中国各省市县等行政区划(含十段线)shape及geosjon数据

mhf403445651的博客 3458

华为OD机试新系统真题-魔法咒语组合(Java/Py/C++/Go/C/Js)

华为OD机试 新系统真题【魔法咒语组合】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD机试真题【魔法咒语组合】多语言题解

qq_45776114的博客 667

tomcat窗口修改显示名字

在运行多个tomcat窗口的时候,可以通过修改tomcat命令窗口的名字来区分不同的tomcat,修改如下: 找到tomcat下面的这个文件:tomcat_homeincatalina.bat 搜索到:set _EXECJAVA=start “Tomcat” %_RUNJAVA% 将"Tomcat"修改成想替换的名称即可 ...

a10534126的博客 2660

海外购物商城源码 带即时通讯IM源码

在海外购物商城中,即时通讯IM源码可以为用户提供更便捷、高效的沟通方式,帮助解决跨国交易中的语言和文化差异问题。总之,即时通讯IM源码在海外购物商城中扮演着重要的角色,可以为用户提供更方便、高效的沟通方式,解决跨国交易中的语言和文化差异问题。(购买本专栏可免费下载栏目内所有资源不受限制,持续发布中,需要注意的是,本专栏为批量下载专用,并无法保证某款源码或者插件绝对可用,介意不要购买)实时翻译:即时通讯IM源码提供实时翻译功能,能够快速准确地翻译不同语言之间的文字、语音、视频等信息,帮助用户克服语言障碍。

很酷的站长的博客 259

Java线程——常见方法

①:直接调用run方法并不会启动新的线程} };t1 . run();} }} };t1 . run();} }} };t1 . run();} }} };t1 . run();} }} };t1 . run();} }} };t1 . run();} }} };t1 . run();} }} };t1 . run();} }

m0_56188609的博客 1077

包络检波电路(Envelope Detector)

把 100 kHz 载波想象成"电台的高频信号",把 1 kHz 包络想象成"主持人说话的声波"。而今天电路的工作,就是把这个"调大调小的外壳"(包络)重新剥出来,还原成主持人的声音 🎵。这是三极管开关电路的重要搭档,也是从"二极管家族"过渡到"三极管家族"的桥梁 🔌。——倍压整流看的是"幅值累积"(多个电容接力),包络检波看的是"频率分离"(高低频分开)。同样的"D1 串联 + RC 并联"拓扑,今天被赋予了"AM 解调"的新生命。——电感负载的"泄洪闸",保护三极管开关不被反向电动势击穿。

飞多学堂的博客 93

LSTM 双向 Bi-LSTM

LSTM 双向 Bi-LSTM

jacke121的专栏 1516

java tomcat源码_详解Tomcat系列(一)-从源码分析Tomcat的启动

在整个Tomcat系列文章讲解之前, 我想说的是虽然整个Tomcat体系比较复杂, 但是Tomcat中的代码并不难读, 只要认真花点功夫, 一定能啃下来.由于篇幅的原因, 很难把Tomcat所有的知识点都放到同一篇文章中, 我将把Tomcat系列文章分为Tomcat的启动, Tomcat中各模块的介绍和Tomcat中的设计模式三部分, 欢迎阅读与关注.一:通过idea搭建Tomcat源码阅读环境首...

weixin_39714528的博客 245

Android/Linux驱动开发:使用dev_dbg进行设备驱动调试

在本文中,我们将介绍如何使用dev_dbg宏和相关的函数来进行设备驱动调试。函数在该目录下创建了一个名为"debug"的debugfs文件。在这些函数中,您可以执行与您的设备驱动相关的调试操作。函数在该目录下创建了一个名为"debug"的debugfs文件。在这些函数中,您可以执行与您的设备驱动相关的调试操作。的结构体指针,其中包含了我们定义的文件操作函数。的结构体指针,其中包含了我们定义的文件操作函数。在上面的示例中,我们首先通过调用。在上面的示例中,我们定义了。在上面的示例中,我们定义了。

ByteKnight的博客 372

的run代码_SpringApplication.run() 源码分析

我们都知道使用 Spring Boot 创建一个 Java 项目非常的简单,Spring 官网也给出了 Quickstart 示例。 只需要写几行代码,就可以让项目运行起来。在这简单的背后,是 Spring 默默的做了很多事情。今天我们就来研究一下 Spring 在背后为我们做了哪些事情。以下是 Spring 官网的 Quickstart 示例代码:@SpringBootApplication p...

weixin_29205373的博客 292

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

java多线程调用run和不调用run的区别

当在java程序中创建一个线程的时候,会三种情况: 1,只运行run方法 2,先执行start,再执行run方法 3,只运行start方法 三者的区别如下: 运行方式 区别 只运行run 只运行run的时候,多个线程的执行顺序是按代码顺序执行的,只不过执行的线程是当前线程,而不是你定义的线程,比如:你在main函数里面运行了定义了线程1、

xuqiaobo的博客 4672

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Java -- Thread中start和run方法的区别

2018年02月26日 19:32:28 JAVADeveloper_ZW 阅读数:179 标签: java 更多个人分类: JAVA多线程一、认识Thread的 start() 和 run()1。start():我们先来看看API中对于该方法的介绍: 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 结果是两个线程并发地运行;当前线程(从调用返回给 start 方法...

weixin_34185560的博客 78

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Android Studio源码分析系列一之点击Run背后的秘密

关键词:Android studio run背后的秘密,android编译流程提示:可能需要大概30分钟阅读时间背景:平时在开发过程中,大部人人都是通过点击Run' 按钮,运行app,Android Studio 就会开动,代码奇迹般地变成 APK,被安装到手机上,显示 APP 的界面。背后发生了什么?提示:以下是本篇文章正文内容,下面案例可供参考例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

博客 815

java Thread中start() 和run()方法的区别

start 用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。 start方法源码示例 一个 Java 线程的创建本质上就对应了一个本地线程(native thread)的创建,两者是一一对

u010968350的博客 399

java编写线程函数start,了解Java线程的start方法如何回调run方法详解

面试中可能会被问到为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?Java 创建线程的方法实际上,创建线程最重要的是提供线程函数(回调函数),该函数作为新创建线程的入口函数,实现自己想要的功能。Java 提供了两种方法来创建一个线程:继承 Thread 类class MyThread extends Thread{public void run() {...

weixin_34093899的博客 566

java线程的start方法_Java线程中的run和start方法

刚开始接触线程的时候,只是生硬的记住了如果要启动一个线程必须调用该线程的start方法,可是由于刚开始不经常使用多线程方面的知识,所以渐渐的就模糊了,现在常常碰到多线程的使用,知其然知其所以然嘛,今天就从源码方面探究一下。run方法查看一下JDK的源码,可以看到Thread中的run方法调用的是Runnable中run:private Runnable target;public void run...

weixin_36327582的博客 564

run as server和run as run java application

运行Java web 程序时,一直都是点击整个项目,后面的run as server,今天发现有些Java类可以后面有run as java application. 查了资料发现是因为有些Java类里面main方法,运行main方法时就用run as java application.而web项目的Java类很多都没有main方法,右键后没有出现run as java appl

我想问问天的专栏 5900

JAVA代码编译流程

java 生成class文件过程

weixin_46164384的博客 1万+

Flask Run运行机制剖析

但一般我们几乎不会直接写WSGI接口,这样编写代码还是过于低级编写维护困难,然后在WSGI基础上进一步抽象,用一个函数处理一个URL,这样就交由Web框架来做,实现WSGI的Web框架有Flask、Django、Tornado等等。WSGI全称Web Server Gateway Interface ,它是一个规范,定义了Web服务器如何与Python应用程序进行交互,使得Python写的Web应用程序可以和Web服务器对接起来,它与JAVA体系中的Servlet是差不多的概念。

程序员青菜学厨记 906

Java面试必看:start()和run()哪个才是正确的线程启动方式?

《Java面试必看:start()和run()哪个才是正确的线程启动方式?》:在Java中,线程的启动必须通过`start()`方法,而非直接调用`run()`。`start()`会启动新的线程并执行`run()`中的代码,而`run()`仅在当前线程执行,不开启新线程。理解这一机制对多线程编程至关重要,确保程序高效运行和避免潜在错误。

yanpeng2580的博客 443

java thread start到run:C++源码分析

转:https://hunterzhao.io/post/2018/06/11/hotspot-explore-inside-java-thread-run/ 整体流程 java new Thread()时,本地线程并没有创建,是在start()时才开始创建本地线程 os线程创建之后, 处于wait状态 Th...

weixin_30471561的博客 221

python多线程没有java_Java 多线程启动为什么调用 start() 方法而不是 run() 方法?...

多线程在工作中多多少少会用到,我们知道启动多线程调用的是 start() 方法,而不是 run() 方法,你知道原因吗?在探讨这个问题之前,我们先来了解一些多线程的基础知识~线程的状态Java 中,定义了 6 种线程状态,在 Thread 类可以找到: // 为了节约空间,我删除了注释public enum State {NEW,//初始状态RUNNABLE,//运行状态BLOCKED,// 阻塞...

weixin_35452394的博客 133
下一篇: Mybatis xml中SQL处理小于号与大于号
一枚双鱼座的程序员
博客等级 码龄7年 7粉丝 6原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一枚双鱼座的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值