1.什么是JVM?
JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。
2.JVM原理
1.Java编译器只要面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。
2.JVM执行程序的过程 :
(1)加载.class文件
(2)管理并分配内存
(3)垃圾回收
3.JVM是Java程序运行的容器,但是他同时也是操作系统的一个进程,因此他也有他自己的运行的生命周期,也有自己的代码和数据空间。
4.JVM在整个jdk中处于最底层,负责与操作系统的交互,用来屏蔽操作系统环境,提供一个完整的Java运行环境,因此也叫虚拟计算机.操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境。
(1)创建JVM装载环境和配置
(2)装载JVM.dll
(3)初始化JVM.dll并挂接到JNIENV(JNI调用接口)实例
(4)调用JNIEnv实例装载并处理class类
3.JVM体系结构
粗略分来,JVM的内部体系结构分为三部分,分别是:类装载器(ClassLoader)子系统,运行时数据区,和执行引擎。
-1.类加载器子系统
每一个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。JVM的两种类装载器包括:启动类装载器和用户自定义类装载器,启动类装载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。
类加载器(ClassLoader)
1.类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的Java.lang.Class对象,用来封装类在方法区类的对象。
类的加载的最终产品是位于堆区中的Class对象, Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
2.类加载过程
JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)
链接又分为三个步骤:验证、准备、解析
1) 装载:查找并加载类的二进制数据;
2)链接:
- 验证:确保被加载类的正确性;
- 准备:为类的静态变量分配内存,并将其初始化为默认值;
- 解析:把类中的符号引用转换为直接引用;
3)初始化:为类的静态变量赋予正确的初始值;
3.类加载器种类
1)Bootstrap ClassLoader
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
2)Extension ClassLoader
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
3)App ClassLoader
负责记载classpath中指定的jar包及目录中class
4)Custom ClassLoader
属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader
4.类加载机制–双亲委派模型
这个模型要求除了Bootstrap ClassLoader外,其余的类加载器都要有自己的父加载器。子加载器通过组合来复用父加载器的代码,而不是使用继承。在某个类加载器加载class文件时,它首先委托父加载器去加载这个类,依次传递到顶层类加载器(Bootstrap)。如果顶层加载不了(它的搜索范围中找不到此类),子加载器才会尝试加载这个类。
双亲委派模型最大的好处就是让Java类同其类加载器一起具备了一种带优先级的层次关系。
-2.运行时数据区
运行时数据区:主要包括:方法区,堆,Java虚拟机栈,程序计数器,本地方法栈,其中方法区和堆所有线程共享,Java栈,程序计数器,本地方法栈线程私有。
#1.方法区 (Method Area)
1.在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代。方法区是堆的逻辑部分。
由于持久代内可能会发生内存泄露或溢出等问题而导致的java.lang.OutOfMemoryError: PermGen ,JEP小组从JDK1.7开始就筹划移除持久代(JEP 122: Remove the Permanent Generation),并且在JDK 1.7中把字符串常量,符号引用等移出了持久代。到了Java 8,持久代被彻底地移出了JVM,取而代之的是元空间(Metaspace):
In JDK 8, classes metadata is now stored in the native heap and this space is called Metaspace.
所以从Java 8开始,方法区被移至 Metaspace 内。
2.方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。
3.运行时常量池
运行时常量池是class文件中每一个类或接口的常量池表的运行时表示形式,是方法区的一部分。它包括了若干种不同的常量。常量池表存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。运行时常量池具有动态性,运行期间也可以将新的量放到运行时常量池中,典型的应用是String类的intern方法:
public native String intern()
JDK 1.7开始,字符串常量和符号引用等就被移出持久代:
- 符号引用迁移至系统堆内存(Native Heap)
- 字符串字面量迁移至Java堆(Java Heap)
#2.堆(Heap)
1.它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。
2.堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的。
3.Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配。
4.TLAB仅作用于年轻代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
#3.程序计数器(Program Counter Register)
1.由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
2.如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。
#4.Java虚拟机栈(Java Virtual Machine Stacks)
1.与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。
2.虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame )用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
3.虚拟机只会直接对Java虚拟机栈执行两种操作:以帧为单位的压栈或出栈,每个帧代表一个方法,Java方法有两种返回方式,return和抛出异常,两种方式都会导致该方法对应的帧出栈和释放内存。
4.帧的组成:局部变量区(包括方法参数和局部变量,对于instance方法,还要首先保存this类型,其中方法参数按照声明顺序严格放置,局部变量可以任意放置),操作数栈,帧数据区(用来帮助支持常量池的解析,正常方法返回和异常处理)。
#5.本地方法栈(Native Method Stacks)
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
-3.执行引擎
1.它或者在执行字节码,或者执行本地方法
2.主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行;其中解释属于第一代JVM,即时编译JIT属于第二代JVM,自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代JVM的经验,采用两者结合的方式 。
3.自适应优化:开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对那些经常调用的方法启动一个后台线程,将其编译为本地代码,并进行仔细优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。
4.JVM垃圾回收
0.引用计数 VS 可达分析
主流的垃圾回收主要分两大类:引用计数和可达性分析。
引用计数
引用计数的实现很简单,它的思想就是给一个对象增加一个引用计数器,每当一个新的对象引用它时就给计数器加1,不引用了就减1,当计数器为0时即可认为对象无用了,可进行回收。
这个思想很简单,而且很多语言的底层(如Swift,Python)都是基于引用计数法进行回收的。但是引用计数法有一个很大的缺陷:它无法解决循环引用的问题,比如A引用B,B引用A,这样两对象的引用计数器永远不为0,两对象不能被回收,从而造成内存不能被及时清理。
JVM没有使用引用计数法,而是使用了可达性分析来进行GC。
可达分析
可达性分析是基于图论的分析方法,它会找一组对象作为GC Root(根结点),并从根结点进行遍历,遍历结束后如果发现某个对象是不可达的(即从GC Root到此对象没有路径),那么它就会被标记为不可达对象,等待GC(GC Root的对象必定为可以存活的对象)。
1.GC原理
1.GC的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停
(1)对年轻代的对象的收集称为minor GC;
(2)对年老代的对象的收集称为Full GC;
(3)程序中主动调用System.gc()强制执行的GC为Full GC。
2.不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:
(1)强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)
(2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)
(3)弱引用:在GC时一定会被GC回收
(4)虚引用:由于虚引用只是用来得知对象是否被GC
2.GC策略
1.Sun的JVM Generational Collecting(垃圾回收)采用分代回收的策略:把对象分为年轻代(Young)、年老代(Tenured)、持久代(Perm),对不同生命周期的对象使用不同的算法。(基于对对象生命周期分析)
2.通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是之前提到的MethodArea,不属于Heap。
年轻代(Young)
1.年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制年老区(Tenured)。
2.需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。
年老代(Tenured)
年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。
持久代(Perm)
用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。
参考资料
1. 《深入理解java虚拟机》
2. JAVA和JVM运行原理揭秘:http://developer.51cto.com/art/201009/227032.htm
3. JRE和JVM的区别:http://www.java800.com/peixun-8111304.html
4. 什么是jvm?:http://blog.csdn.net/stanlee_0/article/details/51171382#t4
5. 深入探究JVM | 初探GC - 引用计数 VS 可达分析:http://www.sczyh30.com/posts/Java/jvm-gc-intro-count-vs-reachability/
6. JVM学习笔记(二)——Java代码编译和执行的整个过程:http://blog.csdn.net/cutesource/article/details/5904542