Java虚拟机工作原理详解

原文地址:http://blog.csdn.net/bingduanlbd/article/details/8363734

一、类加载器

首先来看一下java程序的执行过程。

从这个框图很容易大体上了解java程序工作原理。首先,你写好java代码,保存到硬盘当中。然后你在命令行中输入

[java] view plaincopy

  1. javac YourClassName.java

此时,你的java代码就被编译成字节码(.class).如果你是在Eclipse
IDE或者其他开发工具中,你保存代码的时候,开发工具已经帮你完成了上述的编译工作,因此你可以在对应的目录下看到class文件。此时的class文
件依然是保存在硬盘中,因此,当你在命令行中运行

[java] view plaincopy

  1. java YourClassName

就完成了上面红色方框中的工作。JRE的来加载器从硬盘中读取class文件,载入到系统分配给JVM的内存区域--运行数据区(Runtime
Data Areas). 然后执行引擎解释或者编译类文件,转化成特定CPU的机器码,CPU执行机器码,至此完成整个过程。

接下来就重点研究一下类加载器究竟为何物?又是如何工作的?

首先看一下来加载器的一些特点,有点抽象,不过总有帮助的。

》》层级结构

类加载器被组织成一种层级结构关系,也就是父子关系。其中,Bootstrap是所有类加载器的父亲。如下图所示:

--Bootstrap class loader:

当运行java虚拟机时,这个类加载器被创建,它加载一些基本的java API,包括Object这个类。需要注意的是,这个类加载器不是用java语言写的,而是用C/C++写的。

--Extension class loader:

这个加载器加载出了基本API之外的一些拓展类,包括一些与安全性能相关的类。(目前了解得不是很深,只能笼统说,待日后再详细说明)

--System Class Loader:

它加载应用程序中的类,也就是在你的classpath中配置的类。

--User-Defined Class Loader:

这是开发人员通过拓展ClassLoader类定义的自定义加载器,加载程序员定义的一些类。

》》委派模式(Delegation Mode)

仔细看上面的层次结构,当JVM加载一个类的时候,下层的加载器会将将任务委托给上一层类加载器,上一层加载检查它的命名空间中是否已经加载这个
类,如果已经加载,直接使用这个类。如果没有加载,继续往上委托直到顶部。检查完了之后,按照相反的顺序进行加载,如果Bootstrap加载器找不到这
个类,则往下委托,直到找到类文件。对于某个特定的类加载器来说,一个Java类只能被载入一次,也就是说在Java虚拟机中,类的完整标识是
(classLoader,package,className)。一个雷可以被不同的类加载器加载。

举个具体的例子来说明,现在加入我有一个自己定义的类MyClass需要加载,如果不指定的话,一般交App(System)加载。接到任务
后,System检查自己的库里是否已经有这个类,发现没有之后委托给Extension,Extension进行同样的检查,发现还是没有继续往上委
托,最顶层的Boots发现自己库里也没有,于是根据它的路径(Java
核心类库,如java.lang)尝试去加载,没找到这个MaClass类,于是只好(人家看好你,交给你完成,你无能为力,只好交给别人啦)往下委托给
Extension,Extension到自己的路径(JAVA_HOME/jre/lib/ext)是找,还是没找到,继续往下,此时System加载
器到classpath路径寻找,找到了,于是加载到Java虚拟机。

现在假设我们将这个类放到JAVA_HOME/jre/lib/ext这个路径中去(相当于交给Extension加载器加载),按照同样的规则,
最后由Extension加载器加载MyClass类,看到了吧,统一各类被两次加载到JVM,但是每次都是由不同的ClassLoader完成。

》》可见性限制

下层的加载器能够看到上层加载器中的类,反之则不行,也就是是说委托只能从下到上。

》》不允许卸载类

类加载器可以加载一个类,但是它不能卸载一个类。但是类加载器可以被删除或者被创建。

当类加载完毕之后,JVM继续按照下图完成其他工作:

框图中各个步骤简单介绍如下:

Loading:文章前面介绍的类加载,将文件系统中的Class文件载入到JVM内存(运行数据区域)

Verifying:检查载入的类文件是否符合Java规范和虚拟机规范。

Preparing:为这个类分配所需要的内存,确定这个类的属性、方法等所需的数据结构。(Prepare a data structure
that assigns the memory required by classes and indicates the fields,
methods, and interfaces defined in the class.)

Resolving:将该类常量池中的符号引用都改变为直接引用。(不是很理解)

Initialing:初始化类的局部变量,为静态域赋值,同时执行静态初始化块。

那么,Class Loader在加载类的时候,究竟做了些什么工作呢?

要了解这其中的细节,必须得先详细介绍一下运行数据区域。

二、运行数据区域

Runtime Data
Areas:当运行一个JVM示例时,系统将分配给它一块内存区域(这块内存区域的大小可以设置的),这一内存区域由JVM自己来管理。从这一块内存中分
出一块用来存储一些运行数据,例如创建的对象,传递给方法的参数,局部变量,返回值等等。分出来的这一块就称为运行数据区域。运行数据区域可以划分为6大
块:Java栈、程序计数寄存器(PC寄存器)、本地方法栈(Native Method
Stack)、Java堆、方法区域、运行常量池(Runtime Constant
Pool)。运行常量池本应该属于方法区,但是由于其重要性,JVM规范将其独立出来说明。其中,前面3各区域(PC寄存器、Java栈、本地方法栈)是
每个线程独自拥有的,后三者则是整个JVM实例中的所有线程共有的。这六大块如下图所示:

》PC计数器:

每一个线程都拥有一个PC计数器,当线程启动(start)时,PC计数器被创建,这个计数器存放当前正在被执行的字节码指令(JVM指令)的地址。

》Java栈:

同样的,Java栈也是每个线程单独拥有,线程启动时创建。这个栈中存放着一系列的栈帧(Stack
Frame),JVM只能进行压入(Push)和弹出(Pop)栈帧这两种操作。每当调用一个方法时,JVM就往栈里压入一个栈帧,方法结束返回时弹出栈
帧。如果方法执行时出现异常,可以调用printStackTrace等方法来查看栈的情况。栈的示意图如下:

OK。现在我们再来详细看看每一个栈帧中都放着什么东西。从示意图很容易看出,每个栈帧包含三个部分:本地变量数组,操作数栈,方法所属类的常量池引用。

》局部(本地)变量数组:

局部(本地)变量数组中,从0开始按顺序存放方法所属对象的引用、传递给方法的参数、局部变量。举个例子:

[java] view plaincopy

  1. public void doSomething(int a, double b, Object o) {
  2. ...
  3. }

这个方法的栈帧中的局部变量存储的内容分别是:

[java] view plaincopy

  1. 0: this
  2. 1: a
  3. 2,3:b
  4. 4:0

看仔细了,其中double类型的b需要两个连续的索引。取值的时候,取出的是2这个索引中的值。如果是静态方法,则数组第0个不存放this引用,而是直接存储传递的参数。

》操作数栈:

操作数栈中存放方法执行时的一些中间变量,JVM在执行方法时压入或者弹出这些变量。其实,操作数栈是方法真正工作的地方,执行方法时,局部变量数组与操作数栈根据方法定义进行数据交换。例如,执行以下代码时,操作数栈的情况如下:

[java] view plaincopy

  1. int a = 90;
  2. int b = 10;
  3. int c = a + b;

注意在这个图中,操作数栈的地步是在上边,所以先压入的100位于上方。可以看出,操作数栈其实是一个数据临时存储区,存放一些中间变量,方法结束了,操作数栈也就没有啦。

》栈帧中数据引用:

除了局部变量数组和操作数栈之外,栈帧还需要一个常量池的引用。当JVM执行到需要常量池的数据时,就是通过这个引用来访问常量池的。栈帧中的数据
还要负责处理方法的返回和异常。如果通过return返回,则将该方法的栈帧从Java栈中弹出。如果方法有返回值,则将返回值压入到调用该方法的方法的
操作数栈中。另外,数据区中还保存中该方法可能的异常表的引用。下面的例子用来说明:

[java] view plaincopy

  1. class Example3C{
  2. public static void addAndPrint(){
  3. double result = addTwoTypes(1,88.88);
  4. System.out.println(result);
  5. }
  6. public static double addTwoTypes(int i, double d){
  7. return i+d;
  8. }
  9. }

执行上述代码时,Java栈如下图所示:

花些时间好好研究上图。一样需要注意的是,栈的底部在上方,先押人员addAndPrint方法的栈帧,再压入addTwoTypes方法的栈帧。上图最右边的文字说明有错误,应该是addTwoTypes的执行结果存放在addAndPrint的操作数栈中。

》》本地方法栈

当程序通过JNI(Java Native Interface)调用本地方法(如C或者C++代码)时,就根据本地方法的语言类型建立相应的栈。

》》方法区域

方法区域是一个JVM实例中的所有线程共享的,当启动一个JVM实例时,方法区域被创建。它用于存运行放常量池、有关域和方法的信息、静态变量、类
和方法的字节码。不同的JVM实现方式在实现方法区域的时候会有所区别。Oracle的HotSpot称之为永久区域(Permanent
Area)或者永久代(Permanent Generation)。

》》运行常量池

这个区域存放类和接口的常量,除此之外,它还存放方法和域的所有引用。当一个方法或者域被引用的时候,JVM就通过运行常量池中的这些引用来查找方法和域在内存中的的实际地址。

》》堆(Heap)

堆中存放的是程序创建的对象或者实例。这个区域对JVM的性能影响很大。垃圾回收机制处理的正是这一块内存区域。

所以,类加载器加载其实就是根据编译后的Class文件,将java字节码载入JVM内存,并完成对运行数据处于的初始化工作,供执行引擎执行。

三、 执行引擎(Execution  Engine)

类加载器将字节码载入内存之后,执行引擎以Java
字节码指令为但愿,读取Java字节码。问题是,现在的java字节码机器是读不懂的,因此还必须想办法将字节码转化成平台相关的机器码。这个过程可以由
解释器来执行,也可以有即时编译器(JIT Compiler)来完成。

Java虚拟机工作原理详解,布布扣,bubuko.com

时间: 2024-09-30 21:09:49

Java虚拟机工作原理详解的相关文章

Java虚拟机工作原理详解 (一)

一.类加载器 首先来看一下java程序的执行过程. 从这个框图很容易大体上了解java程序工作原理.首先,你写好java代码,保存到硬盘当中.然后你在命令行中输入 [java] view plain copy javac YourClassName.java 此时,你的java代码就被编译成字节码(.class).如果你是在Eclipse IDE或者其他开发工具中,你保存代码的时候,开发工具已经帮你完成了上述的编译工作,因此你可以在对应的目录下看到class文件.此时的class文 件依然是保存

Java虚拟机工作原理详解 ( 二 )

首先这里澄清两个概念:JVM实例和JVM执行引擎实例,JVM实例对应了一个独立运行的Java程序,而JVM执行引擎实例则对应了属于用户运行程序的线程:也就是JVM实例是进程级别,而执行引擎是线程级别的. JVM是什么?—JVM的生命周期 JVM实例的诞生:当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有 publicstaticvoidmain(String[]args)函数的class都可以作为JVM实例运行的起点,既然如此,那么JVM如何知道 是运行classA的main而不是

Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解

概述 类android.graphics.PorterDuffXfermode继承自android.graphics.Xfermode.在用Android中的Canvas进行绘图时,可以通过使用PorterDuffXfermode将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新Canvas中最终的像素颜色值,这样会创建很多有趣的效果.当使用PorterDuffXfermode时,需要将将其作为参数传给Paint.setXfermode(Xfermo

块级格式化上下文(block formatting context)、浮动和绝对定位的工作原理详解

CSS的可视化格式模型中具有一个非常重要地位的概念——定位方案.定位方案用以控制元素的布局,在CSS2.1中,有三种定位方案——普通流.浮动和绝对定位: 普通流:元素按照先后位置自上而下布局,inline元素水平排列,直到行被占满后换行,block元素则被渲染为完整的一行,除非指定,所有元素默认为普通流定位. 浮动:浮动布局中,元素首先按照普通流位置出现,然后根据浮动方向尽可能向左或右偏移,效果与文本环绕相似. 绝对定位:元素会脱离普通流,因此绝对定位元素不会对其兄弟元素产生影响(与float不

硬盘内部硬件结构和工作原理详解[zz]

一般硬盘正面贴有产品标签,主要包括厂家信息和产品信息,如商标.型号.序列号.生产日期.容量.参数和主从设置方法等.这些信息是正确使用硬盘的基本依据,下面将逐步介绍它们的含义. 硬盘主要由盘体.控制电路板和接口部件等组成,如图1-1所示.盘体是一个密封的腔体.硬盘的内部结构通常是指盘体的内部结构:控制电路板上主要有硬盘BIOS.硬盘缓存(即CACHE)和主控制芯片等单元,如图1-2所示:硬盘接口包括电源插座.数据接口和主.从跳线,如图1-3所示. 图1-1 硬盘的外观 图1-2 控制电路板 图1-

Java虚拟机工作原理具体解释

一.类载入器 首先来看一下java程序的运行过程. 从这个框图非常easy大体上了解java程序工作原理.首先,你写好java代码,保存到硬盘其中.然后你在命令行中输入 javac YourClassName.java 此时,你的java代码就被编译成字节码(.class).假设你是在Eclipse IDE或者其它开发工具中,你保存代码的时候,开发工具已经帮你完毕了上述的编译工作,因此你能够在相应的文件夹下看到class文件.此时的class文件依旧是保存在硬盘中,因此,当你在命令行中执行 ja

Java轻量级锁原理详解(Lightweight Locking)

大家知道,Java的多线程安全是基于Lock机制实现的,而Lock的性能往往不如人意. 原因是,monitorenter与monitorexit这两个控制多线程同步的bytecode原语,是JVM依赖操作系统互斥(mutex)来实现的. 互斥是一种会导致线程挂起,并在较短的时间内又需要重新调度回原线程的,较为消耗资源的操作. 为了优化Java的Lock机制,从Java6开始引入了轻量级锁的概念. 轻量级锁(Lightweight Locking)本意是为了减少多线程进入互斥的几率,并不是要替代互

lvs和keeplived的工作原理详解

一.lvs的工作原理 使用集群的技术和liunx的操作系统实现一个高性能.高可用的服务器.可伸缩性.可靠性.很好的管理性. 特点:可伸缩网络服务的几种结构,它们都需要一个前端的负载调度器(或者多个进行主从备份).我们先分析实现虚拟网络服务的主要技术,指出IP负载均衡技术是在负载调度器的实现技术中效率最高的.在已有的IP负载均衡技术中,主要有通过网络地址转换(Network Address Translation)将一组服务器构成一个高性能的.高可用的虚拟服务器,我们称之为VS/NAT技术(Vir

走进JavaWeb技术世界4:Servlet 工作原理详解

本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下Star哈 文章首发于我的个人博客: www.how2playlife.com 本文是微信公众号[Java技术江湖]的<走进JavaWeb技术世界>其中一篇,本文部分内容来源于网络,为了把本文主题讲得清晰透彻,也整合了很多我认为不错的技术博客内容,引用其中了一些比较好的博客文章,如有侵权,请联系作