一、JAVA语言的特点
在进入正题之前,先问一个陈词滥调的问题,相较于C,JAVA语言的劣势是什么?置信学过JAVA的人都晓得,无论是大学时的第一堂课还是JAVA相干书籍的第一章也都会讲到:一次编写、到处运行;真正意义上的实现了跨平台。那再问一个问题,为什么Java能够跨平台?大多数人都晓得Java能够跨平台得益于 JVM(java虚拟机)。在这之前,我理解到的java跨平台得益于不同版本的JVM,那么它的底层原理是什么呢?“一次编译,到处运行” 是Java的跨平台个性。像 C 、C++ 这样的编程语言没有它。通过上面的介绍,置信你会有一个近一步的理解。Java是一种能够跨平台的编程语言。首先,咱们须要晓得什么是平台。咱们把CPU处理器与操作系统的整体叫平台。CPU相当于计算机的大脑,指令集是CPU中用来计算和管制计算机系统的一套指令的汇合。指令集分为精简指令集(RISC)和简单指令集(CISC)。每个CPU都有本人的特定指令集。要开发一个程序,咱们必须首先晓得程序运行在什么CPU上,也就是说,咱们必须晓得CPU应用的指令集。操作系统是用户与计算机之间的接口软件。不同的操作系统反对不同的CPU。严格来说,不同的操作系统反对不同的CPU指令集。但问题是,原来的Mac操作系统只反对PowerPC,不能装置在英特尔上。咱们该怎么办?因而,苹果必须重写其Mac操作系统来反对这一变动。最初,咱们应该晓得不同的操作系统反对不同的CPU指令集。当初windows、Linux、MAC和Solaris都反对Intel和AMD CPU指令集。如果你想开发一个程序,首先应该确定:
- CPU类型,即指令集类型;
- 操作系统;咱们称之为软硬件平台的联合。也能够说“平台=CPU+OS”。而且因为支流操作系统反对支流CPU,有时操作系统也被称为平台。
二、如何实现跨平台
通常,咱们编写的Java源代码在编译后会生成一个Class文件,称为字节码文件。Java虚拟机负责将字节码文件翻译成特定平台下的机器代码,而后运行。简言之,java的跨平台就是因为不同版本的 JVM。换句话说,只有在不同的平台上装置相应的JVM,就能够运行字节码文件(.class)并运行咱们编写的Java程序。在这个过程中,咱们编写的Java程序没有做任何改变,只是通过JVM的“中间层”,就能够在不同的平台上运行,真正实现了“一次编译,到处运行”的目标。JVM是跨平台的桥梁和中间件,是实现跨平台的要害。首先将Java代码编译成字节码文件,而后通过JVM将其翻译成机器语言,从而达到运行Java程序的目标。因而,运行Java程序必须有JVM的反对,因为编译的后果不是机器代码,必须在执行前由JVM再次翻译。即便您将Java程序打包成可执行文件(例如。Exe),依然须要JVM的反对。 留神:编译的后果不是生成机器代码,而是生成字节码。字节码不能间接运行,必须由JVM转换成机器码。编译生成的字节码在不同的平台上是雷同的,然而JVM翻译的机器码是不同的。
三、JVM简介
JVM------Java Virtual Machine.JVM是Java平台的根底,与理论机器一样,他有本人的指令集(相似CPU通过指令操作程序运行),并在运行时操作不同的内存区域(JVM内存体系)。Java虚拟机位于操作系统之上(如下图所示),将通过JAVAC命令编译后的字节码加载到其内存区域,通过解释器将字节码翻译成CPU能辨认的机器码行。每一条Java指令,Java虚拟机标准中都有具体定义,如怎么取操作数,怎么解决操作数,处理结果放在哪里。
JVM是运行在操作系统之上的,它与硬件没有间接交互。
四、JVM的内存构造
JAVA源代码文件通过编译后变成虚拟机能够辨认的字节码,JAVA程序在执行时,会通过类加载器把字节码加载到虚拟机的内存中(虚拟机的内存是一个逻辑概念,相当于是对主内存的一个形象,实际上实在的数据还是寄存在主存中),详见下图。
Java 虚拟机在执行 Java 程序的过程中会把它治理的内存划分为若干个不同的数据区域。每个区域都有各自的作用。剖析 JVM 内存构造,次要就是剖析JVM 运行时数据存储区域。JVM 的运行时数据区次要包含:堆、栈、办法区、程序计数器等。而 JVM 的优化问题次要在线程共享的数据区中:堆、办法区。
4.1 办法区
又称非堆(non-heap),办法区用于存储已被虚拟机加载的类信息,常量、动态变量,即时编译后的代码等数据。办法区中最驰名的就是CLASS对象,CLASS对象中寄存了类的元数据信息,包含:类的名称、类的加载器、类的办法、类的注解等。当咱们new一个新对象或者援用动态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,而后JVM再依据这个类型信息相干的Class对象创立咱们须要实例对象或者提供动态变量的援用值。留神,咱们定义的一个类,无论创立多少个实例对象,在JVM中都只有一个Class对象与其对应,即:在内存中每个类有且只有一个绝对应的Class对象,如图:
实际上所有的类都是在对其第一次应用时动静加载到JVM中的,当程序创立第一个对类的动态成员援用时,就会加载这个被应用的类(实际上加载的就是这个类的字节码文件)。注:应用new创立类的新实例对象也会被当作对类的动态成员的援用(构造函数也是类的静态方法)由此看来Java程序在它们开始运行之前并非被齐全加载到内存的,其各个局部是按需加载,所以在应用该类时,类加载器首先会查看这个类的Class对象是否已被加载(类的实例对象创立时根据Class对象中类型信息实现的),如果还没有加载,默认的类加载器就会先依据类名查找.class文件(编译后Class对象被保留在同名的.class文件中),在这个类的字节码文件被加载时,它们必须承受相干验证,以确保其没有被毁坏并且不蕴含不良Java代码(这是java的平安机制检测),齐全没有问题后就会被动静加载到内存中,此时相当于Class对象也就被载入内存了(毕竟.class字节码文件保留的就是Class对象),同时也就能够依据这个类的Class对象来创立这个类的所有实例对象。
4.2 堆
所有创立进去的实例对象还有数组都是寄存在堆内存中,堆是Java虚拟机所治理的内存中最大的一块存储区域,堆内存被所有线程共享。垃圾收集器就是依据GC算法,收集堆上对象所占用的内存空间,堆上又分为了新生代和老年代,针对不同的分代又会有对象的垃圾回收器和相应的回收算法(GC章节中会具体介绍)。
4.3 栈
JVM 中的栈包含 Java 虚拟机栈和本地办法栈,两者的区别就是,Java 虚拟机栈为 JVM 执行 Java 办法服务,本地办法栈则为 JVM 应用到的 Native 办法服务。两者作用是极其类似的,本文次要介绍 Java 虚拟机栈,以下简称栈。栈属于线程公有的数据区域,与线程同时创立,总数与线程关联,代表Java办法执行的内存模型。每个办法执行时都会创立一个栈帧来存储办法的的局部变量表、操作数栈、动静链接办法、办法返回值、返回地址等信息。每个办法从调用值完结就对于一个栈桢在虚拟机栈中的入栈和出栈过程,栈帧中的局部变量表能够寄存根本类型,也能够寄存指向对象的援用,当在某个办法中new Object()时,会在以后办法栈帧中的局部变量表寄存一个指向堆内存实例对象的援用,详见下图。
4.4 程序计数器
是一块较小的内存空间,用来存储虚拟机下一条执行的字节码指令地址,和CPU中的程序计数器是一样的概念。
五 、JAVA程序在JVM内是如何执行的
上文已介绍了JVM的内存构造,接下来再看一下这个程序在JVM外部是怎么运行的:
1.JAVA程序的执行过程简略来说包含:
2.JAVA源代码编译成字节码;
3.字节码校验并把JAVA程序通过类加载器加载到JVM内存中;
4.在加载到内存后针对每个类创立Class对象并放到办法区;
5.字节码指令和数据初始化到内存中;
6.找到main办法,并创立栈帧;
7.初始化程序计数器外部的值为main办法的内存地址;
8.程序计数器一直递增,逐条执行JAVA字节码指令,把指令执行过程的数据寄存到操作数栈中(入栈),执行实现后从操作数栈取出后放到局部变量表中,遇到创建对象,则在堆内存中调配一段间断的空间存储对象,栈内存中的局部变量表寄存指向堆内存的援用;遇到办法调用则再创立一个栈帧,压到以后栈帧的下面。 上面以一段理论的代码举例,来看一下,程序在JVM外部的执行过程。
咱们先通过JAVAP命令,展现上述代码对应的字节码,下图是JVM把类加载到内存当前在办法区的常量池中初始化好的Class对象和各种办法援用,这外面须要重点关注一下后面的#1,#2,#5这些符号,这些数字保留的是和Class对象以及办法的援用关系,前面的字节码中会用到。
随后执行引擎中的解释器会率先启动,对ClassFile字节码采纳逐行解释的形式加载机器码,并配合运行时数据区的程序计数器与操作数栈来反对。下图是main办法的字节码指令,咱们联合JVM内存状况对代码做逐个剖析。
上图中stack=3,local=2:stack=3代表栈的深度为3,local=2代表局部变量表中的变量数量。
程序样例执行详解
下图是main办法中的字节码执行到detail.Sum办法前的JVM内存构造。
具体执行流程如下: 首先会在JAVA栈中压入main办法的栈帧,而后程序计数器中的值更新成字节码new所在的内存地址,样例中为了不便起见就间接以0示意,程序计数器逐条解析字节码,其中new(new前面的#5中有讲到,对应的是JvmDetailClass的Class对象),dup,invokespecial三个字节码指令别离代表创建对象、赋值援用、调用构造方法,astore_1代表是把操作数(援用)放入操作数栈,aload_1代表是把操作数(援用)出栈,并放到局部变量表中。Iconst_3,iconst_5别离代表把操作数3,5入栈放到操作数栈中。接下来咱们再看一下办法调用时JVM的内存构造是怎么做的,下面的代码中波及到2块代码调用,一个是detail.Sum,一个是detail.getSum,这里咱们detail.getSum是个带有返回值的办法,比拟典型,咱们间接以detail.getSum的调用为样例,看一下JVM外部是怎么执行的。当解释器执行到办法调用时,会批改程序计数器中的值为调用的办法外部第一行指令,同时在栈中压入getSum办法的栈帧,压入栈帧后会在局部变量表中初始化一个以后办法所属的对象的援用this,如果调用办法波及到传参的状况下,则会在局部变量表中存入传递的参数。当getSum办法执行实现后,会做两步动作:程序计数器又会批改为main办法调用getSum处的下一行指令的地址。办法返回值写入main办法栈帧中的操作数栈中。
作者|伍玉莹(姬无)
点击立刻收费试用云产品 开启云上实际之旅!
原文链接
本文为阿里云原创内容,未经容许不得转载。