Java技术原理详解

一、Java 运行原理 
1、高级语言运行过程 
在程序真正运行在CPU上之前,必须要让OS的kernel理解我们在编辑器或者IDE里根据每种语言的语法规则敲入的源代码,kernel才能做出相关的调度,所以需要先将源代码转化成可执行的二进制文件,这个过程通常由编译器完成。有些编译器直接将源代码编译成机器码,载入内存后CPU可以直接运行。而机器码的格式与跟具体的CPU架构相关连,例如ARM CPU无法理解Intel CPU机器码。因此,同样的源代码需要根据不同的硬件进行特定的编译。高级语言转换到低级语言的桥梁就是编译器。程序员写好源代码,编译器将源码编译成可执行的机码,然后CPU读取机器码,执行程序。 
2、Java语言的执行过程 
这里写图片描述 
宽泛地讲,Java源代码(.java)经过java编译器(javac.exe)编译之后,并没有直接转化为机器码,而是转化成一种中间格式——字节码(.class),字节码再经过Java虚拟机解释,转化成机器码,然后经由操作系统到达CPU运行。整个执行过程如下图所示: 
这里写图片描述 
Java的跨平台是基于JVM虚拟机这一中间物来实现的,Java源程序经过编译器编译后生成虚拟机能够理解的字节码(ByteCode——class文件的内容),虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定系统上的机器码,然后在特定的机器上运行。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。 
这里写图片描述 
3、JVM——Java Virtual Machine 
JVM是一个虚构出来的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 的主要工作是解释自己的指令集(即字节码)并映射到本地的 CPU 的指令集或 OS 的系统调用。 
三、 JVM的体系结构 
Class Loader:类装载器,从入口处开始按需加载.class文件,填充这些数据到运行时数据区 
Execution Engine:执行引擎,JVM的CPU,不断地取指令,JIT编译翻译执行字节码,或者执行本地方法 
Runtime Data Areas:运行时数据区,核心区,运行的时候操作所分配的内存区,包括方法区、堆、java栈、PC寄存器、本地方法栈 
这里写图片描述 
1、类加载器 
类加载器加载其实就是根据编译后的Class文件,将Java字节码载入JVM内存,并完成对运行数据处于的初始化工作,供执行引擎执行。 
类加载过程: 
装载——链接(验证,准备,解析)—— 初始化 
这里写图片描述 
1.Loading:,找到二进制字节码(Class文件)并加载至JVM内存中,标识一个被加载的类:类名+类所在的包名+Class Loader instance ID 
2.Linking:

  • Verifying:验证元数据,文件格式,字节码等,确保class文件包含的字节码信息符合JVM的规范,以免危及JVM安全;

  • Preparing:准备分配给类所需要内存的数据结构,指示在类中定义的字段、方法和接口;

  • Resolving:对类中的所有属性、方法进行验证,以确保其需要调用的属性、方法存在,以及具备应的权限;符号引用的转换等

3.Initialing:初始化执行类中的静态初始化代码、构造器代码以及静态属性 
类装载器类型: 
启动类装载器:JVM实现的一部分; 
用户自定义类装载器:是Java程序的一部分,必须是ClassLoader类的子类。 
类装载顺序: 
Jvm启动时,由Bootstrap向User-Defined方向加载类;应用进行Class Loader时,由User-Defined向Bootstrap方向查找并加载类; 
类加载采用父类委托制,子加载器能查询父加载器已缓存类,委托只能从下到上,反之不行。类加载器可以加载一个类,但是它不能卸载一个类。但是类加载器可以被删除或者被创建。一个类可以被不同的类加载器加载。 
这里写图片描述 
Bootstrap ClassLoader 
JVM的根ClassLoader,它是用C++实现的,在JVM启动的时候创建,负责装载$JAVA_HOME中jre/lib/rt.jar(Sun JDK的实现)中所有class文件,这个jar中包含了Java规范定义的所有接口以及实现。 
Extension ClassLoader 
装载除了基本的Java API以外的扩展类,它也负责装载其他的安全扩展功能。 
System ClassLoader 
负责加载应用程序类,加载启动参数中指定的Classpath中的jar包以及目录,在Sun JDK中ClassLoader对应的类名为AppClassLoader。 
User-Defined ClassLoader 
Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中的jar以及目录。 
2、执行引擎 
类加载器将.class文件载入内存之后,执行引擎以Java 字节码指令为单元,读取Java字节码;而后由解释器或者即时编译器(JIT Compiler)将字节码转化成平台相关的机器码。 
这里写图片描述
JVM实现技术: 
解释器:第一代JVM,一条一条地读取,解释并且执行字节码指令。因为它一条一条地解释和执行指令,所以它可以很快地解释字节码,但是执行起来会比较慢。这是解释执行的语言的一个缺点。字节码这种“语言”基本来说是解释执行的。 
这里写图片描述 
即时编译器(just-in-time compiler):第二代JVM,狭义来说是当某段代码即将第一次被执行时进行编译,将class类文件解释成二进制文件后的结果缓存下来,当第二次执行时直接从缓存中取,因此JIT依赖更多内存缓存解释的结果。JIT编译是动态编译的一种特例。JIT编译一词后来被泛化,时常与动态编译等价;但要注意宽泛与狭义的JIT编译所指的区别。 
这里写图片描述 
自适应编译器(adaptive compiler):柔和第一代和第二代JVM,也是动态编译的一种,但它通常执行的时机比JIT编译迟,先让程序“以某种形式”先运行起来,收集一些信息之后再做动态编译,也就是说在所有执行过的代码里只寻找一部分来编译;而”收集信息”决定了编译哪部分代码,换个角度说“收集信息”就是在程序运行过程中监控代码执行的频率,自动缓存利用率高的代码,这样的编译可以更加优化。这个”某种形式”可以称为“baseline execution“,可以由解释器或简单的JIT编译器承担。 
这里写图片描述 
HotSpot是一个JVM的实现,得名于它得混合模式执行引擎(包括解释器和自适应编译器),这个JVM最初由Longview/Animorphic实现,随着公司被Sun/JavaSoft收购而成为Sun的JVM,并于JDK 1.3.0开始成为Sun的Java SE的主要JVM。在Sun被Oracle收购后,现在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是较新的JVM,用来代替JIT(Just in Time), Java原先是把源代码编译为字节码在虚拟机执行,这样执行速度较慢;而HotSpot将最需要编译的“热点”代码编译为本地(原生native)代码,如果已经被编译成本地代码的字节码不再被频繁调用了,那么Hotspot VM会把编译过的本地代码从cache里移除,并且重新按照解释的方式来执行它,这样显着提高了性能。 HotSpot VM 参数可以分为规则参数(standard options)和非规则参数(non-standard options)。Hotspot VM分为Server VM和Client VM两种,这两种VM使用不同的JIT编译器。 
3、运行时数据区 
当运行一个JVM Instance时,系统将分配给它一块内存区域(大小可设置),这一内存区域由JVM自行管理。从这一块内存中分出一块用来存储一些运行数据,例如创建的对象,传递给方法的参数,局部变量,返回值等等。这一块内存就称为运行数据区域。运行数据区域可以划分为6大块:Java栈、程序计数寄存器(PC寄存器)、本地方法栈(Native Method Stack)、Java堆、方法区域(包括运行常量池——Runtime Constant Pool)。其中每个线程私有程序计数器,JVM栈,本地方法栈,方法区和堆则由JVM实例中的所有线程共享,在同一个实例中可以启用多个线程。 
这里写图片描述 
程序计数器 
每个线程私有,线程启动时创建,用来存放当前正在被执行的字节码指令(JVM指令)的地址,如该方法为native的,则PC寄存器中不存储任何信息。 
JVM栈 
每个线程私有,线程启动时创建。存放着一系列的栈帧(Stack Frame),JVM只能进行压入(push)和弹出(pop)栈帧这两种操作。每当调用一个方法时,JVM就往栈里压入一个栈帧,方法结束返回时弹出栈帧。如果方法执行时出现异常,可用printStackTrace等方法来查看栈的情况。栈的示意图如下: 
这里写图片描述 
每个栈帧包含三个部分:本地变量数组,操作数栈,方法所属类的常量池引用

  • Local Variable Array:从0开始按顺序存放方法所属对象的引用、传递给方法的参数、局部变量。

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

  • Reference to Constant Pool:当JVM执行到需要常量池的数据时,就是通过这个引用来访问常量池的。栈帧中的数据还要负责处理方法的返回和异常。如果通过return返回,则将该方法的栈帧从Java栈中弹出。如果方法有返回值,则将返回值压入到调用该方法的方法的操作数栈中。另外,数据区中还保存中该方法可能的异常表的引用。

本地方法栈 
当程序通过JNI(Java Native Interface)调用本地方法(如C或者C++代码)时,就根据本地方法的语言类型建立相应的栈,此区域用于存储每个native方法调用的状态。 
堆(Heap) 
堆中存放的是程序创建的对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配。当堆中的空间无法满足新建对象所需的内存开销,会有溢出现象而导致程序崩溃,为了避免溢出,当对象执行结束时,其占据的内存空间需要等待GC(Garbage Collection)进行回收,因此这个区域对JVM的性能影响很大。 
注意: 堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,导致了new对象的开销是比较大的 
Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配。 
TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。 
方法区域 
每个线程共享的,启动一个JVM实例时被创建,它用于存运行放常量池、所加载的类的信息(域、方法、静态变量、final类型的常量)。开发人员在程序中通过Class对象中的getName、isInterface等方法获取的数据都来源于方法区域,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出Out Of Memory的错误信息。不同的JVM实现方式在实现方法区域的时候会有所区别。Oracle的HotSpot称之为永久区域(Permanent Area)或者永久代(Permanent Generation)。 
运行常量池 
其空间从方法区域中分配,用来存放类、方法、接口的常量和域的引用信息,当一个方法或者域被引用的时候,JVM就通过运行常量池中的引用信息来查找方法和域在内存中的的实际地址。 
四、JVM垃圾回收 
Garbage Collection的基本原理: 
将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停。 
垃圾回收算法 
1、按照基本回收策略分为以下4种: 
Reference Counting:引用计数,比较古老的回收算法;原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,引用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。

Mark-Sweep:标记-清除,此算法执行分两阶段;第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。 
这里写图片描述 
Copying: 复制,把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题;但是此算法的缺点就是需要两倍内存空间。 
这里写图片描述 
Mark-Compact:标记-整理,结合了Mark-Sweep和Copying两个算法的优点;也分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。避免了Mark-Sweep算法的碎片问题,同时也避免了Copying算法的空间问题。 
这里写图片描述 
2、按分区对待的方式分为以下2种

  • Incremental Collecting:增量收集,实时垃圾回收算法,即:在应用进行的同时进行垃圾回收。JDK5.0中的收集器没有使用这种算法的。

  • Generational Collecting:分代收集,基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。

3、按系统线程分为以下3种

  • 串行收集:串行收集使用单线程处理所有垃圾回收工作,因为无需多线程交互,实现容易,而且效率比较高。但是,其局限性是无法使用多处理器的优势,所以此收集适合单处理器机器。当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上。

  • 并行收集:并行收集使用多线程处理垃圾回收工作,因而速度快,效率高。而且理论上CPU数目越多,越能体现出并行收集器的优势。

  • 并发收集:相对于串行收集和并行收集而言,前面两个在进行垃圾回收工作时,需要暂停整个运行环境,而只有垃圾回收程序在运行,因此,系统在垃圾回收时会有明显的暂停,而且暂停时间会因为堆越大而越长。

处理碎片 
由于不同Java对象存活时间是不一定的,因此,在程序运行一段时间以后,如果不进行内存整理,就会出现零散的内存碎片。碎片最直接的问题就是会导致无法 分配大块的内存空间,以及程序运行效率降低。所以,在上面提到的基本垃圾回收算法中,“复制”方式和“标记-整理”方式,都可以解决碎片的问题。 
对象创建和对象回收 
垃圾回收线程是回收内存的,而程序运行线程则是消耗(或分配)内存的,一个回收内存,一个分配内存,从这点看,两者是矛盾的。因此,在现有的垃圾回收方式 中,要进行垃圾回收前,一般都需要暂停整个应用(即:暂停内存的分配),然后进行垃圾回收,回收完成后再继续应用。这种实现方式是最直接,而且最有效的解决二者矛盾的方式。 
但是这种方式有一个很明显的弊端,就是当堆空间持续增大时,垃圾回收的时间也将会相应的持续增大,对应应用暂停的时间也会相应的增大。一些对相应时间要求很高的应用,比如最大暂停时间要求是几百毫秒,那么当堆空间大于几个G时,就很有可能超过这个限制,在这种情况下,垃圾回收将会成为系统运行的一个瓶颈。 为解决这种矛盾,有了并发垃圾回收算法,使用这种算法,垃圾回收线程与程序运行线程同时运行。在这种方式下,解决了暂停的问题,但是因为需要在新生成对象的同时又要回收对象,算法复杂性会大大增加,系统的处理能力也会相应降低,同时碎片问题将会比较难解决。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit) 
JRE是指运行Java程序所必须的环境集合,包含JVM标准实现及Java核心类库。JDK 是 Java 语言的软件开发工具包,针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。如果运行Java程序,只需安装JRE就可以了。如果编写Java程序,需要安装JDK。OpenJDK则是包含了开发与运行的开源实现。最主流的JDK是Sun公司发布的JDK,除了Sun之外,还有很多公司和组织都开发了属于自己的JDK,例如IBM,阿里等。 
根据应用领域的不同,JDK可分为三种版本:

SE(Standard Edition)标准版,通常用的一个版本,从JDK 5.0开始,改名为Java SE

EE(Enterprise Edition)企业版,使用这种JDK开发J2EE应用程序,从JDK 5.0开始,改名为Java EE

ME(Micro Edition)微型版,主要用于移动设备、嵌入式设备上的Java应用程序,从JDK 5.0开始,改名为Java ME

 

 

 

(转)Java 详解 JVM 工作原理和流程

作为一名Java使用者,掌握JVM的体系结构也是必须的。
说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成:Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(Java API)。它们的关系如下图所示:

运行期环境代表着Java平台,开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件),再然后字节码被装入内存,一旦字节码进入虚拟机,它就会被解释器解释执行,或者是被即时代码发生器有选择的转换成机器码执行。

Java平台由Java虚拟机和Java应用程序接口搭建,Java语言则是进入这个平台的通道,用Java语言编写并编译的程序可以运行在这个平台上。这个平台的结构如下图所示:

在Java平台的结构中, 可以看出,Java虚拟机(JVM) 处在核心的位置,是程序与底层操作系统和硬件无关的关键。它的下方是移植接口,移植接口由两部分组成:适配器和Java操作系统, 其中依赖于平台的部分称为适配器;JVM 通过移植接口在具体的平台和操作系统上实现;在JVM 的上方是Java的基本类库和扩展类库以及它们的API, 利用Java API编写的应用程序(application) 和小程序(Java applet) 可以在任何Java平台上运行而无需考虑底层平台, 就是因为有Java虚拟机(JVM)实现了程序与操作系统的分离,从而实现了Java 的平台无关性。 

JVM在它的生存周期中有一个明确的任务,那就是运行Java程序,因此当Java程序启动的时候,就产生JVM的一个实例;当程序运行结束的时候,该实例也跟着消失了。下面我们从JVM的体系结构和它的运行过程这两个方面来对它进行比较深入的研究。

1、Java虚拟机的体系结构

·每个JVM都有两种机制:

①类装载子系统:装载具有适合名称的类或接口

②执行引擎:负责执行包含在已装载的类或接口中的指令 

·每个JVM都包含

方法区、Java堆、Java栈、本地方法栈、指令计数器及其他隐含寄存器

 

 

对于JVM的学习,在我看来这么几个部分最重要:

Java代码编译和执行的整个过程

JVM内存管理及垃圾回收机制

下面分别对这几部分进行说明:

2、Java代码编译和执行的整个过程

也正如前面所说,Java代码的编译和执行的整个过程大概是:开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件),再然后字节码被装入内存,一旦字节码进入虚拟机,它就会被解释器解释执行,或者是被即时代码发生器有选择的转换成机器码执行。

(1)Java代码编译是由Java源码编译器来完成,也就是Java代码到JVM字节码(.class文件)的过程。 流程图如下所示:

(2)Java字节码的执行是由JVM执行引擎来完成,流程图如下所示:

 

Java代码编译和执行的整个过程包含了以下三个重要的机制:

·Java源码编译机制

·类加载机制

·类执行机制

 

(1)Java源码编译机制

Java 源码编译由以下三个过程组成:

①分析和输入到符号表

②注解处理

③语义分析和生成class文件

流程图如下所示:

最后生成的class文件由以下部分组成:

①结构信息:包括class文件格式版本号及各部分的数量与大小的信息

②元数据:对应于Java源码中声明与常量的信息。包含类/继承的超类/实现的接口的声明信息、域与方法声明信息和常量池

③方法信息:对应Java源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试符号信息

(2)类加载机制
JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

①Bootstrap ClassLoader

负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

②Extension ClassLoader

负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包

③App ClassLoader

负责记载classpath中指定的jar包及目录中class

④Custom ClassLoader

属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader

 

加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

(3)类执行机制

JVM是基于堆栈的虚拟机。JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。

JVM执行class字节码,线程创建后,都会产生程序计数器(PC)和栈(Stack),程序计数器存放下一条要执行的指令在方法内的偏移量,栈中存放一个个栈帧,每个栈帧对应着每个方法的每次调用,而栈帧又是有局部变量区和操作数栈两部分组成,局部变量区用于存放方法中的局部变量和参数,操作数栈中用于存放方法执行过程中产生的中间结果。栈的结构如下图所示:

3、JVM内存管理及垃圾回收机制

JVM内存结构分为:方法区(method),栈内存(stack),堆内存(heap),本地方法栈(java中的jni调用),结构图如下所示:

(1)堆内存(heap)

所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。 
操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的delete语句才能正确的释放本内存空间。但由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。这时由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,它不是在堆,也不是在栈,而是直接在进程的地址空间中保留一块内存,虽然这种方法用起来最不方便,但是速度快,也是最灵活的。堆内存是向高地址扩展的数据结构,是不连续的内存区域。由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大

(2)栈内存(stack)

在Windows下, 栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是固定的(是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 由系统自动分配,速度较快。但程序员是无法控制的。

堆内存与栈内存需要说明:

基础数据类型直接在栈空间分配,方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收。引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量 。方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完成后从栈空间回收。局部变量new出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收。方法调用时传入的literal参数,先在栈空间分配,在方法调用完成后从栈空间收回。字符串常量、static在DATA区域分配,this在堆空间分配。数组既在栈空间分配数组名称,又在堆空间分配数组实际的大小。

如:

(3)本地方法栈(java中的jni调用)

用于支持native方法的执行,存储了每个native方法调用的状态。对于本地方法接口,实现JVM并不要求一定要有它的支持,甚至可以完全没有。Sun公司实现Java本地接口(JNI)是出于可移植性的考虑,当然我们也可以设计出其它的本地接口来代替Sun公司的JNI。但是这些设计与实现是比较复杂的事情,需要确保垃圾回收器不会将那些正在被本地方法调用的对象释放掉。

(4)方法区(method)

它保存方法代码(编译后的java代码)和符号表。存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用持久代(Permanet Generation)来存放方法区,可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值。

垃圾回收机制

堆里聚集了所有由应用程序创建的对象,JVM也有对应的指令比如 new, newarray, anewarray和multianewarray,然并没有向 C++ 的 delete,free 等释放空间的指令,Java的所有释放都由 GC 来做,GC除了做回收内存之外,另外一个重要的工作就是内存的压缩,这个在其他的语言中也有类似的实现,相比 C++ 不仅好用,而且增加了安全性,当然她也有弊端,比如性能这个大问题。

 

4、Java虚拟机的运行过程示例

上面对虚拟机的各个部分进行了比较详细的说明,下面通过一个具体的例子来分析它的运行过程。

虚拟机通过调用某个指定类的方法main启动,传递给main一个字符串数组参数,使指定的类被装载,同时链接该类所使用的其它的类型,并且初始化它们。例如对于程序:

编译后在命令行模式下键入: java HelloApp run virtual machine 

将通过调用HelloApp的方法main来启动java虚拟机,传递给main一个包含三个字符串"run"、"virtual"、"machine"的数组。现在我们略述虚拟机在执行HelloApp时可能采取的步骤。

开始试图执行类HelloApp的main方法,发现该类并没有被装载,也就是说虚拟机当前不包含该类的二进制代表,于是虚拟机使用ClassLoader试图寻找这样的二进制代表。如果这个进程失败,则抛出一个异常。类被装载后同时在main方法被调用之前,必须对类HelloApp与其它类型进行链接然后初始化。链接包含三个阶段:检验,准备和解析。检验检查被装载的主类的符号和语义,准备则创建类或接口的静态域以及把这些域初始化为标准的默认值,解析负责检查主类对其它类或接口的符号引用,在这一步它是可选的。类的初始化是对类中声明的静态初始化函数和静态域的初始化构造方法的执行。一个类在初始化之前它的父类必须被初始化。整个过程如下:

展开阅读全文

没有更多推荐了,返回首页