JVM学习总结

430 阅读9分钟

前言

www.processon.com/view/link/5…

正文

类加载机制

juejin.cn/post/690482…

运行时数据区

Method Area(方法区)

方法区是线程共享区域,在虚拟机启动时创建。
用于存储被虚拟机记载的类的信息、常量、静态变量、即时编译器编译后的代码等。
当方法区无法满足内存分配需求时,会抛出OutOfMemoryError异常。

Constant Pool(常量池)

用于存放编译时期生成的各种字面量和符号引用。

Heap(堆)

堆被所有线程共享。
Java对象实例以及数组都在堆上分配。

Virtual Machine Stacks(虚拟机栈)

线程独享
每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。
调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。

栈帧

  1. 局部变量表:方法定义的局部变量以及方法的参数存放在这张表中,不可以直接使用,如需要使用,必须通过相关指令将其加载到操作数栈中作为操作数去使用。
  2. 操作数栈:以压栈出栈的方式存储操作数。配合局部变量表使用。
  3. 动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
  4. 方法返回地址: 当一个方法开始执行后,只有两中方式可以退出,一种是遇到方法的返回的字节码指令;一种是遇见异常,并且这个异常没有在方法体内得到处理。

垃圾回收

如何确定一个对象是垃圾?

引用计数法

持有其对象的引用的数量

弊端:AB互相持有,永远无法回收

可达性分析法

通过GC Root对象开始向下寻找,看对象是否可达

GC Root:类加载器,Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量等。

算法

标记-清除

先标记需要回收的对象,后清理回收。

缺点:
(1) 标记和清除两个过程都比较耗时,效率不高
(2) 会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动

复制

空出一半区域,两个区域交替使用。一个区域满了,将存活的对象复制到另一个区域,然后将这个区域清空。

缺点:
空间浪费

标记-整理

先标记,然后让所有存活的对象都向一端移动,清理掉边界以外的内存

缺点:
移动速度慢,效率低。

分代收集

Young区:复制算法(对象在被分配之后,可能生命周期比较短,Young区复制效率比较高)。
Old区:标记清除或标记整理,Old区对象存活时间比较长,复制来复制去没必要,不如做个标记再清理。

垃圾回收器(算法的落地)

Serial收集器

简单高效,在单线程下有收集效率很高。
但是收集阶段需要STW(Stop the world)

应用算法:复制算法。
应用区域:Young

ParNew收集器

Serial的多线程版本
收集过程暂停所有应用程序线程,单CPU时比Serial效率差。

应用算法:复制算法。
应用区域:Young

Parallel Scavenge收集器

ParNew差不多,但是可以设置吞吐量

吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)
比如虚拟机总共运行了100分钟,垃圾收集时间用了1分钟,吞吐量=(100-1)/100=99%。
若吞吐量越大,意味着垃圾收集的时间越短,则用户代码可以充分利用CPU资源,尽快完成程序的运算任务。

-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间
-XX:GCTimeRatio直接设置吞吐量的大

应用算法:复制算法。
应用区域:Young

CMS收集器

(1) 初始标记 CMS initial mark 标记GC Roots能关联到的对象 Stop The World--->速度很快
(2) 并发标记 CMS concurrent mark 进行GC Roots Tracing
(3) 重新标记 CMS remark 修改并发标记因用户程序变动的内容 Stop TheWorld
(4) 并发清除

应用算法:标记-清除算法
应用区域:Old

优点:并发收集、低停顿
缺点:产生大量空间碎片、并发阶段会降低吞

G1收集器

将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

(1) 初始标记(Initial Marking)标记一下GC Roots能够关联的对象,并且修改TAMS的值,需要暂停用户线程
(2) 并发标记(Concurrent Marking)从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
(3) 最终标记(Final Marking)修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
(4) 筛选回收(Live Data Counting and Evacuation)对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划

应用算法:标记整理
应用区域:Young,Old

缺点:对于硬件要求高,最低4核,8核才能发挥完全作用。

垃圾收集器分类

  1. 串行收集器:Serial和Serial Old只能有一个垃圾回收线程执行,用户线程暂停。适用于内存比较小的嵌入式设备。
  2. 并行收集器 [吞吐量优先] :Parallel Scanvenge、Parallel Old多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。适用于科学计算、后台处理等若交互场景。
  3. 并发收集器[停顿时间优先]:CMS、G1用户线程和垃圾收集线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集线程在执行的时候不会停顿用户线程的运行。适用于相对时间有要求的场景,比如Web。

如何选择合适的垃圾收集器

  1. 优先调整堆的大小让服务器自己来选择
  2. 如果内存小于100M,使用串行收集器
  3. 如果是单核,并且没有停顿时间要求,使用串行或JVM自己选
  4. 如果允许停顿时间超过1秒,选择并行或JVM自己选
  5. 如果响应时间最重要,并且不能超过1秒,使用并发收集器

Full GC发生的时机

(1)调用Sytem.GC()

(2)老年代空间不足时

(3)GC担保失败

GC合理表现

Full GC耗时在0.1-0.3秒以内的话,一般不需要花费额外的时间做GC调优。
Full GC耗时达到1-3秒甚至10秒以上,就需要调优 。

  1. Minor GC执行迅速(50毫秒以内) 
  2. Minor GC执行不频繁(间隔10秒左右一次) 
  3. Full GC执行迅速(1秒以内) 
  4. Full GC执行不频繁(间隔10分钟左右一次)

如何分析JVM问题

  1. 使用jps/top查看进程端口号
  2. jstack判断是否有死锁现象
  3. jstat -gc PID 1000 :打印gc日志,查看GC是否正常
  4. jmap -heap PID :导出dump堆快照,查看大对象。

常见问题思考

(1)内存泄漏与内存溢出的区别?

内存泄漏:对象无法得到及时的回收,持续占用内存空间,从而造成内存空间的浪费。
内存溢出:内存泄漏到一定的程度就会导致内存溢出,但是内存溢出也有可能是大对象导致的。

(2)young gc会有stw吗?

不管什么 GC,都会有 stop-the-world,只是发生时间的长短。

(3)major gc和full gc的区别?

major gc指的是老年代的gc,而full gc等于young+old+metaspace的gc。

(4)G1与CMS的区别是什么?

CMS 用于老年代的回收,而 G1 用于新生代和老年代的回收。G1 使用了 Region 方式对堆内存进行了划分,且基于标记整理算法实现,整体减少了垃圾碎片的产生。

(5)什么是直接内存?

直接内存是在java堆外的、直接向系统申请的内存空间。通常访问直接内存的速度会优于Java堆。因此出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。

(6)不可达的对象一定要被回收吗?

即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

(7)方法区中的无用类回收方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?

判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。
类需要同时满足下面 3 个条件才能算是“无用的类”:
该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
加载该类的 ClassLoader 已经被回收。
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。