JVM 深入理解

JVM是JAVA虚拟机(JAVA Virtual Machine)的缩写,是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JAVA虚拟机有自己完善的虚拟硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得JAVA程序只需生成在JAVA虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。





JVM的生命周期:

(1) JVM 实例的诞生
当启动一个JAVA程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点。

(2) JVM 实例的运行
main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,JAVA程序也可以标明自己创建的线程是守护线程。

(3) JVM 实例的消亡
当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出。


JVM  :JAVA 虚拟机
所有的JAVA程序都是运行在JVM上,JVM是JRE的一部分。

JRE  : JAVA Runtime Environment(JAVA 运行环境)
JRE主要用于执行JAVA程序,JRE除了包含JVM外还包含一些基础的JAVA API,JRE是JDK的一部份。

JDK : JAVA Development Kit(JAVA 开发工具包)
JDK提供了JAVA的开发环境和运行环境(JRE),开发环境主要包含了一些开发工具,例如常用的JAVAc编译工具、jar打包执行程序、还有一些JVM监控工具等等。





JVM体系结构:

JVM 体系结构主要包含两个子系统和两个组件:
Class Loader(类加载器)子系统,Execution Engine(执行引擎)子系统
Runtim Data Area(运行时数据区域)组件,Native Interface(本地接口)组件





Class Loader  类加载器:

类加载器负责加载 JAVA 类的字节代码到JAVA 虚拟机中,可以根据指定的类名(如java.lang.Object)来装载class文件的内容到Runtime data area中的method area(方法区域)。JAVA程序员可以extends java.lang.ClassLoader类来写自己的Class loader。




双亲委派模型工作过程
如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

优点:java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在tools.jar中,无论哪个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果用户自己写了一个名为java.lang.Object的类,并放在程序的Classpath中,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为也无法保证,应用程序也会变得一片混乱。


站在JVM的角度讲,主要有两种类型加载器:启动类加载器和所有其它的类加载器。

启动类加载器是JVM实现的一部分,使用C++语言实现,其它类加载器都由java语言实现 ,独立于虚拟机外部,并且全部继承抽象类java.lang.ClassLoader

(1) Bootstrap ClassLoader  启动类加载器
这是JVM的根ClassLoader,它是用C++实现的,JVM启动时初始化此ClassLoader,并由此ClassLoader完成$JAVA_HOME$中jre\lib\rt.jar(Sun JDK的实现)中所有class文件的加载,这个jar中包含了java规范定义的所有接口以及实现。启动类加载器无法被JAVA程序直接引用。

(2) Extension ClassLoader  扩展类加载器
扩展类加载器负责加载<JAVA_HOME>\lib\ext目录中或者java.ext.dirs系统变量所指定的所有类库,开发者可以直接使用扩展类加载器。

(3)Application ClassLoader  应用程序类加载器
JVM用此classloader来加载用户类路径 (Classpath)上所指定的类库,包含指定的jar包以及目录,该加载器有时也称为系统类加载器。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

(4) User-Defined ClassLoader  用户自定义类加载器
User-DefinedClassLoader是Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中的jar以及目录。


类加载器之类加载过程:





1.  加载
加载过程负责找到二进制字节码并加载至JVM中,JVM通过类名、类所在的包名通过ClassLoader来完成类的加载。

2.  连接
链接过程负责对二进制字节码的格式进行校验、初始化装载类中的静态变量以及解析类中调用的接口、类。
(1)验证:确保被导入类的正确性
(2)准备:为类变量分配内存,并将其初始化为默认值
(3)解析:把类中的符号引用转换为直接引用

3. 初始化
初始化过程即为执行类中的静态初始化代码、构造器代码以及静态属性的初始化,在四种情况下初始化过程会被触发执行:调用了new;反射调用了类中的方法;子类调用了初始化;JVM启动过程中指定的初始化类。


Execution Engine 执行引擎:

执行引擎是JVM最核心的组成部分之一,其主要是执行class中的指令,任何JVM实现的核心是Execution engine 。执行引擎可以把JAVA字节码转为机器能识别的字节码,并调用机器的指令进行计算等,不同JVM的执行效率很大程度决定于他们各自实现的Execution engine的好坏。“虚拟机”的执行引擎与“物理机”的执行引擎是比较类似的,这两种机器都有执行代码能力,其区别是物理机的执行引擎是直接建立在处理器、硬件、指令集和操作系统层面上的,而虚拟机的执行引擎是自己实现的,因此虚拟机可以自行制定指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令。

JAVA Native Interface(JNI)本地接口:
Java本地接口(Java Native Interface,JNI)是一个标准的Java API,它支持将 Java 代码与使用其他编程语言编写的代码相集成,例如可以调用Native语言函数C\C++等。JNI是java与其它编程语言交互的接口。

Runtime Date Area 运行时数据区:
这个组件就是JVM的内存区域。

Runtime data area  主要包括五个部分:
Heap ( 堆)
Method Area( 方法区域)
VM Stack( 虚拟机栈 )
Native method stack( 本地方法栈)
Program Counter( 程序计数器)

Heap 和 Method Area是被所有线程的共享使用的;而vm stack, Program counter 和Native method stack是以线程为粒度的,每个线程独自拥有 。


程序计数器Program Counter Register:

程序计数器是是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器完成。JAVA虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程”私有的内存。

虚拟机栈 VM stack:

与程序计数据器一样,JAVA虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是JAVA方法执行的内存模型:每个方法被执行时都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入到出栈的过程 。

本地方法栈Native Method stack:

本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行JAVA方法服务的,而本地方法栈则是为虚拟机使用到的本地方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。例如Sun HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一。

方法区 Method area:

方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息等。当开发人员在程序中通过Class对象中的getName等方法来获取信息时,这些数据都来源于方法区域。

方法区域也是全局共享的,因此会涉及到多线种访问的同步问题,方法区在一定的条件下它也
会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出JAVA.lang.OutOfMemoryError:PermGen full的错误信息

在Sun JVM中这块区域对应的为Permanet Generation,又称为持久代, Permanet Generation实际上并不等同于方法区,只不过是Hotspot JVM用Permanet Generation来实现方法区而已,有些虚拟机也没有PermSpace而是用其他机制来实现方法区。


Heap  堆空间:

堆空间是JAVA对象生死存亡的地区,JAVA对象的出生,成长,死亡都在这个区域完成。JAVA程序在运行时创建的所有类实例或数组都放在堆中。

每一个JAVA程序独占一个JVM实例, 一个JVM实例只存在一个堆空间,因些每个JAVA程序都有它自己的堆空间,它们不会彼此干扰。

同一个JAVA程序的多个线程都共享着同一个堆空间,所以就需要考虑多线程访问对象(堆数据)的同步问题。

Sun的 HotSpot虚拟机对于堆内存共划分为二大部分:
年轻代 / 新生代( Young Generation )、老年代( Old Generation)





1. Young (年轻代)

jvm规范中的 Heap的一部份, 年轻代又分三个区:一个Eden区,两个Survivor区

(1) 伊甸园(Eden space ):
JAVA堆空间中的大部分对象在此出生,该区的名字因此而得名。也即是说当你的JAVA程序运行时,需要创建新的对象时,JVM都将在该区为你创建一个指定的对象供程序使用。

(2) 幸存者0 区(Survivor 0 space )和幸存者1 区(Survivor1 space ):
当伊甸园的空间用完时,程序又需要创建对象;此时JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的还存活的对象 移动到幸存者0区或1区。幸存者两个区就是用于存放伊甸园垃圾回收时所幸存下来的JAVA对象。

Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。

2. Tenured (老年代)
在年轻代中经历了多次垃圾回收后仍然存活的对象,就会被放到老年代中。因此,可以认为老年代中存放的都是一些生命周期较长的对象。另外一些大对象也会直接进入老年代,可以通过设置jvm参数来指定多大对象直 接进入老年代( 参数为-XX:PretenureSizeThreshold=1024,单位为字节)。








猜你喜欢

转载自maosheng.iteye.com/blog/2267238