Solo  当前访客:0 开始使用

Carson

记录精彩的程序人生

03深入浅出JVM 有更新!

2019-04-23 08:42:13 dulinanaaa
0  评论    146  浏览

需要了解的

  1. JVM内存模型

需要了解方法区、堆、PC寄存器、栈、本地方法栈的作用,保存着哪些数据

  1. 类加载
    双亲委派的加载机制
    常用的类加载器,以及它们都各自加载哪种类型的类

  2. GC部分
    分代回收的思想和依据
    不同拉圾回收算法的实现思路和适合场景

  3. 性能调优
    常用JVM优化参数的作用
    参数调优的依据
    常用的JVM分析工具能分析哪类的问题、及使用方法

  4. 执行模式
    解释、编译、混合模式的优缺点
    Java7提供的分层编译技术
    JIT的即时编译技术
    OSR栈上替换
    C1、C2两个编译器针对的场景:C2针对的是Server模式,优化更激进

  5. 新技术
    Java10提供的由Java提供的gravel编译器

  6. 编译优化
    前端编译器Javac的编译过程
    ast抽象语法树
    编译器优化和运行器优化
    常用技术:公共子表达式的消除、方法的内联、逃逸分析、栈上分配、同步消除等

JVM内存模型

指运行时的数据区,包括5个部分

  1. 栈:又称方法栈,线程私有的,线程在执行每个方法时都会同时创建一个栈帧,用来存储局部变量表、操作栈、动态链接、方法出口等信息。调用方法时时执行入栈,方法返回时时执行出栈。
  2. 本地方法栈:与栈类似,也是用来保存线程执行方法时的信息。不同的是,执行Java方法时使用栈,而执行Native方法时使用本地方法栈。
  3. PC寄存器:保存着当前线程所执行的字节码位置,每个线程工作时都有一个独立的计数器。程序计数器(PC寄存器)只为执行Java方法服务。执行Native方法时,程序计数器为空.
  4. 堆:JVM内存管理中最大的一块。堆被所有的线程共享,目的是为了存放对象的实例,几乎所有的对象实例都会放在这里。当堆内存没有可用的空间时,会抛出OOM异常。根据对象存活的周期不同,JVM把堆内存进行分代管理,由垃圾回收器来进行对象的回收管理。
  5. 方法区:也是各个线程共享的内存区,又叫非堆区。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器优化后的代码等数据

    JDK1.7中的永久代和1.8中的metaspace都是方法区的一种实现。
    面试回答这个问题时,有两个要点:
    1.各部分的功能
    2.哪些是线程共享的,哪些是线程独占的。

JMM与内存可见性

03-2.jpg

JMM是Java内存模型与刚才的JVM内存模型是两回事
JMM的主要目标是定义程序中变量的访问规则。

如图所示,所有的共享变量都存储在主内存中共享。每个线程有自己的工作内存。工作内存中保存的是主内存中变量的副本。线程对变量的读写等操作必须在自己的工作内存中进行,而不能直接读写主内存中的变量。

在多线程进行数据交到时,例如线程A给一个共享变量赋值后由线程B来读取这个值。A修改完变量是修改在自己的工作内存区中,B是不可见的。只有从A的工作内存区写回到主内存,B再从主内存读取到自己的工作区才能进行进一步的操作。由于指令重排序的存在,这个写、读的顺序有可能会被打乱。因此JMM需要提供原子性、可见性、有序性的保证。

JMM原子性、可见性、有序性的保证

03-3.jpg

原子性:

  • 对除long、double外的基本数据类型,它的读写操作是原子性的
  • 关键字synchronized也可以提供原子性保证。它是通过Java的两个高级字集码指令来保证的

可见性:
* synchronized
* volatile:强制变量的赋值会同步刷新回主内存,强制变量的读取会从主内存中重新加载,保证不同的线程总是能看到该变量的最新值

有序性:
* volatile:阻止指令重排序。这个就可以保证变量读写的有序性
* 一系列happens-before原则:包括一系列规则

比如:

程序顺序原则:就是一个线程内必须保证语义串行性

锁规则:就是对同一把锁的解锁一定要发生在再次加锁之前

此外,还包括happens-before原则的传递性,线和的启动,中断、终止规则等。

类的加载与卸载

03-4.jpg

类的加载是指将编译好的Class文件中字节码读入到内存中,将其放入方法区内,并创建对应的Class对象。

类的加载分为加载、链接、初始化
其中链接又分为验证、准备、解析3步

  • 加载:文件到内存的过程
    通过类的完全限定名,查找此类字节码文件,利用字节码文件创建Class对象
  • 验证:对文件类内容的验证
    目地确保Class文件符合当前虚拟机的要求,不会危害到虚拟机自身安全

    主要包括四种:
    文件格式的验证、元数据的验证、字节码的验证、符号引用的验证

  • 准备:是进行内存分配

    为类变量,也就是由类中由static修饰的变量分配内存,并设置初始值。
    这里要注意:
    1. 初始值是0或null,而不是代码中设置的具体值。代码中设置的值在初始化阶段完成。
    1. 另外这里也不包括由final修饰的静态变量,因为final变量在编译时就已经分配了

  • 解析:主要是解析字段、接口、方法,主要是将常量池中的符号引用替换为直接引用的过程

    直接引用:就是直接指向目标的指针或者相对偏移量等

  • 初始化:主要完成静态块的执行与静态变量的赋值,这是类加载最后阶段。若被加载类的父类没有初始化,则先对父类进行初始化。只有对类的主动使用时,才会进行初始化。

初始化的触发条件包括:
1. 创建类的实例的时候、
1. 访问类的静态方法或静态变量的时候,
1. 使用class.forname反射类的时候,
1. 或者某个子类被初始化的时候

类的生命周期:类的加载、类的实例的创建与使用、类对象不再被使用可以被GC回收

由Java虚拟机自带的三种类加载器加载的类在虚拟机的整个生命周期中是不会被卸载的。只有用户自定义的类加载器所加载的类才可以被卸载

双亲委派模式

03-5.jpg

Java自带的三种类加载器:
1. bootstrap 启动类加载器
1. 扩展类加载器
1. 应用加载器(系统加载器)

图中橘黄色是对应的加载器所加载的目录

双亲委派模式:即一个类加载器在加载类时,先把这个请求委托给自己的父类加载器去执行。如果父类加载器还存在父类加载器,就继续向上委托,直到顶层的启动类加载器。(如图中蓝色向上的箭头)
如果父类加载器能够完成类的加载,就成功返回。如果父类加载器无法完成加载,那么子类加载器才会尝试自己去加载(如图中黄色向下的箭头)

双亲委派模式的好处:
1. 避免类的重复加载
1. 避免Java的核心API被篡改

Java的堆内存的分代管理

分代管理主要是为了方便垃圾回收,这样做基于两个事实
1. 大部分对象很快就不使用了
2. 还有一部分不会立即无用,但也不会持续很长时间

Java的堆内存的分代回收

虚拟机中的堆内存划分为年轻代、老年代和永久代

03-6.jpg

年轻代

主要用来存放新创建的对象
年经代分为Eden区和两个Survivor区
大部分对象在Eden区中生成
当Eden区满时,还存活的对象会在两个Survivor区中交替保存。达到一定次数后,对象会晋升到老年代

老年代

用来存放由年轻代晋升而来的存活时间较长的对象

永久代

主要保存类信息等内容。这里的永久代是指对象划分方式,不是专指1.7的permanent generation和1.8之后的matespace。

垃圾回收算法

根据年轻代与老年代的特点,JVM提供了不同的垃圾回收算法。

垃圾回收算法按类型分为:引入计数法、复制法、标记清除法几种。

引入计数法是通过对象被引用的次数来确定对象是否还在被使用。缺点是无法解决循环引用的问题。

复制算法需要from和to两块大小相同的内存空间,对象分配时只在from块中进行,回收时把存活对象复制到to块中,并清空from块。然后交换两块的分工,把from块作为to块,把to块作为from块。缺点是内存使用率较低

标记清除算法分为标记对象和清除不再使用的对象两个阶段。标记清除算法的缺点是会产生内存碎片。

JVM中提供的年轻代回收算法Serial、ParNew和Parallel Scavenge都是复制算法,而CMS、G1和ZGC都属于标记清除算法

CMS算法

jdk1.7以前最主要的垃圾回收算法。为标记清除算法,优点是并发收集、停顿小

03-7.jpg

回收过程:

初始标记会StopTheWorld,标记的对象只是从root及最直接可达的对象.

并发标记这时GC线程和应用线程并发执行,标记可达的对象

重新标记第二个StopTheWorld,停顿时间比并发标记小很多,但比初始标记稍长.主要对对象重新扫描并标记

并发清理进行并发的垃圾清理

并发重置为下一次GC重置相关数据结构

G1算法

在jdk1.9后成为JVM的默认垃圾回收算法。
特点是保持高回收率的同时减少了停顿。

取消了堆中年轻代与老年代的物理划分,但它仍然是分代回收器。G1算法将堆分为若干个区域,成为region,如图中的小方格所示。一部分区域用作年轻代,一部分用作老年代,还有一种专门用作存储巨型对象的分区。

G1和GMS一样,会遍历全部对象,然后标记对象引用情况。在清除对象后会对区域进行复制移动,整合碎片空间

03-8.jpg

回收过程:

  • 年轻代回收

    年轻代回收采用复制算法,并行进行收集,收集过程会StopTheWorld

  • 老年代回收

    同时也会对年轻代进行回收
    分为4个阶段:
    1. 初始标记完成对根对象的标记,会StopTheWorld
    1. 并发标记和用户线程并行执行的
    1. 最终标记完成三色标记周期,会StopTheWorld
    1. 复制/清除会优先对可回收空间较大的Region进行回收,即garbage first

    G1采用每次只清理一部分而不是全部Region的增量式清理,由此来保证每次GC停顿时间不会过长
    

总结:
* G1是逻辑分代而不是物理分代
* 需要知道回收的过程和停顿的阶段
* 允许通过JVM参数设置Region的大小,范围是1-32M,还可以设置期望的最大GC停顿时间

ZGC 针对大内存堆的低延迟垃圾回收算法

最新的jdk1.11中提供的高效垃圾回收算法,
针对大堆内存设计可以支持TB级别的堆。
非常高效,可以做到10ms以下的回收停顿时间

使用了着色指针技术,ZGC限制支持最大4TB的堆
64位(电脑)的寻指只需42位,剩下22位就可以用来保存额外的信息
* 着色指针

着色指针技术,就是利用指针的额外信息位,在指针上对对象进行着色标记
  • 读屏障

    ZGC使用读屏障来解决GC线程和应用线程可能并发修改对象状态的问题,而不是简单粗暴的通过stoptheworld来做全局的锁定。

  • 并发处理

    使用读屏障只会在对单个对象的处理上有概率被减速。由于读屏障的使用,进行垃圾回收的大部分的时候,都是不需要stoptheworld。因此ZGC的大部分时间都是并发处理

  • 基于Region

    这与G1算法一样,不过虽然也分了Region,但是并没有进行分代。ZGC的Region不像G1那样是固定大小,而是动态决定Region的大小。Region可以动态创建和销毁,这样可以更好的对大对象进行分配和管理。

  • 压缩整理

    CMS算法清理对象时原地回收,会存在内存碎片问题。ZGC和G1一样,也会在回收后,对Region中的对象进行移动合并,解决了碎片问题。虽然ZGC的大部分时间是并发进行,但还是会有短暂的停顿。

回收过程:

03-9.jpg

图片是按照ZGC的回收时序绘制的。(从上往下看)

初始状态时,整个堆空间被划分为大小不等的许多Region。(即图中绿色的方块)

开始进行回收时,ZGC首先会进行一个短暂的stopTheWorld,来进行Roots根对象的标记。这个步骤非常短,因为Roots的总数量通常比较小。

然后开始进行并发标记,如图,通过对对象指针进行着色来进行标记。结合读屏障来解决单个对象的并发问题,其实这个阶段在最后的时候,还会有一个非常短的stopTheWorld的时间停顿,用来处理一些边缘情况。这个阶段在绝大部分时间都是并发进行的,所以没有明显标识出这个停顿。

下一个阶段是清理阶段。这个阶段会把标记为不可用的对象进行回收。如图,把橘色的不再使用的对象进行了回收。

最后一个阶段是重定位。重定位就是对GC后存活的对象进行移动,来腾出大块的内存空间解决碎片问题。在重定位最开始,会有一个短暂的stopTheWorld,用来重定义集合中的Root对象。暂停时间取决于Root的数量和重定位及对象的总活动集的比率。

最后是并发重定位,这个过程也是通过读屏障与应用线程并发进行的。

考查点

  1. 深入理解JVM内存模型和Java的内存模型(JMM)
  2. 了解类加载过程、双亲委派机制
  3. 了解内存可见性与Java内存模型对原子性、可见性、有序性的保证机制
  4. 了解常用的GC算法的特点、执行过程和适用场景

    例如:G1适合对最大延迟有要求的场合;ZGC适用于64位系统的大内存服务中

  5. 能够理解常用的JVM参数,明白对不同参数的调整会有怎样的影响、适用于什么样的场景

    例如:垃圾回收的并发数、偏向锁的设置等

加分项

  1. 编译器优化

    比如:知道在编程时如何利用栈上分配降低GC压力

  2. 问题排查经验与思路

    比如:线上经常Full GC的问题,排查过内存泄露的问题

  3. JVM调优经验和调优思路(特定场景的)

    比如:针对高并发、低延迟的场景如何调整GC参数尽量降低停顿时间。针对队列处理及如何尽可能提高吞吐率等

  4. 了解最新的技术趋势(ZGC和Graalvm)

真题汇总

  1. 简单描述一下JVM的内存模型

    记得问JVM的内存模型还是Java对内存访问的模型,别答跑偏了

  2. 什么时候会触发FullGC?

    什么场景下会触发FullGC例如年轻代在晋升时老年代空间不足。永久代空间不足等

  3. Java类加载器有几种,关系怎样的?
  4. 双请问欧派机制的加载流程是谮言的,有什么好处?
  5. 1.8为首么用Metaspace替换掉PermGen?Meatspace保存在哪?
  6. 编译器会对指令做哪些优化?(简答描述编译器的指令重排)
  7. 简单描述一下volatile可以解决什么问题?如何做到的?

    要回答强制主内存读写同步及防止指令重排序

  8. 简单描述一下GC的分代回收?
  9. G1与CMS的区别?
  10. 对象引用有哪几种,有什么特点?

    要重点介绍强、弱、软、虚4种引用,以及在GC中的处理方式

  11. 使用过哪些JVM调试工具,主要分析哪些内容?

    可以了解Java自带的工具的功能,例如JMC中飞行记录器。堆分析工具MAT。线程分析工具JStack和获取堆信息的JMAP

TOP