JVM学习第一篇思考:一个Java代码是怎么运行起来的-上篇
作为一个使用Java语言开发的程序员,我们都知道,要想运行Java程序至少需要安装JRE(安装JDK也没问题)。我们也知道我们Java程序员编写的程序代码文件是*.java的,而JRE运行的是*.class的文件。所以,我们需要将java文件编译成class文件然后才可以。那么,你有没有想过,一个java文件是怎么运行起来的呢?中间都经历了哪些环节呢?我们都知道JVM是Java虚拟机,那么,有没有思考过JVM的内存模型是什么呢?我们new出来的对象,声明不同类型的变量又是存放在JVM哪个位置呢?
本文是凯哥(凯哥Java:kaigejava)学习JVM系列教程第一篇。欢迎大家一起学习
本文目标:
通过本文学习后,希望大家对JVM类加载过程有个了解。
上面程序很简单。那么,有没有想过上面代码怎么运行的呢?
选中main方法,然后ruan as...,编译后,运行输出。这个流程我想大家都很熟悉的。那么对应的流程应该是什么样的呢?如下图:
在Run的时候,先将.java文件编译成.class文件。然后,在通过类加载器,将class文件加载到JVM中,然后在运行。输出结果。
那么为什么编译好的AppTest.class可以加载到JVM中呢?可以被JVM识别呢?
一个java类的一生都会经历哪些步骤呢?
如下图:
在我们run的时候,AppTest.java类先经过编译后,编译成了AppTest.class文件。JVM把class文件加载到内存后需要经历:加载-验证-准备-解析-初始化-使用-卸载这七个阶段。
第一个问题:JVM在什么时候会加载一个类呢?起始也就是在什么时候会加载.class字节码文件到JVM的内存中去呢?上面我们写的,当我们run的时候,才执行的。所以答案就很明确了,就是在你代码中需要使用到这个类的时候,就去加载的。
具体每一步:
加载
加载阶段是将class文件从磁盘或者jar等读到JVM内存中,并为其创建一个Class对象。任何一个类被使用时候系统都会为其创建一个Class对象的。
加载的同时将加载的这些数据转换成方法区中运行时数据(运行时候数据区:静态变量、静态代码块、常量池等),作为方法区数据的访问入口
这个很好理解的。我要想使用你,需要先得到你,是不是。结合上面我们自己写的AppTest类。在此阶段应该是:
扩展:
在类加载阶段JVM都做了什么?获取class文件方式都有哪些?
1.1:在类加载的时候JVM完成了以下:
- 根据类的全路径(全限定名)来获取到该类的二进制字节流
(我们知道,在电脑的世界中,什么都是二进制形式存在的)
- 将加载的字节流中所代表的静态存储结构转换成方法区运行时数据结构
(这个话具体怎么理解,有哪位能留言教教凯哥)
- 将加载的对象在内存中生成一个代表了该类的jvaa.lang.Class对象。这个Class对象作为加载进来对象在方法区各种数据的访问入口。
(要想在内存中访问AppTest这个字节码类中的属性或者方法的时候,可以在内存中方法区找到对应的Class对象。这个Class就是入口)
关于方法区在后面文章中,凯哥会详细讲讲。
1.2:获取class文件的方式
- 可以直接从本地的磁盘文件获取
- 可以从忘了下载class文件
- 可以从ZIP或者jar等文件中
- Java源文件动态编译的class文件
在一个类运行生命周期内,类加载(加载获取类的二进制字节流)阶段,是可控性最强的阶段。因为在这个阶段,我们程序员可以使用系统提供的类加载去来加载完成,也可以使用自己自定义的类加载来完成.(类加载器在后面文章详细讲讲)
1.3:类加载的具体时机,在文章最后,凯哥会列出来。
验证
将上一步加载到内存中的Class对象进行校验。确保加载的类的信息符合JVM的规范。确保没有安全方面的问题。
这个很好理解了,我要使用你,得到你好,我要检查你是不是符合标准的。如果不合法,就没法使用。
在此阶段如下图:
扩展:验证都验证哪些方面?
- 文件给是验证:验证加载的字节流是否 符合Class文件格式的规范。
例如:是否已咖啡babe开头(0xCAFEBABE),主次版八号是否在当前JVM的处理范围内等等
比如你在JDK1.8下编译的class文件,放到JDK1.6版本的JVM中,有可能就运行不了的
- 元数据验证:对字节码描述的信息进行语义分析。保证描述信息符合Java语言规范。
例如:这个类如果有父类,是否实现了父类的抽象方法等.
- 字节码验证
- 符号引用验证:确保解析动作是正确的。
例如:通过符号引用能找到对应点的类和方法。比如com.kaigejava.Person.getAge()
在比如:符号引用中类、属性、方法的访问性是否能被当前类访问等等。
准备
准备阶段,就是给加载进来且验证通过的Class类分配空间的。这里是给类里面的变量(也就是static修饰的变量)分配空间的,同时给变量一个默认的初始值。
如下图:
在准备阶段时候static int m 被分配了4个字节的空间,且分配了默认初始值为0(注意默认初始值是0).
PS:int类型占用4个字节。int的默认值是0.如果是对象的话。默认为null
在此阶段AppTest.class如下图:
该阶段需要注意:
- 在此阶段值只对static修饰的静态变量进行内存分配,赋默认值的(比如0、0L、0D、null、false等);
- 对于final修饰的静态字面值常量直接赋初始值(注意:这里的初始值并不是默认值。如果不是字面值静态常量,那么会和静态变量一样赋默认值)
比如:final int x = 1;这个在此阶段就给赋值的就是1而不是0
解析
解析是将常量池中的符号引用替换为直接引用(内存地址)的过程。
在此阶段AppTest类如下图:
扩展:
符号引用:
就是一组符号来描述目标的。可以是任何字面量。这个属于编译原理方面的东西。
比如:可以是一个类的完整类名字(com.kaigejava.Person)、字段的名称和描述符、方法的名称和描述等。
直接引用:
就是直接指向目标的指针、相对偏移量或者一个间接定位到目标的句柄。比如指向方法区中某一个类的一个指针。
例如:在AppTest这个类中,有个static的静态变量p。这个静态变量p又是一个自定义的类型(com.kaigejava.Person),那么在经过解析阶段后,这个静态的p变量将是一个指针(比如0xddff1),这个指针指向该类在方法区的内存地址值。具体见凯哥后续文章,将会详细讲解。
初始化
到了此阶段(初始化阶段),JVM才开始真正的执行类中定义的Java代码。
当进行到初始化阶段的时候,就是执行类的构造器<clinit>()方法的过程。
- <clinit>()方法是由编译器自动收集类中的所有类变量赋值动作和静态语句。
- <clinit>()方法与类的构造器不同。此方法不需要显示的调用类的父构造器(如果类有父类的话),虚拟机会保证在子类的<clinit>()方法执行前,父类的<clinit>()方法已经执行完毕。因此JVM中第一个被执行的<clinit>()方法的类肯定是java.lang.Object(因为Java中所有类的父类是Object类)
- 因为父类的<clinit>()方法先执行,所以也就意味着父类中定义的static语句块要优先于子类的变量赋值操作
- 如果一个类中没有静态变量或者是静态的语句块的时候,编译器可以不为这个类创建<clinit>()方法的
- 虚拟机会保证一个类的的<clinit>()方法在多线程环境中被正确的加锁和同步。多线程访问,一个访问,其他在访问的话会被阻塞。
使用
类实例化也初始化成功之后,这个类就是一个正常的类了。我们可以正常使用了。
卸载
当遇到以下几种情况的时候,类会被卸载
- 执行了System.exi()方法的时候
- 程序正常执行结束
- 程序在执行过程中遇到了异常或者是错误而异常终止
- 由于操作系统出现错误导致Java虚拟机进程终止
今天问题:
现在我们知道了一个Java类是怎么运行起来的了。那么请看下面代码,运行后输出的顺序是什么?
public class JvmDemo { public static void main(String[] args) { Son son = new Son(); FatherInterface fatherInterface = new SonInterFace(); fatherInterface.say("凯哥Java"); } } class Father{ static String st1 = "父类Father中的静态变量"; String str2 ="父类Father中的非静态变量"; static { System.out.println("当前执行了父类Father的静态代码块中的方法"); } { System.out.println("执行了父类Father类中的非静态代码块"); } public Father(){ System.out.println("执行了父类Father中的构造方法了"); } } class Son{ static String str1 = "子类Son中的静态变量"; String str2 = "子类Son中的非静态变量"; static{ System.out.println("执行了子类son中的静态代码块"); } { System.out.println("执行了子类Son中的非静态代码块"); } public Son(){ System.out.println("执行了子类son中的构造器方法"); } } interface FatherInterface{ static String str1 = "接口父类FatherInterface中的静态变量"; void say(String say); } class SonInterFace implements FatherInterface{ static String str1 = "子类SonInterFace中的静态变量"; String str2 = "子类SonInterFace中的非静态变量"; static{ System.out.println("执行了子类SonInterFace中的静态代码块"); } { System.out.println("执行了子类SonInterFace中的非静态代码块"); } public SonInterFace(){ System.out.println("执行了子类SonInterFace中的构造器方法"); } @Override public void say(String say) { System.out.println(FatherInterface.str1+"--say:"+say); } } |
运行后答案将在下一篇文章中揭晓。
下一篇预告:
因为这是第一篇,所以只是大致讲解了下一个类怎么加载过程。在下一篇文章中,咱们来讲解在加载阶段使用到类加载器、父类委派机制等、类在什么时候会被初始化等?。欢迎继续学习。
相关推荐
Java源代码编译成字节码,这种中间语言可以在任何支持Java的平台上通过JVM(Java虚拟机)转换为本地机器码执行。由于JVM在各个平台上有所不同,因此Java程序无需修改就能在不同操作系统上运行。 3. Java语言的自动...
- **第1章:简介** - **1.1 环境搭建**:介绍如何安装JDK并配置环境变量。 - **1.2 开发工具**:推荐几种常用的IDE(如Eclipse、IntelliJ IDEA)及其基本操作。 - **1.3 实例演示**:通过示例代码展示如何编写...
Java程序的执行过程分为三个主要阶段:源代码->字节码->机器码。首先,Java源代码通过JDK中的javac编译器被编译成字节码(.class文件),然后通过Java虚拟机(JVM)在不同的平台上解释执行字节码,最终转化为特定...
- 方法名:采用驼峰命名法,第一个单词小写,后续单词首字母大写。 - 变量名:同方法名,采用驼峰命名法。 - 常量名:全部大写字母,单词间用下划线分隔。 #### 8. Java的源文件结构一般分那些部分? Java程序的...
理解这些基础知识是学习Java的第一步。 2. **面向对象编程**:Java是一种纯面向对象的语言,它支持类、对象、继承、封装和多态等概念。了解如何创建类、实例化对象,以及如何通过继承和多态来实现代码复用至关重要...
- 多个单词组成的变量名,除第一个单词外,其余单词首字母大写。 - **示例:** - 合法但不太建议的命名方式:`$abc`, `_ref`。 - 推荐的命名方式:`password`, `lastName`。 #### 第5课 Java的基本数据类型 ####...
#### 第一讲 Java语言概述 ##### 课前思考: 1. **新的编程语言是否需要借鉴以前的编程语言?** - 新的编程语言在设计时往往会借鉴已有语言的成功经验和失败教训。Java 作为一种相对较新的语言,吸取了 C 和 C++ ...
- 鼓励学生思考Java程序在不同操作系统上的运行情况。 #### 二、HelloWorld案例 这部分将通过编写第一个Java程序——“Hello World”来熟悉Java的基础语法和开发流程。该案例将覆盖以下知识点: - 如何编写并...
- **第一章 Java概述**:介绍Java语言的基本概念和特点。 - **第二章 数据表达式**:讲解数据类型、变量声明与使用等基础内容。 - **第三章 流程控制语句**:学习条件语句、循环语句等流程控制结构。 - **第四章 ...
- **JVM**(Java Virtual Machine):Java虚拟机,负责执行Java字节码,提供了一个运行Java程序的平台。 - **JRE**(Java Runtime Environment):Java运行时环境,包含了运行Java程序所需的所有组件,包括JVM和核心...
跨平台的原因:只要在需要运行 java 应用程序的操作系统上,先安装一个 Java 虚拟机(JVM Java Virtual Machine)即可。由 JVM 来负责 Java 程序在该系统中的运行。 3. 有符号数据的表示法: 原码、反码(原码取反...
Java的设计目标是实现“一次编写,到处运行”的理念,这意味着编写的Java程序可以在任何安装了Java虚拟机(JVM)的平台上运行而无需修改。 #### 2. Java环境搭建 - **JDK(Java Development Kit)**:Java开发工具...
java8流源码Java8InAction 该存储库包含 Java 8 实战:Lambdas、Streams 和函数式编程一书中示例和测验的所有源代码。 您可以在这里购买这本书:或在亚马逊上 所有示例的源代码都可以在目录中找到 第 1 章:Java 8:...
这本书旨在帮助读者理解Java编程的核心概念,通过丰富的示例和解释来培养"思考像一个Java程序员"的能力。书后的答案和代码是学习过程中极其宝贵的资源,它们可以帮助读者验证自己的理解,解决在阅读过程中遇到的疑惑...
java8流源码Java8InAction 该存储库包含 Java 8 实战:Lambdas、Streams 和函数式编程一书中示例和测验的所有源代码。 您可以在这里购买这本书:或在亚马逊上 所有示例的源代码都可以在目录中找到 第 1 章:Java 8:...
Java2简明教程源代码是学习Java编程语言的重要参考资料,尤其对于初学者而言,通过查看和分析源代码,可以深入理解各种Java概念和技术。在这个压缩包中,包含的"Java2电子教案"很可能是系列教程的实践部分,帮助读者...
《2018年黑马程序员全套教程java基础第1天笔记+讲义》是一份针对初学者精心编排的Java编程教程,旨在帮助新手快速掌握Java编程的基础知识。本教程结合了JDK9的新特性以及IntelliJ IDEA的实用技巧,为学习者提供了一...
在“学习Java的第二个项目,面试技巧和基础进阶知识”的资源中,我们可以找到一系列关于提升Java编程技能和准备面试的材料。这个压缩包可能包含了笔记、代码示例或者指导文档,帮助学习者巩固基础知识并掌握面试中的...
Java入门课件(第一讲JDK)是一套针对初学者设计的教学资料,旨在帮助学习者掌握Java编程的基础知识。在这一讲中,我们将主要探讨Java开发工具包(JDK)以及Java编程的基本原理。 首先,Java开发工具包(JDK)是学习...
《侯捷-Java编程思想》是一本深受Java开发者喜爱的经典著作,尽管是繁体版本,但其中也包含英文内容,方便不同语言背景的读者理解。这本书深入浅出地讲解了Java编程的核心概念和技术,旨在帮助读者掌握Java编程的...