JVM之JVM的体系结构

1、JDK的组成

JDK:JDK是Java开发工具包,是Sun Microsystems针对Java开发员的产品。JDK中包含JRE(在JDK的安装目录下有一个名为jre的目录,里面有两个文件夹bin和lib,在这里能够认为bin里的就是jvm,lib中则是jvm工做所须要的类库,而jvm和 lib和起来就称为jre)和一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。html

Java Runtime Environment(JRE):是运行基于Java语言编写的程序所不可缺乏的运行环境。也是经过它,Java的开发者才得以将本身开发的程序发布到用户手中,让用户使用。JRE中包含了Java virtual machine(JVM),runtime class libraries和Java application launcher,这些是运行Java程序的必要组件。java

JVM(java virtual machine):就是咱们常说的java虚拟机,它是整个java实现跨平台的最核心的部分,全部的java程序会首先被编译为.class的类文件,这种类文件能够在虚拟机上执行。数据库

2、JVM的位置

JVM就是运行在操做系统之上的一个软件数组

 3、JVM体系结构

JVM的组成:安全

  • 类加载子系统 Class loader
  • 运行时数据区 JVM 内存模型
  • 执行引擎

4、类加载子系统


 ======================类加载器=======================网络

 类加载器(ClassLoader):负责加载class文件(classs文件在文件开头有特定的文件标识),将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构;ClassLoader只负责加载class文件的加载,至于它是否能够运行,则由Execution Engine决定。数据结构

 

一、BootStrapLoader(引导类加载器):类加载器也是java类,他们也须要类加载器加载进入内存,显然必需要有第一个不是java类的类加载器,来完成这个工做,这个正是BootStrap。负责加载存放在D:\Program Files (x86)\Java\jdk1.7.0_79\jre\lib下,或被-Xbootclasspath参数指定的路径中的,而且能被虚拟机识别的类库(如rt.jar,全部的java.*开头的类均被Bootstrap ClassLoader加载);启动类加载器是没法被Java程序直接引用的;rt.jar 里面的类的加载器都是BootStrapLoader。多线程

 二、Extension ClassLoader(扩展类加载器):该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载D:\Program Files (x86)\Java\jdk1.7.0_79\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的全部类库(如javax.*开头的类),开发者能够直接使用扩展类加载器。ext 目录下全部的类的加载器都是Extension ClassLoaderapp

 

 三、Application ClassLoader(应用程序类加载器):该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者能够直接使用该类加载器,若是应用程序中没有自定义过本身的类加载器,通常状况下这个就是程序中默认的类加载器。jvm

====================JVM类加载机制==============

全盘负责:当前线程的类加载器负责加载某个Class时,该Class所依赖的和引用的其余Class也将由该类加载器负责载入,除非显示使用CLassLoader.loadClass()指定类加载器来载入

父类委托:先让父类加载器试图加载该类,只有在父类加载器没法加载该类时才尝试从本身的类路径中加载该类。因此咱们在开发中尽可能不要使用与JDK相同的类(例如自定义一个java.lang.System类),由于父类加载器中已经有一份java.lang.System类了,它会直接将该类给程序使用,而你自定义的类压根就不会被加载。

双亲委派模型:

  双亲委派模型的工做流程是:若是一个类加载器收到了类加载的请求,它首先不会本身去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,所以,全部的类加载请求最终都应该被传递到顶层的启动类加载器中,
只有当父加载器在它的搜索范围中没有找到所需的类时,即没法完成该加载,子加载器才会尝试本身去加载该类。
双亲委派机制:

  • 一、当AppClassLoader加载一个class时,它首先不会本身去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
  • 二、当ExtClassLoader加载一个class时,它首先也不会本身去尝试加载这个类,而是把类加载请求委派给BootStrap ClassLoader去完成。
  • 三、若是BootStrap ClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
  • 四、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,若是AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

双亲委派模型意义:

  •   -系统类防止内存中出现多份一样的字节码
  •   -保证Java程序安全稳定运行

 

 ==================类的加载过程======================

类的加载过程:JVM将javac编译好的class字节码文件加载到内存中,并对该数据进行验证、解析和初始化、造成JVM能够直接使用的JAVA类,最终回收(卸载)的过程。

字节码(.class)文件来源:

  • – 从本地系统中直接加载
  • – 经过网络下载.class文件
  • – 从zip,jar等归档文件中加载.class文件
  • – 从专有数据库中提取.class文件
  • – 将Java源文件动态编译为.class文件

一、加载:加载阶段其实就是JVM经过一个类的全限定名来获取其定义的二进制字节流,并将这个字节流所表明的静态存储结构转化为方法区的运行时数据结构且在Java堆中生成一个表明这个类的java.lang.Class对象,做为对方法区中这些数据的访问入口。在该阶段咱们开发人员能够干预,例如:咱们能够指定类加载器来加载该字节数组或者自定义类加载器来加载。

二、连接:将java类的二进制代码合并到JVM的运行状态中的过程

  • a、验证:验证是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。
  • b、准备:该阶段是在方法区中为类变量(static变量)分配内存并设置类变量初始值。例如:public static int flag=1;该阶段初始化值为0。
  • c、解析:虚拟机将常量池中的符号引用替换为直接引用的过程。(直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄)

三、初始化:初始化为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。

  • 初始化阶段就是执行类构造器<clinit>()的过程,类构造器<clinit>()是由编译器自动收集类中的全部类变量的赋值动做和静态语句块中的语句合并产生的。
  • 当初始化一个类的时候,若是发现其父类尚未进行过初始化,则须要先初始化其父类。
  • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。
  • 当访问一个java 类的静态域时,只有正真申明这个域的类才会被初始化。

四、使用:程序使用JVM加载的类

五、卸载 

  • 执行了System.exit()方法
  • JVM垃圾回收机制触发回收
  • 程序正常执行结束
  • 程序在执行过程当中遇到了异常或错误而异常终止
  • 因为操做系统出现错误而致使Java虚拟机进程终止

5、运行时数据区

一、方法区(Method Area):方法区是各个线程共享的内存区域;方法区用于存储已被虚拟机加载的类的模板信息、常量、静态变量等;虽然Java虚拟机规范把方法区描述为堆的一部分,可是他还有个别名叫作Non-heap(非堆),目的应该是与Java堆区分开来;根据Java虚拟机规范的规定,当方法区没法知足内存分配需求时,将抛出OutOfMemoryError 异常;相对而言,垃圾收集在这个区域是比较少出现的,但并不是数据进入了方法区就如永久代的名字同样永久存在了。这区域的内存回收目标重要是针对常量池的回收和类型的卸载。
方法区只是一个规范:

  • 在HotSpot虚拟机上开发、部署程序咱们把方法区称为“永久代”(Permanent Generation);
  • 他虚拟机(如 BEA JRockit、IBM J9 等)来讲是不存在永久代的概念的。
  • HotSpot虚拟机在JKD.8中已经没有方法区的概念了,他使用元空间代替该区域

二、PC寄存器(程序计数器):每一个线程都有一个程序计数器,是线程私有的;就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,既将要执行的指令代码),由执行引擎读取下一条指令,是一个很是小的内存空间,几乎能够忽略不记;它是当前线程所执行的字节码的行号指示器,字节码解释器经过改变这个计数器的值来选取下一条须要执行的字节码指令。若是执行的是一个Native方法,那这个计数器是空的;用以完成分支、循环、跳转、异常处理、线程恢复等基础功能。不会发生内存溢出OOM错误
本地方法栈(Native Stack):与虚拟机栈基本相似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。(栈的空间大小远远小于堆)

三、虚拟机栈(Vm Stack)
  栈也叫栈内存,主管 Java 程序的运行,是在线程建立时建立,它的生命期是跟随线程的生命期,线程结束栈内存也就仔放,对于栈来讲不存在垃圾回收问题,只要线程结束该栈就释放,生命周期和线程一致,是线程私有的。8种基木类型的变量+对象的引用变量+实例方法都是在函数的栈内存中分配。

栈的运行原理:栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法( Method )和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧 Fl ,并被压入到栈中, A方法又调用了B方法,因而产生栈帧 F2 也被压入栈,B方法又调用了C方法,因而产生栈帧 F3 也被压入栈,执行完毕后,先弹出 F3 栈帧,再弹出 F2 栈帧,再弹出 Fl 栈帧 以此类推, 遵循“先进后出” / “后进先出”原则。每一个方法执行的同时都会建立一个栈帧,用于存储局部变量表、操做数、动态连接、方法出口等信息,每个方法从调用直至执行完毕的过,就对应着一个栈帧在虚拟机中入栈到出栈的过程。栈的大小和具体JVM的实现有关,一般在 256K~1024K 之间, 1M 左右。

JVM栈的特色:

  • 局部变量表所需的内存空间在编译期间完成内存分配。当进入一个方法时,这个方法须要在帧中分配多大的内存空间是彻底肯定的,在方法运行期间不会改变局部变量表的大小。
  • 在Java虚拟机规范中,对这个区域规定了两种异常状态:若是线程请求的栈的深度大于虚拟机容许的深度,将抛出StackOverFlowError异常(栈溢出);若是虚拟机栈能够动态扩展(如今大部分Java虚拟机均可以动态扩展,只不过Java虚拟机规范中也容许固定长度的java虚拟机栈),若是扩展时没法申请到足够的内存空间,就会抛出OutOfMemoryError异常(没有足够的内存)。

四、本地方法栈(Native Method Stacks):与虚拟机栈所发挥的做用是很是类似的,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的本地Native方法服务‘;在虚拟机规范中对本地方法栈中的使用方法、语言、数据结构并无强制规定,所以具体的虚拟机能够自由实现它。甚至有的虚拟机(例如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。本地方法栈也会抛出StackOverFlowError和OutOfmMemoryError异常。


五、Java堆(Java Heap):是Java虚拟机管理内存中的最大一块;Java堆是全部线程共享的一块内存管理区域。此内存区域惟一目的就是存放对象的实例,几乎全部对象实例都在堆中分配内存。这一点在Java虚拟机规范中的描述是:全部对象实例以及数组都要在堆上分配,可是随着JIT编译器的发展与逃逸技术逐渐成熟,栈上分配、标量替换优化技术将会致使一些微妙的变化发生,全部的对象都分配在堆上也不是变的那么“绝对”了。详解请学习个人:JVM之堆的体系结构

相关文章
相关标签/搜索