面试必问之JVM原理

1:什么是JVMjava

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是经过在实际的计算机上仿真模拟各类计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操做系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就能够在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终仍是把字节码解释成具体平台上的机器指令执行。算法

2:JRE/JDK/JVM是什么关系数组

JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。全部的Java 程序都要在JRE下才能运行。普通用户只须要运行已开发好的java程序,安装JRE便可。缓存

JDK(Java Development Kit)是程序开发者用来来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也须要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程当中,JRE也是 安装的一部分。因此,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。多线程

JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。它是一个虚构出来的计算机,是经过在实际的计算机上仿真模拟各类计算机功能来实现的。JVM有本身完善的硬件架构,如处理器、堆栈、寄存器等,还具备相应的指令系统。Java语言最重要的特色就是跨平台运行。使用JVM就是为了支持与操做系统无关,实现跨平台。架构

3:JVM原理并发

JVM是java的核心和基础,在java编译器和os平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于下层的操做系统和硬件平台,能够在上面执行java的字节码程序。工具

图片描述

java编译器只要面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译成字节码程序,经过JVM将每一条指令翻译成不一样平台机器码,经过特定平台运行。性能

4:JVM的体系结构
图片描述开发工具

类装载器(ClassLoader)(用来装载.class文件)

执行引擎(执行字节码,或者执行本地方法)

运行时数据区(方法区、堆、java栈、PC寄存器、本地方法栈)

5:JVM运行时数据区

图片描述

第一块:PC寄存器

PC寄存器是用于存储每一个线程下一步将执行的JVM指令,如该方法为native的,则PC寄存器中不存储任何信息。

第二块:JVM栈

JVM栈是线程私有的,每一个线程建立的同时都会建立JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。

第三块:堆(Heap)

它是JVM用来存储对象实例以及数组值的区域,能够认为Java中全部经过new建立的对象的内存都在此分配,Heap中的对象的内存须要等待GC进行回收。
图片描述

(1) 堆是JVM中全部线程共享的,所以在其上进行对象内存的分配均须要进行加锁,这也致使了new对象的开销是比较大的

(2) Sun Hotspot JVM为了提高对象内存分配的效率,对于所建立的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的状况计算而得,在TLAB上分配对象时不须要加锁,所以JVM在给线程的对象分配内存时会尽可能的在TLAB上分配,在这种状况下JVM中分配对象内存的性能和C基本是同样高效的,但若是对象过大的话则仍然是直接使用堆空间分配

(3) TLAB仅做用于新生代的Eden Space,所以在编写Java程序时,一般多个小的对象比大的对象分配起来更加高效。

(4) 全部新建立的Object 都将会存储在新生代Yong Generation中。若是Young Generation的数据在一次或屡次GC后存活下来,那么将被转移到OldGeneration。新的Object老是建立在Eden Space。

第四块:方法区域(Method Area)

(1)在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代。

(2)方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中经过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在必定的条件下它也会被GC,当方法区域须要使用的内存超过其容许的大小时,会抛出OutOfMemory的错误信息。

第五块:运行时常量池(Runtime Constant Pool)

存放的为类中的固定的常量信息、方法和Field的引用信息等,其空间从方法区域中分配。

第六块:本地方法堆栈(Native Method Stacks)

JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每一个native方法调用的状态。

6:对象“已死”的断定算法

因为程序计数器、Java虚拟机栈、本地方法栈都是线程独享,其占用的内存也是随线程生而生、随线程结束而回收。而Java堆和方法区则不一样,线程共享,是GC的所关注的部分。

在堆中几乎存在着全部对象,GC以前须要考虑哪些对象还活着不能回收,哪些对象已经死去能够回收。

有两种算法能够断定对象是否存活:

1.)引用计数算法:给对象中添加一个引用计数器,每当一个地方应用了对象,计数器加1;当引用失效,计数器减1;当计数器为0表示该对象已死、可回收。可是它很难解决两个对象之间相互循环引用的状况。

2.)可达性分析算法:经过一系列称为“GC Roots”的对象做为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(即对象到GC Roots不可达),则证实此对象已死、可回收。Java中能够做为GC Roots的对象包括:虚拟机栈中引用的对象、本地方法栈中Native方法引用的对象、方法区静态属性引用的对象、方法区常量引用的对象。

在主流的商用程序语言(如咱们的Java)的主流实现中,都是经过可达性分析算法来断定对象是否存活的。

7:JVM垃圾回收

GC (Garbage Collection)的基本原理:将内存中再也不被使用的对象进行回收,GC中用于回收的方法称为收集器,因为GC须要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽量的缩短GC对应用形成的暂停

(1)对新生代的对象的收集称为minor GC;

(2)对旧生代的对象的收集称为Full GC;

(3)程序中主动调用System.gc()强制执行的GC为Full GC。

不一样的对象引用类型, GC会采用不一样的方法进行回收,JVM对象的引用分为了四种类型:

(1)强引用:默认状况下,对象采用的均为强引用(这个对象的实例没有其余对象引用,GC时才会被回收)

(2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的状况下才会被GC)

(3)弱引用:在GC时必定会被GC回收

(4)虚引用:因为虚引用只是用来得知对象是否被GC

8:垃圾收集算法

一、标记-清除算法

最基础的算法,分标记和清除两个阶段:首先标记处所须要回收的对象,在标记完成后统一回收全部被标记的对象。

它有两点不足:一个效率问题,标记和清除过程都效率不高;一个是空间问题,标记清除以后会产生大量不连续的内存碎片(相似于咱们电脑的磁盘碎片),空间碎片太多致使须要分配大对象时没法找到足够的连续内存而不得不提早触发另外一次垃圾回收动做。

图片描述

二、复制算法

为了解决效率问题,出现了“复制”算法,他将可用内存按容量划分为大小相等的两块,每次只须要使用其中一块。当一块内存用完了,将还存活的对象复制到另外一块上面,而后再把刚刚用完的内存空间一次清理掉。这样就解决了内存碎片问题,可是代价就是能够用内容就缩小为原来的一半。

图片描述

三、标记-整理算法

复制算法在对象存活率较高时就会进行频繁的复制操做,效率将下降。所以又有了标记-整理算法,标记过程同标记-清除算法,可是在后续步骤不是直接对对象进行清理,而是让全部存活的对象都向一侧移动,而后直接清理掉端边界之外的内存。
图片描述

四、分代收集算法

当前商业虚拟机的GC都是采用分代收集算法,这种算法并无什么新的思想,而是根据对象存活周期的不一样将堆分为:新生代和老年代,方法区称为永久代(在新的版本中已经将永久代废弃,引入了元空间的概念,永久代使用的是JVM内存而元空间直接使用物理内存)。

这样就能够根据各个年代的特色采用不一样的收集算法。

图片描述

新生代中的对象“朝生夕死”,每次GC时都会有大量对象死去,少许存活,使用复制算法。新生代又分为Eden区和Survivor区(Survivor from、Survivor to),大小比例默认为8:1:1。

老年代中的对象由于对象存活率高、没有额外空间进行分配担保,就使用标记-清除或标记-整理算法。

新产生的对象优先进去Eden区,当Eden区满了以后再使用Survivor from,当Survivor from 也满了以后就进行Minor GC(新生代GC),将Eden和Survivor from中存活的对象copy进入Survivor to,而后清空Eden和Survivor from,这个时候原来的Survivor from成了新的Survivor to,原来的Survivor to成了新的Survivor from。复制的时候,若是Survivor to 没法容纳所有存活的对象,则根据老年代的分配担保(相似于银行的贷款担保)将对象copy进去老年代,若是老年代也没法容纳,则进行Full GC(老年代GC)。

大对象直接进入老年代:JVM中有个参数配置-XX:PretenureSizeThreshold,令大于这个设置值的对象直接进入老年代,目的是为了不在Eden和Survivor区之间发生大量的内存复制。

长期存活的对象进入老年代:JVM给每一个对象定义一个对象年龄计数器,若是对象在Eden出生并通过第一次Minor GC后仍然存活,而且能被Survivor容纳,将被移入Survivor而且年龄设定为1。没熬过一次Minor GC,年龄就加1,当他的年龄到必定程度(默认为15岁,能够经过XX:MaxTenuringThreshold来设定),就会移入老年代。可是JVM并非永远要求年龄必须达到最大年龄才会晋升老年代,若是Survivor 空间中相同年龄(如年龄为x)全部对象大小的总和大于Survivor的一半,年龄大于等于x的全部对象直接进入老年代,无需等到最大年龄要求。

9:垃圾收集器

垃圾收集算法是方法论,垃圾收集器是具体实现。JVM规范对于垃圾收集器的应该如何实现没有任何规定,所以不一样的厂商、不一样版本的虚拟机所提供的垃圾收集器差异较大,这里只看HotSpot虚拟机。

JDK7/8后,HotSpot虚拟机全部收集器及组合(连线)以下:
图片描述

1.Serial收集器

Serial收集器是最基本、历史最久的收集器,曾是新生代手机的惟一选择。他是单线程的,只会使用一个CPU或一条收集线程去完成垃圾收集工做,而且它在收集的时候,必须暂停其余全部的工做线程,直到它结束,即“Stop the World”。停掉全部的用户线程,对不少应用来讲难以接受。好比你在作一件事情,被别人强制停掉,你内心奔腾而过的“羊驼”还数的过来吗?

尽管如此,它仍然是虚拟机运行在client模式下的默认新生代收集器:简单而高效(与其余收集器的单个线程相比,由于没有线程切换的开销等)。

工做示意图:
图片描述

2.ParNew收集器

ParNew收集器是Serial收集器的多线程版本,除了使用了多线程以外,其余的行为(收集算法、stop the world、对象分配规则、回收策略等)同Serial收集器同样。

是许多运行在Server模式下的JVM中首选的新生代收集器,其中一个很重还要的缘由就是除了Serial以外,只有他能和老年代的CMS收集器配合工做。

工做示意图:
图片描述

3.Parallel Scavenge收集器

新生代收集器,并行的多线程收集器。它的目标是达到一个可控的吞吐量(就是CPU运行用户代码的时间与CPU总消耗时间的比值,即 吞吐量=行用户代码的时间/[行用户代码的时间+垃圾收集时间]),这样能够高效率的利用CPU时间,尽快完成程序的运算任务,适合在后台运算而不须要太多交互的任务。

4.Serial Old收集器

Serial 收集器的老年代版本,单线程,“标记整理”算法,主要是给Client模式下的虚拟机使用。

另外还能够在Server模式下:

JDK 1.5以前的版本中雨Parallel Scavenge 收集器搭配使用

能够做为CMS的后背方案,在CMS发生Concurrent Mode Failure是使用

工做示意图:
图片描述

5.Parallel Old收集器

Parallel Scavenge的老年代版本,多线程,“标记整理”算法,JDK 1.6才出现。在此以前Parallel Scavenge只能同Serial Old搭配使用,因为Serial Old的性能较差致使Parallel Scavenge的优点发挥不出来,尴了个尬~~

Parallel Old收集器的出现,使“吞吐量优先”收集器终于有了名副其实的组合。在吞吐量和CPU敏感的场合,均可以使用Parallel Scavenge/Parallel Old组合。组合的工做示意图以下:
图片描述

6.CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,停顿时间短,用户体验就好。

基于“标记清除”算法,并发收集、低停顿,运做过程复杂,分4步:

1)初始标记:仅仅标记GC Roots能直接关联到的对象,速度快,可是须要“Stop The World”

2)并发标记:就是进行追踪引用链的过程,能够和用户线程并发执行。

3)从新标记:修正并发标记阶段因用户线程继续运行而致使标记发生变化的那部分对象的标记记录,比初始标记时间长但远比并发标记时间短,须要“Stop The World”

4)并发清除:清除标记为能够回收对象,能够和用户线程并发执行

因为整个过程耗时最长的并发标记和并发清除均可以和用户线程一块儿工做,因此整体上来看,CMS收集器的内存回收过程和用户线程是并发执行的。

工做示意图:
图片描述

CSM收集器有3个缺点:

1)对CPU资源很是敏感

并发收集虽然不会暂停用户线程,但由于占用一部分CPU资源,仍是会致使应用程序变慢,总吞吐量下降。

CMS的默认收集线程数量是=(CPU数量+3)/4;当CPU数量多于4个,收集线程占用的CPU资源多于25%,对用户程序影响可能较大;不足4个时,影响更大,可能没法接受。

2)没法处理浮动垃圾(在并发清除时,用户线程新产生的垃圾叫浮动垃圾),可能出现"Concurrent Mode Failure"失败。

并发清除时须要预留必定的内存空间,不能像其余收集器在老年代几乎填满再进行收集;若是CMS预留内存空间没法知足程序须要,就会出现一次"Concurrent Mode Failure"失败;这时JVM启用后备预案:临时启用Serail Old收集器,而致使另外一次Full GC的产生;

3)产生大量内存碎片:CMS基于"标记-清除"算法,清除后不进行压缩操做产生大量不连续的内存碎片,这样会致使分配大内存对象时,没法找到足够的连续内存,从而须要提早触发另外一次Full GC动做。

7.G1收集器

G1(Garbage-First)是JDK7-u4才正式推出商用的收集器。G1是面向服务端应用的垃圾收集器。它的使命是将来能够替换掉CMS收集器。

G1收集器特性:

并行与并发:能充分利用多CPU、多核环境的硬件优点,缩短停顿时间;能和用户线程并发执行。

分代收集:G1能够不须要其余GC收集器的配合就能独立管理整个堆,采用不一样的方式处理新生对象和已经存活一段时间的对象。

空间整合:总体上看采用标记整理算法,局部看采用复制算法(两个Region之间),不会有内存碎片,不会由于大对象找不到足够的连续空间而提早触发GC,这点优于CMS收集器。

可预测的停顿:除了追求低停顿还能创建能够预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片断内,消耗在垃圾收集上的时间不超N毫秒,这点优于CMS收集器。

为何能作到可预测的停顿?

是由于能够有计划的避免在整个Java堆中进行全区域的垃圾收集。

G1收集器将内存分大小相等的独立区域(Region),新生代和老年代概念保留,可是已经再也不物理隔离。

G1跟踪各个Region得到其收集价值大小,在后台维护一个优先列表;

每次根据容许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来);

这就保证了在有限的时间内能够获取尽量高的收集效率。

对象被其余Region的对象引用了怎么办?

判断对象存活时,是否须要扫描整个Java堆才能保证准确?在其余的分代收集器,也存在这样的问题(而G1更突出):新生代回收的时候不得不扫描老年代?不管G1仍是其余分代收集器,JVM都是使用Remembered Set来避免全局扫描:每一个Region都有一个对应的Remembered Set;每次Reference类型数据写操做时,都会产生一个Write Barrier 暂时中断操做;而后检查将要写入的引用指向的对象是否和该Reference类型数据在不一样的 Region(其余收集器:检查老年代对象是否引用了新生代对象);若是不一样,经过CardTable把相关引用信息记录到引用指向对象的所在Region对应的Remembered Set中;进行垃圾收集时,在GC根节点的枚举范围加入 Remembered Set ,就能够保证不进行全局扫描,也不会有遗漏。

不计算维护Remembered Set的操做,回收过程能够分为4个步骤(与CMS较为类似):

1)初始标记:仅仅标记GC Roots能直接关联到的对象,并修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时能在正确可用的Region中建立新对象,须要“Stop The World”

2)并发标记:从GC Roots开始进行可达性分析,找出存活对象,耗时长,可与用户线程并发执行

3)最终标记:修正并发标记阶段因用户线程继续运行而致使标记发生变化的那部分对象的标记记录。并发标记时虚拟机将对象变化记录在线程Remember Set Logs里面,最终标记阶段将Remember Set Logs整合到Remember Set中,比初始标记时间长但远比并发标记时间短,须要“Stop The World”

4)筛选回收:首先对各个Region的回收价值和成本进行排序,而后根据用户指望的GC停顿时间来定制回收计划,最后按计划回收一些价值高的Region中垃圾对象。回收时采用复制算法,从一个或多个Region复制存活对象到堆上的另外一个空的Region,而且在此过程当中压缩和释放内存;能够并发进行,下降停顿时间,并增长吞吐量。

工做示意图:
图片描述

10:基本结构

从Java平台的逻辑结构上来看,咱们能够从下图来了解JVM:

图片描述

从上图能清晰看到Java平台包含的各个逻辑模块,也能了解到JDK与JRE的区别。

相关文章
相关标签/搜索