JVM 面试题总结

1.java优点

  • 摆脱硬件平台的束缚,实现了一次编写,到处运行的理想
  • 它提供了一个相对安全的内存管理和访问机制,避免了大部分的内存泄漏和指针越界问题
  • 它实现了热点代码检测和运行时编译及优化,这使得java应用能随着运行时间的增加而获得更高的性能
  • 它有一套完善的应用程序接口,还有无数来自商业机构和开源社区的第三方类库来帮助它实现各种各样的功能

2.sun官方定义的java技术体系包括那几个组成部分

  • java程序设计语言
  • 各种硬件平台上的java虚拟机
  • class文件格式
  • java api类库
  • 第三方类库
  • 总结:java程序设计语言和java虚拟机,java api类库这三部分统称为JDK,jdk是用于支持java程序开发最小环境。
  • 总结:java api类库中的java se api子集和java虚拟机这两部分成为jre,jre是支持java程序运行的标准环境。

3.java按照技术所服务的领域来划分,可以分为4个平台

  • java card:支持一些java小程序运行在小内存设备
  • java ME:支持java程序运行在移动终端
  • java SE:支持面向桌面级应用
  • java EE:支持使用多层架构的企业应用

4.运行时数据区域

4.1程序计数器

  • 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
  • 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器
  • java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为线程私有内存
  • 如果java在执行一个java方法,计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行native方法,这个计数器值为空
  • 程序计数器是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域

4.2java虚拟机栈

  • java虚拟机栈也是线程私有的,它的生命周期与线程相同
  • 虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。

4.2.1每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

  • 局部变量表中存放了编译期可知的各种基本数据类型,对象引用和returnAddress类型(指向了一条字节码指令的地址)
  • 64位long和double类型数据会占用2个局部变量空间,其余的数据类型只占用1个
  • 局部变量表和所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小
  • 如果线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常
  • 如果虚拟机栈可以动态扩展,扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常.

4.3本地方法栈

  • 本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行java方法服务,而本地方法栈则为虚拟机使用到的native方法服务
  • 与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常

4.4java堆

  • java堆是被所有线程共享的一块内存区域
  • 在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例
  • java虚拟机规范中描述:所有的对象实例以及数组都要在堆上分配
  • java堆是垃圾收集器管理的主要区域
  • 从内存回收角度看,java堆细分为:新生代和老年代,再细致一点有,Eden空间,From Survivor空间,To Survivor空间.
  • 进一步划分的目的是为了更好的回收内存,或者更快地分配内存
  • 如果堆上没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常

4.5方法区

  • 方法区与java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
  • 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常.

5.运行时常量池

  • 运行时常量池是方法区的一部分
  • class文件中除了有类的版本,字段,方法接口等描述信息外,还有一项是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存入
  • 方法区的运行时常量池存放
  • 常量池不要求常量一定要在编译期产生,运行期间也可能将新的常量放入池中,比如string的intern()方法
  • 当常量池无法再申请到内存时会抛出OutOfMemoryError异常

6.直接内存

  • 直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,但是这部分内存也被频繁的使用
  • jdk1.4引入Nio(new input/output),引入了一种基于通道(channel)和缓存区(Buffer)的I/O方式,它可以使用Native函数直接分配堆外内存,
  • 然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样能在一些场景中显著提高性能,因为避免了在java堆和native堆来回复制数据
  • 本机直接内存的分配不会受到java堆大小的限制,但是既然是内存肯定还是收到本机总内存的大小的以及处理器寻址空间的限制。
  • 各个内存区域总和大于物理内存限制时,从而导致动态扩展时出现OutOfMemoryError异常

7.对象创建过程

  • 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号的引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过,
  • 如果没有,则必须先执行相应的类加载过程。
  • 在类加载检查通过后,为新生对象分配内存,对象所需内存在类加载完成后,便可确定
  • 如果堆内存是绝对规整的,那分配内存就仅仅是将指针向空闲空间移动一段与对象大小相对的距离,这种分配方式称为,指针碰撞
  • 如果堆内存不是规整的,虚拟机就必须维护一个列表,记录那些内存块是可用的,在分配的时候从列表中找到一块足够大的内存划分给对象实例,并更新列表中的记录,分配方式为空闲列表
  • 使用那种分配方式由堆内存是否规整决定,java堆是否规整,由所采用的垃圾收集器是否带有压缩整理功能决定。
  • serial parNew等带compact过程的收集器采用指针碰撞
  • cms这种基于mark-sweep算法的收集器,通常采用空闲列表
  • 指针碰撞并发情况下不是线程安全的,采用cas和失败重试的方式保证更新操作的原子性。另一种是把内存分配的动作按照线程划分在不同的空间之中进行 TLAB
  • 虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例,如何找到类的元数据信息,对象的哈希吗,对象的gc分代年龄。存到对象的对象头中

8.对象的内存布局

  • 在hotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头,实例数据和对齐填充
  • 对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码,gc分代年龄,锁状态标志,线程持有的锁,偏向线程id,偏向时间戳
  • 对象头另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
  • 如果对象是一个数组,对象头中还必须有一块用于记录数组长度的数据
  • 实例数据是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。这部分的存储顺序会受到虚拟机分配策略的影响,
  • 对齐填充不是必然存在的,仅仅起着占位符的作用,由于hotspot vm的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节整数倍

9.对象的访问定位

  • java程序需要通过栈上的reference数据来操作堆上的具体对象。
  • 对象访问方式也是取决于虚拟机实现而定的,目前主流的访问方式有使用句柄,和直接指针两种。
  • 如果使用句柄的话,那么java堆中将会划分一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息
  • 直接指针访问,java堆对象的布局就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址
  • 句柄访问的好处是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference对象不改变
  • 使用直接指针访问的方式好处是速度更快。节省了一次定位开销

10.jvm参数设置含义

  • 类似-Xms、-Xmn这些参数的含义:

10.1堆内存分配:

  • JVM初始分配的内存由-Xms指定,默认是物理内存的1/64
  • JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4
  • 默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。
  • 因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。
  • -Xmn2G:设置年轻代大小为2G。
  • -XX:SurvivorRatio,设置年轻代中Eden区与Survivor区的比值。

10.2非堆内存分配:

  • JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;
  • 由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。

11.怎么处理OutOfMemoryError异常

  • 先通过内存映像分析工具对dump出来的堆转储快照进行分析
  • 重点是确认内存中的对象是否是必要的,也就是先分清楚是出现了内存泄漏还是内存溢出
  • 如果是内存泄漏,进一步通过工具查看泄漏对象到gc roots的引用链,找到泄漏对象是通过怎样的路径与gc roots相关联导致无法自动回收
  • 如果不存在泄漏,检查虚拟机堆参数与机器物理内存对比看是否还可以调大,尝试减少程序运行期的内存消耗

12.gc的垃圾收集器三个设计重点

  • 那些内存需要回收
  • 什么时候回收
  • 如何回收

13.为什么gc不考虑程序计数器,虚拟机栈,本地方法栈,而是针对java堆和方法区

  • 因为程序计数器,虚拟机栈,本地方法栈三个区域随线程而生,随线程而灭
  • 每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,可以认为是编译期可知的
  • 因此这几个区域的内存分配和回收都具备确定性
  • java堆和方法区则,一个接口需要的内存需要在运行时知道,编译期间是未知的。

14.引用计数法

  • 含义:给对象中添加一个引用计数器,每当有一个地方引用的时候,计数器值加1,当引用失效的时候,减1,任何时刻计数器为0的对象就是不可能被访问的
  • java为什么不采用该算法:很难解决循环引用的问题

15.可达性分析算法

  • 含义:通过一系列的的称为gc roots的对象作为起始点,从这些节点开始向下搜索,搜素所走过的路径称为引用链,当一个对象到gc roots没有任何引用链相连则证明不可用

16.gc roots对象有那些

  • 虚拟机栈中(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的native方法)引用的对象

17.为什么要将引用区分为强引用,软引用,弱引用,虚引用

  • 为了处理一些当内存空间还足够时,则能保留在内存之中,如果内存空间在进行垃圾收集之后还是非常紧张,则可以抛弃这些对象,很多系统的缓存功能都符合这样的场景

18.强引用,软引用,弱引用,虚引用含义

  • 强引用:只要强引用还存在,垃圾收集器永远不回收被引用的对象
  • 软引用:软引用描述一些还有用,但并非必须的对象,在系统将要发生内存溢出异常之前,将会将这些对象列进回收范围之中进行第二次回收,softRefernece类实现
  • 弱引用:也是描述非必须对象,强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集之前,无论当前内存是否足够,垃圾收集器都会回收只被弱引用引用的对象,weakRefernece类实现
  • 虚引用:称为幻影引用,一个对象是否有虚引用,完全不会对其生存时间构成影响,唯一目的:在对象被回收时收到一个系统通知,PhantomReference类实现

19.回收对象的过程

  • 回收一个对象,至少要经历两次标记过程
  • 如果对象在可达性分析之后发现没有gc roots相连接的引用链,那它将会被第一次标记,并且进行一次筛选
  • 筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,则视为没有必要执行
  • 如果判定为有必要执行,则这个对象会放置在一个F-Queue队列之中,并在稍后由一个finalize线程执行它的finalize()方法,线程触发该方法,但不等结束,以防执行过慢
  • 如果对象在finalize()方法执行期间重新与引用链上的任何一个对象建立关联,则在第二次标记过程中移除出即将回收的集合
  • 将F-Quence中未逃脱的对象回收
  • 注意:任何一个对象的finalize()方法只会被虚拟机调用一次

20.方法区回收

  • 在堆中,新生代中进行一次回收,一般回收70%-90%的空间,而永久代的垃圾回收效率远低于此
  • 永久代主要回收废弃常量和无用的类
  • 常量清理:常量池中的字符串如果没有被其他地方引用,会被系统清理出常量池,其他类接口,方法,字段的符号清理同理
  • 在大量使用反射,动态代理,cglib,动态生成jsp,以及频繁自定义classLoader的场景需要虚拟机具备类卸载功能,保证永久代不会溢出

20.1无用的类判定:

  • 该类所有的实例都已经被回收
  • 加载该类的classLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

21.标记-清除算法

  • 含义:算法分为标记和清除两部分,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
  • 不足:一个是效率问题,标记和清除两个过程效率不高
  • 空间问题:标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作

22.复制算法

  • 含义:将可用内存按容量划分为大小相等的两块,每次只使用其中一块,当这一块内存使用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉,这样每次都对整个半区回收,无碎片
  • 不足:算法代价大,将内存缩小为了原来的一半
  • 新生代采用复制算法回收,采用eden s0 s1 三个区域,每次使用eden和s的其中一个区域,清理时,将可用对象移动到另一块没使用的s区上,然后清理eden和s区,8:1:1,只浪费10%空间
  • 不能保证每次回收都只有10%的对象存活,所以需要老年代进行分配担保,如果大于10%的部分,分配到老年代。

23.标记-整理算法

  • 含义:标记过程与标记清除算法一样,让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存
  • 使用场景:老年代。

24.分代收集算法

  • 根据对象存活周期的不同将内存划分为几块,一般将堆分为新生代和老年代。
  • 新生代,每次垃圾收集时都发现有大量对象死去,只有少量存活,就选用复制算法,付出少量存活对象的复制成本完成收集
  • 老年代,对象存活率高,没有额外空间对它进行担保,使用标记-清理,或者标记-整理算法进行回收

25.为什么gc要stop the world

  • 可达性分析要从gc roots节点找引用链,gc roots节点主要在全局性的引用(常量或类静态属性)与执行上下文,逐个检查,消耗时间长
  • 可达性分析必须在一个能确保一致性的快照中进行
  • 一致性是说在整个分析期间整个执行系统看起来就像被冻结在某个时间上,不可以出现分析过程中对象引用关系还在不断变化,如果变化,分析结果准确性无法保证

26.gc怎么快速枚举节点

  • 虚拟机将对象引用存放在OopMap的数据结构中,在类加载完成的时候,Hotspot就把对象内什么偏移量上是什么类型的数据计算出来
  • 在JIT编译过程中,在特定位置记录下栈和寄存器中那些位置是引用,这样gc扫描就可以直接扫描OopMap
  • 特定位置是指安全点,即程序执行时并非在所有地方都能停顿下来gc,只有在到达安全点时才能暂停。

27.安全点的选定基本原则

  • 以是否具有让程序长时间执行的特征为标准进行选定
  • 长时间的明显特征是指令序列复用
  • 例如方法调用,循环跳转,异常跳转,所以具有这些功能的指令才会产生safePoint

28.gc发生时,怎么让线程都跑到安全点上再停顿下来

  • 抢先式中断:在gc发生时,首先把所有线程中断,如果发现有中断的地方不是安全点,就恢复线程,让它跑到安全点上。(几乎没有虚拟机选择这种)
  • 主动式中断:gc需要中断线程时不直接操作,在安全点设置一个轮询标志的动作,另外在创建对象分配内存的地方加上轮询标志动作
  • 各个线程在安全点主动去轮询标志,如果标志为真时,则主动中断挂起。
  • 轮询标志是虚拟机通过把0x160100的内存页设置为不可读,线程执行到test指令时就会产生一个自陷异常信息,在预先注册的异常处理器中暂停线程实现等待
  • 这样一条汇编指令便完成了安全点轮询和触发线程中断

29.安全区域

  • 安全点解决了,程序在执行时,在不太长的时间内就会遇到可进入GC的safepoint,但是有些程序是sleep状态,或者blocker状态,jvm显然不会等待线程被重新分配cpu时间。这个时候需要安全区域
  • 安全区域是指在一段代码片段中,引用关系不会发生变化,在这个区域的任意地方开始gc都是安全的
  • 在线程执行到safe region中的代码时,首先标识自己已经进入了safe region,当在这段时间内jvm要发起gc时,就不管标识自己为safe region状态的线程了
  • 在线程要离开safe region时,它要检查系统是否已经完成了根节点枚举(或者整个gc过程),如果完成了,那线程继续执行,否则它就必须等待直到收到可以安全离开saferegion的信号为止

30.垃圾收集器使用场景

  • 年轻代收集器:Serial,ParNew,Parallel Scavenge
  • 老年代收集器:CMS,Serial Old(MSC),Parallel Old
  • 年轻代和老年代都可以使用:G1

31.各个收集器工作原理

31.1Serial收集器:

  • 含义:单线程收集器,在进行垃圾收集时,必须暂停其他所有的工作线程,直到收集结束
  • 工作过程:在安全点暂停所有用户线程,新生代采用复制算法,老年代CMS采用标记整理算法,暂停所有用户线程
  • 优点:简单高效,在单个CPU环境,没有线程交互的开销,效率高
  • 缺点:停顿时间长,必须暂停所有其他用户线程

31.2ParNew收集器:

  • 含义:Serial收集器的多线程版本,多条gc线程一起工作,同样需要暂停所有用户线程
  • 工作过程:在安全点暂停所有用户线程,多个gc线程在新生代采用复制算法,默认线程数与CPU数量相等
  • 优点:虽然暂停所有用户线程,但是gc线程可以有多个,能与CMS老年代收集器配合工作
  • 缺点:必须暂停所有其他用户线程,单CPU情况下,线程切换开销大

31.3Parallel Scavenge收集器

  • 含义:新生代收集器,采用复制算法,并行的多线程收集器,自适应调节策略,吞吐量优先收集器
  • 工作过程:可以设置最大垃圾收集停顿时间,吞吐量大小(默认值是99,就是允许1%的收集时间)
  • 打开-XX:UseAdaptiveSizePolicy则不用指定新生代大小,Eden,s区比例,晋升老年代对象大小,虚拟机会自动调节

31.4Serial Old收集器

  • 含义:Serial收集器老年代版本,单线程收集器,标记整理算法
  • 工作过程:在安全点暂停所有用户线程,单gc线程采用标记整理算法进行垃圾回收

31.5Parallel Old收集器

  • 含义:Parallel Old是Parallel Scavenge收集器老年代版本,使用多线程和标记整理算法
  • 工作过程:吞吐量优先,在安全点暂停所有用户线程,多个gc线程开始进行垃圾回收
  • 使用场景:注重吞吐量以及CPU资源敏感,可以使用Parallel Scavenge加Parallel Old收集器

31.6CMS收集器

  • 含义:Concurrent Mark Sweep 是一种以获取最短回收停顿时间为目标的收集器,采用标记清除算法,并发低停顿收集器
  • 工作过程:
  • 1.初始标记:单线程标记一下gc roots能直接关联到的对象,速度快,需要暂停所有用户线程2.并发标记:单线程进行 gc roots tracing过程,可以与用户线程一起工作
  • 3.重新标记:多线程修改并发标记期间因用户程序继续运行而导致标记产生变动的一部分对象的标记记录
  • 4.并发清除:单线程工作,可以和用户线程一起工作
  • 使用场景:希望系统停顿时间最短
  • 优点:时间最长的并发标记和并发清除都可以与用户线程一起工作
  • 缺点:
  • 1.CMS收集器对CPU资源非常敏感,在并发阶段,虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢
  • 2.CMS收集器无法处理浮动垃圾,由于并发清理阶段用户线程还在运行,这一部分出现的垃圾称为浮动垃圾,只能等下次标记回收,因此CMS收集器不能像其他几乎完全被填满了,再进行收集,需要预留一部分空间提供并发收集时的程序运作使用,默认1.6jdk预留92%,如果cms运行期间内存无法满足程序需要,jvm启动备选方案,启用serial old收集器进行收集
  • 3.采用标记-清除算法,有大量空间碎片产生,给大对象分配连续内存时,容易提前触发full gc
  • 4.+UseCMSCompactAtFullCollection默认开启,用于cms要进行full gc时,开启内存碎片合并整理,单线程整理,停顿时间变长
  • 5.-XX:CMSFullGCsBeforeCompaction设置执行多少次不压缩的full gc后,跟着来一次带压缩的。默认值0,每次full gc都整理

31.7G1收集器

  • 特点:
  • 1.并行与并发:g1能充分利用多cpu,多核环境下的硬件优势,缩短stw时间,g1收集器可以多gc线程与用户线程一起执行
  • 2.分代收集:独立管理整个堆,但它能够采用不同方式,处理新创建的对象和以及存活了一段时间,熬过多次gc的旧对象
  • 3.空间整合:g1从整体上看是标记-整理算法实现收集器,从局部region上看是基于复制算法,gc期间不会产生内存空间碎片
  • 4.可预测的停顿:让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒
  • 含义:
  • 1.java堆内存布局划分为多个大小相等的独立区域Region,新生代和老年代不再是物理隔离,它们都是一部分region的集合
  • 2.g1可以有计划地避免在整个java堆中进行全区域的垃圾收集,通过跟踪各个region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的region,这种使用region划分内存空间,以及有优先级的区域回收方式,保证了获取尽可能高的收集效率
  • 3.region之间的对象引用,jvm通过使用remembered set来避免全堆扫描,g1中每个region都有一个与之对应的remembered set,虚拟机发现有程序在对reference引用
  • 类型数据进行写操作时,会产生一个write barrier暂时中断写操作,检查reference引用的对象是否处于不同region之中,如果是,便通过cardTable把相关引用信息
  • 记录到被引用对象所属的region的remembered set之中,当进行内存回收时,在gc根节点的枚举范围中加入remembered set即可保证不对全堆扫描也不会有遗漏
  • 工作过程:
  • 1.初始标记:单线程标记一下gc roots能直接关联到的对象,并且修改TAMS的值,让下一阶段用户程序并发运行时,能在正确可用的region中创建新对象
  • 2.并发标记:单线程跟用户线程一起执行,从gc roots开始对堆中对象进行可达性分析,找出存活的对象
  • 3.最终标记:多个gc线程单独执行,修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分记录,jvm将这段时间对象变化记录在线程remembered set logs里面最终标记阶段需要把remembered set logs的数据合并到remembered set中,这阶段需要停顿线程,但是可并行执行
  • 4.筛选回收:首先对各个region的回收价值和成本进行排序,根据用户所期望的gc停顿时间来制定回收计划,多个gc线程并行执行,停掉所有用户线程

32.垃圾收集器参数

  • UseSerialGc:虚拟机运行在Client模式下的默认值,打开此开关,使用serial+serial old收集器
  • UseParNewGc:打开此开关后,使用ParNew+serial old的收集器组合进行内存回收
  • UseConcMarkSweepGc:打开此开关,使用ParNew+CMS+Serial Old的收集器组合进行内存回收,serial old将作为CMS收集器出现收集失败后的后备收集器使用
  • UseParallelGC:虚拟机运行在服务模式下的默认值,打开此开关,使用Parallel Scavenge+serial Old的收集器组合进行内存回收
  • UseParallelOldGC:打开此开关,使用Parallel Scavenge+Parallel Old的收集器组合进行内存回收
  • SurvivorRation:新生代中Eden区域与survivor区域的容量比值,默认为8,代表Eden:Survivor=8:1
  • PretenureSizeThreshold:直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配
  • MaxTenuringThreshold:晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就增加1,当超过这个参数值时就进入老年代
  • UseAdaptiveSizePolicy:动态调整java堆中各个区域的大小以及进入老年代的年龄
  • HandlePromotionFailure:是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个eden和survivor区的所有对象都存活的极端情况
  • ParallelGCThreads:设置并行GC时进行内存回收的线程数
  • GCTimeRatio:GC时间占总时间的比率,默认值为99,即允许1%的GC时间,仅在使用Parallel Scavenge收集器时生效
  • MaxGCPauseMillis:设置GC的最大停顿时间,仅在使用Parallel Scavenge收集器时生效
  • CMSInitiatingOccupancyFraction:设置CMS收集器在老年代空间被使用多少次后触发垃圾收集,默认值为68%,仅在使用CMS收集器时生效
  • UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理,仅在使用CMS收集器时生效
  • CMSFullGCsBeforeCompaction:设置CMS收集器在进行若干次垃圾收集后再启动一次内存碎片整理,仅在使用CMS收集器时生效

33.内存分配与回收策略

  • 1.对象优先在Eden分配
  • 2.大对象直接进入老年代
  • 3.长期存活的对象将进入老年代
  • 4.动态对象年龄判定:如果在survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等MaxTenuringThreshold中要求的年龄
  • 5.空间分配担保:在发生Minor GC之前,jvm会先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的
  • 如果不成立,则jvm会查看HandlePromotionFailure设置值是否允许担保失败
  • 如果允许,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小
  • 如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的
  • 如果小于,或者HandlePromotionFailure设置不允许冒险,那这时改为进行一次full gc

34.jvm命令参数

34.1jps:JVM Process Status Tool 显示指定系统内所有的HotSpot虚拟机进程

  • jps -l 输出主类全名 进程id
  • jps -v 输出虚拟机进程启动时jvm参数

34.2jstat:JVM Statistics Monitoring Tool 用于搜集HotSpot虚拟机各方面的运行数据

  • jstat -class processId 监视类装载,卸载数量,总空间以及耗费时间
  • jstat -gc processId 监视java堆状况,包括Eden区,两个Survivor区,老年代,永久代等容量,已用空间,gc时间合计

34.3jinfo:Configuration Info for Java 显示虚拟机配置信息

  • jinfo -flag name=value 修改一部分运行期可写的虚拟机参数

34.4jmap:Memory Map for Java, 生成虚拟机的内存转储快照(heapdump文件)

  • jmap -dump 生成java堆转储快照
  • jmap -finalizerinfo 显示在F-Queue中等待Finalizer线程执行finalize方法的对象,只在linux/solaris平台下有效
  • jmap -heap 显示java堆详细信息,如使用那种回收器,参数配置,分代状况等
  • jmap -histo 显示堆中对象统计信息,包括类,实例数量,合计容量
  • jmap -permstat 以classLoader为统计口径显示永久代内存状态
  • jamp -F 当虚拟机进程对-dump选项没有相应时,可使用这个选项强制生成dump快照

34.5jhat:JVM Heap Dump Browser 用于分析heapdump文件,它会建立一个Http/HTML服务器,让用户可以在浏览器上查看分析结果

34.6jstack:Stack Trace for Java 显示虚拟机的线程快照,java堆栈跟踪工具,生成快照目的是定位线程出现长时间停顿的原因,如线程间死锁,死循环,请求外部资源过长

  • jstack -F 当正常输出的请求不被响应时,强制输出线程堆栈
  • jstack -l 除堆栈外,显示关于锁的附加信息
  • jstack -m 如果调用到本地方法的话,可以显示c/c++的堆栈

35.jconsole java监视与管理控制台

  • 启动方式:在jdk/bin目录下执行jconsole 选择本地进程里的对应进程路径
  • 可以看到概述,内存,线程,类,VM摘要,MBean

36.VisualVM多合一故障处理工具

功能:

  • 显示虚拟机进程以及进程的配置,环境信息(jps jinfo)
  • 监视应用程序的cpu,gc,堆,方法区,以及线程的信息(jstat jstack)
  • dump以及分析堆转储快照(jmap jhat)
  • 方法级的程序运行性能分析,找出被调用最多,运行时间最长的方法
  • 离线程序快照:收集程序的运行时配置,线程dump,内存dump等信息建立一个快照,可以将快照发送开发者处进行bug反馈
  • 其他plugins的无限可能性
  • 启动方式:在jdk/bin目录下执行jvisualVM

37.jvm 调优实战

  • JVM进程崩溃,后台进程过慢,消费速度低于生产者速度。服务器慢慢挤压请求,导致服务器崩溃,改为生产者/消费者模式的消息队列实现
  • 堆外内存导致的溢出错误:查看jvm各个内存模型都是正常的。堆外内存受到物理机内存大小限制。32位机器受到进程内存限制
  • 不恰当的数据结构导致内存占用过大,对象过大,在minor gc时,s区放不下同一时刻存活的大对象,只能放到老年代,等待full gc
  • 调整内存设置控制垃圾收集频率:新生代gc频繁发生,很明显是由于虚拟机分配给新生代的空间太小而导致的。调整-Xmn

38.同步指令

  • 1.jvm支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor)来支持的.
  • 方法级的同步是隐式的,虚拟机可以从方法常量池的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否声明为同步方法,当方法调用了,调用指令将会
  • 检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成时释放管程,在方法执行
  • 期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。
  • 2.同步一段指令集序列,通常由sysnchronized语句块来表示,java虚拟机指令集中有monitorenter 和monitorexit两条指令来支持synchronized关键字语义

39.类加载生命周期

  • 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载,验证,准备,解析,初始化,使用,卸载7个阶段

40.类加载的过程

40.1加载是类加载过程的一个阶段,在加载阶段,jvm需要完成以下3件事情

  • 通过一个类的全限定名来获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口

40.2验证

  • 文件格式验证
  • 是否以魔数0xCAFEBABE开头
  • 主,次版本号是否在当前虚拟机处理范围之内
  • 常量池的常量中是否有不被支持的常量类型
  • 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
  • CONSTANT_Utf8_info型的常量中是否有不符合UTF8编码的数据
  • Class文件中各个部分及文件本身是否有被删除的或附加的其他信息
  • 元数据验证
  • 这个类是否有父类
  • 这个类的是否继承了不允许被继承的类
  • 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
  • 类中的字段,方法是否与父类产生矛盾
  • 字节码验证
  • 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作
  • 保证跳转指令不会跳转到方法体以外的字节码指令上
  • 保证方法体中的类型转换是有效的
  • 符号引用
  • 符号引用中通过字符串描述的全限定名是否能找到对应的类
  • 在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
  • 符号引用中的类,字段,方法的访问性

40.3准备:

  • 正式为类变量分配内存并设置类变量初始值的阶段

40.4解析:虚拟机将常量池内的符号引用替换为直接引用的过程

  • 类或接口的解析
  • 字段解析
  • 类方法解析
  • 接口方法解析

40.5初始化:

  • 类初始化阶段是类加载过程的最后一步,真正开始执行类中定义的java程序代码

41.类加载器有哪些

  • 启动类加载器:加载java_home/lib目录中的类库加载到虚拟机内存中
  • 扩展类加载器:在家java_home/lib/ext目录中的类库
  • 应用程序类加载器:这个类负责加载用户类路径classPath上所指定的类库

42.双亲委派模型

  • 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,使用组合关系来复用父加载器的代码
  • 工作过程:如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,
  • 因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求,子加载器才会尝试自己去加载
  • 优点:保证java程序的稳定运作,自定义的类不会顶替系统类库中的类

42.1工作过程:

  • 向上委派:实际上就是查找缓存,是否加载了该类,有则直接返回,没有继续向上
  • 委派到顶层之后,缓存中还是没有,则到加载路径中查找,有则加载返回,没有则向下查找
  • 向下查找:查找加载路径,有则加载返回,没有则继续向下查找

42.2破坏双亲委派模型:

  • SPI:向上委派是用户程序调用基础api,一般,所以先加载基础类,再加载用户类,但是基础类也有调用用户程序的时候,SPI就是,
  • SPI:这个时候需要线程上下文类加载器。默认创建线程时,如果没设置会继承父线程的加载器,如果没有值,会设置为应用程序类加载器
  • 热部署代码
  • 兼容jdk1.2之前的用户自定义的类。不建议用户重写loadClass方法,现在如果loadClass()方法查不到,会调用findClass方法

43.运行时栈帧结构

  • 栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素
  • 栈帧存储了方法的局部变量表,操作数栈,动态连接,方法返回地址等信息

43.1每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程

  • 局部变量表:是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量,在java程序编译为class文件时,就在方法的code属性的max_locals数据项中确定了方法所需分配的局部变量表的最大容量
  • 局部变量表:存放boolean byte char short int float reference returnAddress8种类型
  • 局部变量表:reference表示对一个对象实例的引用,从此引用中直接或间接地查找到对象在java堆中的数据存放的起始地址索引,二是直接或间接地查找到对象所属数据类型在方法区中的存储的类型信息
  • 局部变量表:returnAddress指向了一条字节码指令的地址,
  • 操作数栈:是一个后入先出栈,操作数栈的每一个元素可以是任意的java数据类型
  • 动态连接:每一个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,该引用是为了支持方法调用过程中的动态连接
  • 动态连接:class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数,这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,静态解析
  • 动态连接:另外一部分将在每次运行期间转换为直接引用,这部分称为动态连接
  • 方法返回地址:方法退出时,把当前栈帧出栈,退出时,要恢复上层方法的局部变量表和操作数栈,把返回值压入调用者栈帧的操作数栈中,恢复上层方法,则需要根据方法放回地址中的地址来返回

44.并发三大特性

  • 原子性:由java内存模型来保证的原子性变量操作包括,read load assign use store write 基本数据类型的访问读写是具备原子性的,long和double除外,lock unlock操作满足原子性
  • 可见性:是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改,java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为媒介实现可见性
  • 有序性:线程内表现为串行,指令重排序和工作内存与主内存同步延迟,volatitle和synchronized两个关键字来保证线程之间操作的有序性,
  • volatitle包含了禁止指令重排序的语义
  • synchronized由一个变量在同一个时刻只允许一条线程对其进行lock操作
  • 保证了这三大特性,才能保证并发安全
  • 总线lock 和MESI(缓存一致性协议)保证可见性

45.jvm 飞行器 观察线上jvm变化

  • -Dcom.sun.management.jmxremote.port=8888
  • -Dcom.sun.management.jmxremote
  • -Dcom.sun.management.jmxremote.authenticate=false
  • -Dcom.sun.management.jmxremote.ssl=false
  • -XX:+UnlockCommercialFeatures
  • -XX:+FlightRecorder
发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章