java虚拟机jvm

来源:互联网 发布:c语言进制转换函数 编辑:程序博客网 时间:2024/05/22 12:18

文章主要来源:http://www.cnblogs.com/java-my-life/archive/2012/08/01/2615221.html

JVM提供了一个相对安全的内存管理和访问机制,避免了绝大部分的内存泄漏和指针越界问题。

JDK:java程序设计语言、java虚拟机、java API类库。
JRE包括JVM和JAVA核心类库和支持文件。与JDK相比,它不包含开发工具——编译器、调试器和其它工具。

java语言编译程序只需生成在java虚拟机上运行的目标代码(字节码),java虚拟机在执行字节码时,将字节码解释成具体平台上的机器指令执行。


 java程序的运行必须经过编写、编译、运行三个步骤。

1、编写是指在Java开发环境中进行程序代码的输入,最终形成后缀名为.java的源文件。
2、编译是指使用Java编译器对源文件进行错误排查的过程,编译后将生成后缀名为.class的字节码文件,这不像C语言那样最终生成可执行文件。

3、运行是指使用Java解释器将字节码文件翻译成机器代码,执行并显示结果。


字节码文件是一种和任何具体机器环境及操作系统环境无关的中间代码,它是一种二进制文件,是Java源文件由Java编译器编译后生成的目标代码文件。 必须由专用的Java解释器来解释执行,因此Java是一种在编译基础上进行解释运行的语言。


Java解释器负责将字节码文件翻译成具体硬件环境和操作系统平台下的机器代码,以便执行。因此Java程序不能直接运行在现有的操作系统平台上,它必须运行在被称为Java虚拟机的软件平台之上。Java解释器就是Java虚拟机的一部分。

 

java虚拟机运行时数据区:方法区、堆、虚拟机栈、本地方法栈、程序计数器。


所有线程共享的运行时数据区:方法区和堆。当每一个新线程被创建时,它都将得到它自己的PC寄存器(程序计数器)以及一个Java栈,如果线程正在执行的是一个Java方法(非本地方法),那么PC寄存器的值将总是指向下一条将被执行的指令,而它的Java栈则总是存储该线程中Java方法调用的状态——包括它的局部变量,被调用时传进来的参数、返回值,以及运算的中间结果等等。而本地方法调用的状态,则是以某种依赖于具体实现的方法存储在本地方法栈中,也可能是在寄存器或者其他某些与特定实现相关的内存区中。


 类装载器子系统
类装载器子系统除了要定位和导入二进制class文件外,还必须负责验证被导入类的正确性,为类变量分配并初始化内存,以及帮助解析符号引用。这些动作必须严格按以下顺序进行:
  (1)装载——查找并装载类的二进制数据。
  (2)链接——验证、准备、以及解析(可选)。
    ● 验证  确保被导入类型的正确性。
    ● 准备  为类的静态变量分配内存,并将其初始化为默认值。
    ● 解析  把类型中的符号引用转换为直接引用。(符号引用是一个字符串,给出了被引用的内容的名字并且可能会包含一些其他关于这个被引用项的信息——这些信息必须足以唯一的识别一个类、字段、方法。对于类的符号引用必须给出类的全名;对于类的字段,必须给出类名、字段名以及字段描述符;对于类的方法的引用必须给出类名、方法名以及方法的描述符。直接引用指向方法区的本地指针)

(3)初始化——把类变量初始化为正确初始值。


类初始化的时机:

1、创建类的实例,new一个对象;

2、调用类或接口的静态变量,或者对该静态变量赋值;

3、调用类的静态方法;

4、反射;

5、初始化一个类的子类(会首先初始化子类的父类);

6、JVM启动时标明的启动类,即文件名和类名相同的那个类。

 

类的初始化步骤:

1、如果这个类还没有被加载和链接,那就先进行加载和链接;

2、假如这个类存在直接父类,并且这个类还没有被初始化(注意,在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口);

3、假如类中存在初始化语句(比如static变量和static块),那就依次执行这些初始化语句。


类的加载是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的java.lang.Class对象,用来封装类在方法区类的对象。类的加载的最终产品是位于堆区中的Class对象。

Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。



方法区

在java虚拟机中,被装载类型的信息存储在方法区。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件——1个线性二进制数据流,然后它传输到虚拟机中,紧接着虚拟机提取其中的类型信息,并将这些信息存储到方法区。该类型中的类(静态)变量同样也是存储在方法区中。


对于每个装载的类型,虚拟机都会在方法区中存储以下类型信息:
  ● 这个类型的全限定名
  ● 这个类型的直接超类的全限定名(除非这个类型是java.lang.Object,它没有超类)
  ● 这个类型是类类型还是接口类型
  ● 这个类型的访问修饰符(public、abstract或final的某个子集)

  ● 任何直接超接口的全限定名的有序列表


除了上面列出的基本类型信息外,虚拟机还得为每个被装载的类型存储以下信息:
  ● 该类型的常量池
  ● 字段信息
  ● 方法信息
  ● 除了常量以外的所有类(静态)变量
  ● 一个到类ClassLoader的引用

  ● 一个到Class类的引用


常量池:

虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用常量的一个有序集合,包括直接常量和对其他类型、字段和方法的符号引用。池中的数据项就像数组一样是通过索引访问的。因为常量池存储了相应类型所用到的所有类型、字段和方法的符号引用,所以它在Java程序的动态连接中起着核心的作用。

常量池理解与总结:http://www.jianshu.com/p/c7f47de2ee80#


java是一种动态连接的语言,常量池的作用非常重要。常量池存放量大类常量:字面量和符号引用。字面量相当于java语言层面常量的概念,如文本字符串,声明为final的常量值等;符号引用:编译时,如果发现对其他类方法的调用或者对其他类字段的引用的话,记录进Class文件中的,只能是一个文本形式的符号引用,在连接过程中,虚拟机根据这个文本信息去查找对应的方法或字段。


字段类型:
对于类型中声明的每一个字段。方法区中必须保存下面的信息。除此之外,这些字段在类或者接口中的声明顺序也必须保存。
  ○ 字段名
  ○ 字段的类型

○ 字段的修饰符(public、private、protected、static、final、volatile、transient的某个子集)


方法信息:
对于类型中声明的每一个方法,方法区中必须保存下面的信息。和字段一样,这些方法在类或者接口中的声明顺序也必须保存。
  ○ 方法名
  ○ 方法的返回类型(或void)
  ○ 方法参数的数量和类型(按声明顺序)
  ○ 方法的修饰符(public、private、protected、static、final、synchronized、native、abstract的某个子集)
  除了上面清单中列出的条目之外,如果某个方法不是抽象的和本地的,它还必须保存下列信息:
  ○ 方法的字节码(bytecodes)
  ○ 操作数栈和该方法的栈帧中的局部变量区的大小

  ○ 异常表


类(静态)变量:
它们总是作为类型信息的一部分而存储在方法区。除了在类中声明的编译时常量外,虚拟机在使用某个类之前,必须在方法区中为这些类变量分配空间。

编译时常量(就是那些用final声明以及用编译时已知的值初始化的类变量)则和一般的类变量处理方式不同,每个使用编译时常量的类型都会复制它的所有常量到自己的常量池中,或嵌入到它的字节码流中。作为常量池或字节码流的一部分,编译时常量保存在方法区中——就和一般的类变量一样。但是当一般的类变量作为声明它们的类型的一部分数据面保存的时候,编译时常量作为使用它们的类型的一部分而保存。


指向ClassLoader类的引用:

每个类型被加载的时候,需要类加载器。所以虚拟机必须在类型信息中存储对该加载器的引用。虚拟机在动态连接期间使用这个信息。正确地执行动态连接以及维护多个命名空间。


指向Class类的引用:

  对于每一个被装载的类型(不管是类还是接口),虚拟机都会相应地为它创建一个java.lang.Class类的实例,而且虚拟机还必须以某种方式把这个实例和存储在方法区中的类型数据关联起来。


运行时常量池
运行时常量池:方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,存放编译期生成的各种字面量和符号引用。     java虚拟机对Class文件每一部分(包括常量池)的格式都有严格规定,但对于运行时常量池,java虚拟机规范没有做任何细节的要求。具有动态性,运行期间可能将新的常量放入池中。


每个java程序独占一个java虚拟机实例。每个java程序都有自己的堆空间——它们不会彼此干扰。但一个程序中的所有线程都共享这个堆。
数组的内部表示:所有具有相同维度和类型的数组都是同一个类的实例,而不管数组的长度(多维数组每一维的长度)是多少。数组类的名称由两部分组成:每一维用一个方括号“[”表示,用字符或字符串表示元素类型。
 
程序计数器
对于一个运行中的Java程序而言,其中的每一个线程都有它自己的PC(程序计数器)寄存器,它是在该线程启动时创建的,PC寄存器的大小是一个字长,因此它既能够持有一个本地指针,也能够持有一个returnAddress。当线程执行某个Java方法时,PC寄存器的内容总是下一条将被执行指令的“地址”,这里的“地址”可以是一个本地指针,也可以是在方法字节码中相对于该方法起始指令的偏移量。如果该线程正在执行一个本地方法,那么此时PC寄存器的值是“undefined”。
 


 栈

每当启动一个新线程时,Java虚拟机都会为它分配一个Java栈。Java栈以帧为单位保存线程的运行状态。虚拟机只会直接对Java栈执行两种操作:以帧为单位的压栈和出栈。
在线程执行一个方法时,它会跟踪当前类和当前常量池。此外,当虚拟机遇到栈内操作指令时,它对当前帧内数据执行操作。
每当线程调用一个java方法时,虚拟机在该线程的java栈中压入一个新帧,该帧成为当前帧。在执行这个方法时,使用这个帧来存储参数、局部变量、中间运算结果等数据。
java方法以return返回(正常返回)或者通过抛出异常而异常终止。
 


 本地方法栈

  

当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。本地方法可以通过本地方法接口来访问虚拟机的运行时数据区,但不止如此,它还可以做任何它想做的事情。


当它调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。

java虚拟机的主要任务是装载class文件并且执行其中的字节码。类加载器从程序和API(只有程序执行时需要的那些类才会被加载)中加载class文件;字节码由执行引擎来执行。


java方法(由java语言写的,编译成字节码,存储在Class文件中)和本地方法(由其他语言如C,C++,编译语言编写,编译成和处理器相关的机器代码,本地方法时平台相关的),本地方法保存在动态连接库中。本地方法是联系java程序和底层主机操作系统的连接方法,通过本地方法,java程序可以直接访问底层操作系统的资源。


java API类库是用java写的,但它有很多功能是直接调用操作系统的API来完成的,java虚拟机是C++或者C写的。
java和C++区别:内存动态分配和垃圾收集技术。
java程序从源文件创建到程序运行:1、源文件由编译器编译成字节码;2、字节码由java虚拟机解释运行。
汇编语言:直接面向处理器的程序设计语言。汇编语言所操作的对象不是具体的数据而是寄存器或者存储器。汇编语言指令是机器指令的一种符号表示。
0 0
原创粉丝点击