计算机高级编程语言分为两种:
编译型:针对特定的平台,将高级语言源代码一次性的编译成可被该平台硬件执行的机器码,并包装成该平台所能识别的可执行性程序的格式。
一次性的编译成平台相关的机器语言文件,运行时脱离开发环境,运行效率高;与特定平台相关,一般无法移植到其他平台;现有的C、C++、Objective等都属于编译型语言。
解释型:对于解释型语言,每次执行程序都需要一边转换一边执行。
解释型语言每次运行都需要将源代码解释称机器码并执行,效率较低;只要平台提供相应的解释器,就可以运行源代码,所以可以方便源程序移植;Python等属于解释型语言。
Java既有编译型的特点又有解释型的特点
源文件(.java源代码)通过编译器编译成字节码文件class。通过JVM中的解释器将字节码文件生成对应的可执行文件,运行。将编译后的程序加载到方法区,存储类信息。运行时,JVM创建线程来执行代码,在虚拟机栈和程序计数器分配独占的空间。根据方法区里的指令码,在虚拟机栈对线程进行操作,程序计数器保存线程代码执行到哪个位置。“一处编写,处处运行”:编译后,不依赖于平台环境,在各种操作系统均可运行。
Class字节码文件:是一个二进制文件,包含了JAVA程序执行的字节码,包含的信息有版本、访问标志、常量池、当前类、超级类、接口、字段、方法、属性等,中间没有任何分隔符,文件开头有一个特殊标志,用16进制表示为0xcafebabe。
这里对上图中的各个部分的解释:
JVM运行时数据区:包含线程共享部分和线程独占部分。
**线程共享:**线程共同访问的内存数据空间,随着JVM(虚拟机)或者GC(垃圾回收)而创建和销毁。包含方法区和堆内存。
1、方法区:JVM用来存储加载的类信息、常量、静态变量、编译后的代码等数据。在JVM规范中,这是一个逻辑区,根据不同的虚拟机有不同的具体实现,如oracle的HotSpot的方法区,在java7中放在永久代,java8中放在元数据空间,并通过GC机制对这个区域进行管理。目前有三大Java虚拟机:HotSpot,oracle JRockit,IBM J9。
2、堆内存:存放对象的实例,在JVM启动时创建。可细分为老年代、新生代,垃圾回收器主要就是管理堆内存,如果满了就会出现OOM(OutOfMemoryError)。
**线程独占:**每个线程都会有自己独立的空间,随着线程的生命周期而创建和销毁。包含虚拟机栈、本地方法栈、程序计数器。
1、虚拟机栈:即虚拟机执行JAVA代码的栈,每个线程都会在这有一个私有空间。线程栈由多个栈帧(Stack Frame)组成,一个线程会执行一个或多个方法,一个方法对应一个栈帧。栈帧内容包括:局部变量表、操作数栈、动态链接、方法返回地址、附加信息等。栈内存默认最大是1M,超出则抛StackOverflowError。
2、本地方法栈:即虚拟机执行Native本地方法的栈,和虚拟机栈的主要区别是执行的方法不同。在虚拟机规范没有规定具体的实现,由不同的虚拟机厂商去实现。HotSpot虚拟机中虚拟机栈和本地方法栈的实现方式是一样的,超出大小后也会抛出StackOverflowError。
3、程序计数器:记录当前线程执行字节码的位置,存储的是字节码指定地址,如果执行Native方法,则计数器值为空。每个线程在这都有一个私有空间,占用很少的内存空间。
CPU同一时间只会执行一条线程中的指令,JVM多线程会轮流切换并分配CPU执行时间,在线程切换后,需要通过程序计数器来回复正确的执行位置。
类加载的详解:
说到这里那么什么是类的加载呢?
类的加载,指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
加载类的方式有以下几种:
1)从本地系统直接加载
2)通过网络下载.class文件
3)从zip,jar等归档文件中加载.class文件
4)从专有数据库中提取.class文件
5)将Java源文件动态编译为.class文件(服务器)
6)命令行启动应用时候由JVM初始化加载
7)通过Class.forName()方法动态加载
8)通过ClassLoader.loadClass()方法动态加载
类的加载过程分为三个步骤:1、装载(Load) 2、链接(Link) 3、初始化(Initialize)。这其中链接又分为三个步骤,验证、准备、解析。
装载:查找并加载类的二进制数据
1、通过一个类的全限定名来获取其定义的二进制字节流。
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
链接(分为三个步骤):
验证:确保被加载的类的正确性
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:
文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
符号引用验证:确保解析动作能正确执行。
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备:为类的静态变量分配内存,并将其初始化为默认值
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
假设一个类变量的定义为:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。
解析:把类中的符号引用转换为直接引用
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
初始化:对类的静态变量,静态代码块执行初始化操作初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
1、声明类变量是指定初始值。
2、使用静态代码块为类变量指定初始值。
到这里类的加载基本完成。
最后,阳光就在天上,祝大家前程似锦!
如果觉得《java程序运行机制》对你有帮助,请点赞、收藏,并留下你的观点哦!