浅谈JVM及原理

一、什么是JVM ?

JVM, 中文名是Java虚拟机, 正如它的名字, 是一个虚拟机器,来模拟通用的物理机。 JVM是一个标准,一套规范,  规定了.class文件在其内部运行的相关标准和规范。 及其相关的内部构成。 好比:全部的JVM都是基于栈结构的运行方式。那么不符合这种要求的,不算是JVM, 如Android中所使用的Dalvik 虚拟机就不能称做是JAVA 虚拟机, 由于它是基于寄存器(最新的Android系统听说已经放弃了Dalvik VM, 而是使用ART)。java

JVM相关的产品有不少, 一般最有名的莫过于如今Oracle公司全部的HotSpot 虚拟机。所以, 这里讨论的都是HotSpot虚拟机, 若是没有特别说明。 算法


二、类加载?

类加载, 是经过JVM的类加载器从JVM外部以二进制字节流的方式加载到JVM中。但JVM自己有至少三种类加载器:BootStrap(根类加载器,C++实现, 加载位于jre/lib/rt.jar)、Extension(扩展类加载器, 主要用于加载jre/lib/ext/下的jar)、System(加载classpath环境变量所指定的class);固然还有,自定义的类加载器(用于实现本身的类加载器, 如Tomcat中就实现多个类加载器,用来管理不一样的jar)。
若是, 我有一个HelloWorld的类须要加载, 首先类加载器会去从最底层的类加载器去验证这个类是否被加载, 若是没有, 则委托给上一次的类加载器验证是否被加载, 若是到BootStrap类加载器都没有发现HelloWorld类被加载, 那么类加载器将执行加载任务, 若是根类加载器没有加载, 则委托给下一级的Extension类加载器去尝试加载,直到这个类被加载成功。 参考下图:
须要注意的是:若是一个类被不一样的类加载器加载, 那么就是两个不一样的类。

三、类加载的具体过程?

被java编译器(不只限于, 还有其余任何的能够编辑成为.class的编译器)编译过的.class文件(多是以jar、war、jsp等形式), 通过类加载器加载 、 验证、准备、解析、初始化以后, 才能够被使用。基本的过程以下:数据结构

  • 加载: 首先,经过一个类的全类名来获取此类的二进制字节流。其次,将类中所表明的静态存储结构转换为运行时数据结构, 最后,生成一个表明加载的类的java.lang.Class对象, 做为方法区这个类的全部数据的访问入口。加载完成以后, 虚拟机外部的二进制静态数据结构就转换成了虚拟机所须要的结构存储在方法区中(至于如何转换, 则由具体虚拟机本身定义实现), 而所生成的Class对象, 则存放在方法区中, 用来做为程序访问方法区中数据的外部接口。
  • 验证:其目的就是保证加载进来的.class文件不会危害到虚拟机自己, 且内容符合当前虚拟机规范要求。主要验证的内容大体有:文件格式、元数据验证、字节码验证、符号引用验证。其中文件格式验证, 主要确保符合class文件格式规范(如文本后缀为.class的文件将验证不经过), 以及主次版本号, 验证是否当前JVM能够处理等。元数据验证,主要验证编译后的字节码描述信息是否符合java语法规范。字节码验证, 其最为复杂, 主要经过控制流和数据流肯定语义是否合法、符合逻辑。符号引用验证,能够看作是除自身之外(常量池中各类引用符号)的信息匹配校验,如经过持有的引用可否找到对应的实例。
  • 准备:正式为类变量分配内存,并设置类变量的初始值。这些变量都会在方法区中进行分配。
  • 解析:将常量池内的符号引用替换为直接引用的过程。主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄等。
  • 初始化:加载的最后阶段, 程序真正运行的开始。

四、java运行时数据区 ?

既然类以及加载到JVM中, 那么数据如何真正的运行?以下图:
类加载进来, JVM是经过上图所示的区域来运行和管理这些加载进来的CLASS。即程序运行的是时候, 由上面逻辑单元来运行程序, 包括:方法区、堆、本地方法栈、栈、程序计数器(PC)五大部分组成(有些VM说常量池也是其中的一个单元, 可是HotSpot VM中的常量池是方法区中的一部分)。(注意线程共享)

  • 程序计数器 (PC):能够看作是当前线程执行字节码的行号指示器。字节码解释器工做的时候就是经过这个计数器的值来选取下一条须要执行的字节码指令, 分支, 循环、跳转、异常处理、线程恢复等基础功能依赖计数器完成。
  • 虚拟机栈:和计数器同样, 也是线程私有的,生命周期同线程一致。每一个方法在执行时,都会建立一个栈帧,用于存储局部变量表、操做数栈、动态连接、方法出口等信息。方法调入则入栈, 方法执行完则出站。局部变量表存储各类基本类型数据(java的8种,其中long,double占用2个局部变量控件,其他数据占用1个)、对象引用(reference类型)。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时, 这个方法须要在帧中分配多大的局部变量空间是彻底肯定的。在方法运行期间是不会改变局部变量表的大小的。
  • 本地方法栈:此栈和JVM栈做用很是相似, 不一样在于本地方法栈为虚拟机使用到的Native方法服务, 而JVM栈则是为Java执行的方法服务。Sun HotSpot虚拟机, 直接把本地方法栈和虚拟机栈合二为一。本地方法栈也会抛出StackOverFlowError和OutOfMemoryError异常。
  • Java堆:是JVM管理内存中最大的一块。被全部线程共享一块区域。堆是GC垃圾收集器管理的主要区域。从内存回收角度看, java堆被分为新生代、老年代, 再细致一点有其余的划分。这些目的主要就是更快的分配和回收内存。
  • 方法区:和java堆相同, 线程共享区域, 用来存储已被虚拟机加载的类信息, 常量、静态变量、即时编译器编译后的代码等数据。有人称做此方法区为“永久带”, 本质上不等价,只是HotSpot VM将GC分代收集扩展到了方法区,这样HotSpot的垃圾收集器管理方法区和管理java堆同样(优势:不用专门为方法区写一套垃圾收集器, 缺点:容易致使内存溢出)。官方如今拥也有放弃永久带并改成采用Native Memory来实现方法区的计划,目前已经发布的JDK7中的HotSpot中, 已经将本来放在方法区中的字符串常量池移出了。jsp

  • 运行时常量池:是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述外,还有一项就是常量池, 用于存放编译期间生成的各类字面量和符号引用 ,这部份内容在类加载后进入方法区的运行时常量池中存放。spa

五、垃圾收集?

在java运行时区域中, 程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随 程而灭。由于这几个区域
的内存分配和回收都是具备肯定性,这几个区域不须要过多考虑回收的问题。由于方法结束以后或线程结束以后, 
内存天然就跟着回收了(这不是绝对的, 由于若是当栈内存中的引用很消耗内存的时候, 须要手动将引用置为null,
以便垃圾收集器回收大对象)。而java堆和方法区不同,一个接口中的多个实现类须要的内存可能不同, 一个
方法中的 多个分支须要的内存也可能不同,咱们只有在程序处于运行期间时,才知道会建立哪些对象, 垃圾收集
器关注的就 是这部份内存。其也是动态的。
垃圾收集器的区域以下图:

垃圾收集本是有一套很是复杂的算法, 若是在方法区中(HotSpot VM中的永久带)进行垃圾收集, 那么其性价比极
底的,由于垃圾回收主要收集永久带中的两部份内容:废弃的常量和无用的类。回收永久带中的常量和方法区很是相
似。可是在堆中, 尤为是在新生代中,常规应用进行一次垃圾收集, 通常能够回收70%——95%的空间。而永久带
的垃圾收集要远地与此。
如上图所示, 每个黑框中都是一个垃圾收集器, 对应特定的垃圾收集算法, 来挺高总体的垃圾收集效率。