本文来自:曹胜欢博客专栏。转载请注明出处:http://blog.csdn.net/csh624366188
在以前的博客里面,我们介绍了在java
领域中大部分的知识点,从最基础的
java
最基本语法到
SSH
框架。这里面应该包含了在
java
领域里面的大部分内容了吧。但是,那些知识点是让我们从一个应用的层面上了解了
java
,
java
程序真正底层的运行机制和一些底层虚拟机的工作我们还不了解,虽然这些内容在我们真正的开发中几乎用不到这些底层的东西,但对于我们对
java
的理解会有比较大的帮助。尤其也对以后
java
开发中的性能优化有很大帮助,可以使我们减少一些没必要的内存浪费等好处。所以,从今天开始,我将和大家一起来学习一下
java
虚拟机的内容。从底层开一下
java
的运行机制。
Java虚拟机
Java虚拟机
(JavaVirtualMachine)
简称
JVMJava
虚拟机是一个想象中的机器,在实际的计算机上
通过软件模拟来实现。Java
虚拟机有自己想象中的硬件,如处理器
、堆栈、寄存器等,还具有相应的指令系统。下面我们就来看一下这几部分比较重要的java虚拟机的结构
JVM寄存器
所有的CPU
均包含用于保存系统状态和处理器所需信息
的寄存器组。如果虚拟机定义义较多的寄存器,便可以从中得到更多的信息而不必对栈或内存进行访问,这有利于提高运行速度。然而,如果虚拟机中的寄存器比实际CPU
的寄存器多,在实现虚拟机时就会占用处理器大量的时间来用
常规存储器模拟寄存器,这反而会降低虚拟机的效率。针对这种情况,JVM
只设置了
4
个最为常用的寄存器。它们是:
p
c程序计数器,
optop操作数栈顶指针
,frame当前执行环境指针
,vars指向当前执行环境中
第一个局部变量的指针,所有寄存器均为32
位。
pc
用于记录程序的执行。
optop,frame
和
vars
用于记录指向
Java
栈区的指针。
JVM栈结构
作为基于栈结构的计算机,Java
栈是
JVM
存储信息的主要方法。当
JVM
得到一个
java字节码应用程序
后,便为该代码中一个类的每一个方法创建一个栈框架,以保存该方法的状态信息。每个栈框架包括以下三类信息:局部变量执行环境操作数栈局部变量用于存储一个类的方法中所用到的局部变量。vars
寄存器指向该变量表中的第一个局部变量。执行环境用于保存解释器对Java
字节码进行解释过程中所需的信息。它们是:上次调用的方法、局部变量
指针和操作数栈的栈顶和栈底指针。执行环境是一个执行一个方法的控制中心。例如:如果解释器要执行iadd
(整数加法),首先要从
frame
寄存器中找到当前执行环境,而后便从执行环境中找到操作数栈,从栈顶弹出两个整数进行加法运算,最后将结果压入栈顶。 操作数栈用于存储运算所需操作数及运算的结果。
JVM碎片回收堆
Java类的实例所需的
存储空间是在堆上分配的。解释器具体承担为类实例分配空间的工作。解释器在为一个实例分配完存储空间后,便开始记录对该实例所占用的内存区域的使用。一旦对象使用完毕,便将其回收到堆中。在Java
语言中,除了
new
语句外没有其他方法为一对象申请和释放内存。对内存进行释放和回收的工作是由
Java
运行系统承担的。这允许
Java
运行系统的设计者自己决定碎片回收的方法。在
SUN
公司开发的
Java
解释器和
HotJava
环境中,碎片回收用
后台线程的方式来执行。这不但为运行系统提供了良好的性能,而且使程序设计人员摆脱了自己控制内存使用的风险。
JVM存储区
JVM
有两类存储区:
常量缓冲池和方法区。常量缓冲池用于存储类名称、方法和字段名称以及串常量。方法区则用于存储Java
方法的
字节码
。对于这两种存储区域具体实现方式在JVM
规格中没有明确规定。这使得
Java
应用程序的存储布局必须在运行过程中确定,依赖于具体平台的实现方式。JVM
是为
Java
字节码定义的一种独立于具体平台的规格描述,是
Java平台
独立性的基础。目前的JVM
还存在一些限制和不足,有待于进一步的完善,但无论如何,
JVM
的思想是成功的。对比分析:如果把
Java
原程序想象成我们的
C++
原程序,
Java
原程序编译后生成的字节码就相当于
C++
原程序编译后的
80x86
的
机器码(二进制程序文件),JVM
虚拟机相当于80x86
计算机系统
,Java解释器相当于80x86CPU
。在
80x86CPU
上运行的是机器码,在
Java
解释器上运行的是
Java
字节码。
Java
解释器相当于运行
Java
字节码的
“
CPU
”
,但该
“
CPU
”
不是通过硬件实现的,而是用
软件实现的。Java
解释器实际上就是特定的平台下的一个应用程序。只要实现了特定平台下的解释器程序,
Java
字节码就能通过解释器程序在该平台下运行,这是
Java
跨平台的根本。当前,并不是在所有的平台下都有相应
Java
解释器程序,这也是
Java
并不能在所有的平台下都能运行的原因,它只能在已实现了
Java
解释器程序的平台下运行。
Java虚拟机的体系结构
图
Java虚拟机从启动到结束的生命周期,
当java虚拟机启动后,
在如下几种情况下,Java虚拟机将结束生命周期:
1.执行了System.exit()方法
2.程序正常执行结束
3.程序在执行过程中遇到了异常或错误而异常终止
4.由于操作系统出现错误而导致Java虚拟机进程终止
Java虚拟机的栈有三个区域
:
局部变量区、运行环境区、操作数区。
局部变量区
每个Java
方法使用一个固定大小的局部变量集。它们按照与
vars
寄存器的字偏移量来寻址。局部变量都是
32
位的。长整数和双精度浮点数占据了两个局部变量的空间
,
却按照第一个局部变量的索引来寻址。
(
例如
,
一个具有索引
n
的局部变量
,
如果是一个双精度浮点数
,
那么它实际占据了索引
n
和
n+1
所代表的存储空间
)
虚拟机规范并不要求在局部变量中的
64
位的值是
64
位对齐的。虚拟机提供了把局部变量中的值装载到操作数栈的指令
,
也提供了把操作数栈中的值写入局部变量的指令。
运行环境区
在运行环境中包含的信息用于动态链接,
正常的方法返回以及异常捕捉。
操作数栈区
机器指令只从操作数栈中取操作数,
对它们进行操作
,
并把结果返回到栈中。选择栈结构的原因是
:
在只有少量寄存器或非通用寄存器的机器
(
如
Intel486)
上
,
也能够高效地模拟虚拟机的行为。操作数栈是
32
位的。它用于给方法传递参数
,
并从方法接收结果
,
也用于支持操作的参数
,
并保存操作的结果。例如
,iadd
指令将两个整数相加。相加的两个整数应该是操作数栈顶的两个字。这两个字是由先前的指令压进堆栈的。这两个整数将从堆栈弹出、相加
,
并把结果压回到操作数栈中。
每个原始数据类型都有专门的指令对它们进行必须的操作。每个操作数在栈中需要一个存储位置,
除了
long
和
double
型
,
它们需要两个位置。操作数只能被适用于其类型的操作符所操作。例如
,
压入两个
int
类型的数
,
如果把它们当作是一个
long
类型的数则是非法的。在
Sun
的虚拟机实现中
,
这个限制由字节码验证器强制实行。但是
,
有少数操作
(
操作符
dupe
和
swap),
用于对运行时数据区进行操作时是不考虑类型的。
本地方法栈,当一个线程调用本地方法时,它就不再受到虚拟机关于结构和安全限制方面的约束,它既可以访问虚拟机的运行期数据区,也可以使用本地处理器以及任何类型的栈。例如,本地栈是一个C
语言的栈,那么当
C
程序调用
C
函数时,函数的参数以某种顺序被压入栈,结果则返回给调用函数。在实现
Java
虚拟机时,本地方法接口使用的是
C
语言的模型栈,那么它的本地方法栈的调度与使用则完全与
C
语言的栈相同。
下图可以表示出来java程序运行的一个全过程
3Java虚拟机的运行过程
上面对虚拟机的各个部分进行了比较详细的说明,下面通过一个具体的例子来分析它的运行过程。
虚拟机通过调用某个指定类的方法main
启动,传递给
main
一个字符串数组参数,使指定的类被装载,同时链接该类所使用的其它的类型,并且初始化它们。例如对于程序:
classHelloApp
{
publicstaticvoidmain(String[]args)
{
System.out.println("HelloWorld!");
for(inti=0;i<args.length;i++)
{
System.out.println(args[i]);
}
}
}
编译后在命令行模式下键入:javaHelloApprunvirtualmachine
将通过调用HelloApp
的方法
main
来启动
java
虚拟机,传递给
main
一个包含三个字符串
"run"
、
"virtual"
、
"machine"
的数组。现在我们略述虚拟机在执行
HelloApp
时可能采取的步骤。
开始试图执行类HelloApp
的
main
方法,发现该类并没有被装载,也就是说虚拟机当前不包含该类的二进制代表,于是虚拟机使用
ClassLoader
试图寻找这样的二进制代表。如果这个进程失败,则抛出一个异常。类被装载后同时在
main
方法被调用之前,必须对类
HelloApp
与其它类型进行链接然后初始化。链接包含三个阶段:检验,准备和解析。检验检查被装载的主类的符号和语义,准备则创建类或接口的静态域以及把这些域初始化为标准的默认值,解析负责检查主类对其它类或接口的符号引用,在这一步它是可选的。类的初始化是对类中声明的静态初始化函数和静态域的初始化构造方法的执行。一个类在初始化之前它的父类必须被初始化。整个过程如下:
推荐阅读(内含jvm内存区域说明):
参考资料:http://www.kuqin.com/java/20080525/8907.html
相关推荐
java程序员-从笨鸟到菜鸟.pdf
### Java程序员成长之路——从菜鸟到笨鸟 #### 一、引言 《Java程序员由菜鸟到笨鸟》是一本由曹胜欢编写的书籍,旨在帮助初学者掌握Java编程的基础知识,并逐步进阶至更高级的应用场景。本书不仅适合初学者作为...
曹胜欢在《JAVA程序员 从笨鸟到菜鸟.pdf》中分享了他个人学习Java的经历,从迷茫到逐渐成长的过程。他强调了自学的重要性,并鼓励初学者不要怕走弯路,同时希望自己的经验可以帮助到同样在学习Java的初学者。 2. ...
Java程序员的成长之路是一个充满挑战与探索的过程,从初学者到熟练掌握各项技能,需要系统性的学习和实践。"Java程序员由菜鸟到笨鸟学习文档"就是这样一个旨在帮助初入Java世界的学习者逐步进阶的资源。它覆盖了从...
资源名称:《Java程序员-从笨鸟到菜鸟》PDF 下载资源目录:作者简介:..........................................................................................................................................
《java程序员由菜鸟到笨鸟》 刚开始学习java时看过的一个学习资料。 其中包括开发环境搭建、面向对象、Javascript、设计模式、SSH、jquery、java虚拟机等内容。 设计内容较广,可以学习参考。
本书名为《Java程序员由菜鸟到笨鸟》,作者是曹胜欢。虽然无法直接查看具体内容,但基于标题、描述以及常见的Java学习路径,我们可以总结出该书可能涵盖的一些关键知识点。 ### Java基础知识 1. **Java概述**: -...
根据提供的文件信息,“Java程序员由菜鸟到笨鸟”是一本旨在帮助初学者成长为合格Java开发者的书籍。本书作者为曹胜欢,发布日期为2018年3月17日。以下是从该书的标题、描述以及部分可能包含的内容中提炼出的关键...
day03_List、Set、数据结构、Collections day04_Map,斗地主案例 day05_异常,线程 day06_线程、同步 day07_等待与唤醒案例、线程池、Lambda表达式 day08_File类、递归 day09_字节流、字符流 day10_缓冲流、转换流、...
《Java游戏:笨鸟》(也称为FlappyBird)是一款基于Java编程语言开发的简单游戏,旨在帮助初学者更好地理解和掌握Java编程基础。这款游戏的实现涉及了许多Java编程的关键概念和技术,下面将对这些知识点进行详细阐述...