JVM速成大法
《我想进大厂》之JVM夺命连环10问
看完这篇JVM垃圾回收,和面试官扯皮没问题了
https://mp.weixin.qq.com/s/GekJhJBo2WY7girWV7GhBQ
10个经典又容易被人疏忽的JVM面试题
https://mp.weixin.qq.com/s/edUAFzOnu4ILF8_mlreBog
炸了!一口气问了我18个JVM问题!
空投十个JVM核心知识点,速度捡包
终于搞懂了Java8的内存结构,再也不纠结方法区和常量池了!
https://mp.weixin.qq.com/s/BnObMjBfesC1tJWkDDHb6A
深度揭秘垃圾回收底层,这次让你彻底弄懂她
https://mp.weixin.qq.com/s/_wcTxJOCmZXS5MwHMNqbdQ
20张图助你了解JVM运行时数据区,你还觉得枯燥吗?
https://mp.weixin.qq.com/s/Y-VQkMs6c2j2CpnTHl43Lg
垃圾回收算法
新生代和老年代的垃圾回收策略
标记清除
标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。
在标记阶段首先通过根节点(GC Roots),标记所有从根节点开始的对象,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。
复制算法
从根集合节点进行扫描,标记出所有的存活对象,并将这些存活的对象复制到一块儿新的内存(图中下边的那一块儿内存)上去,之后将原来的那一块儿内存(图中上边的那一块儿内存)全部回收掉
标记整理
复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。
这种情况在新生代经常发生,但是在老年代更常见的情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活的对象较多,复制的成本也将很高。
分代收集算法
分代收集算法就是目前虚拟机使用的回收算法,它解决了标记整理不适用于老年代的问题,将内存分为各个年代。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。
在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率搞,没有额外空间对它进行分配担保,所以只能使用标记清除或者标记整理算法。
面试官问为什么新生代不用标记清除算法
https://mp.weixin.qq.com/s/qGL36Q1npiYKKTOG5SVv1A
CMS与G1的区别
CMS收集器是一种以获取最短回收停顿时间为目标的收集器。基于“标记-清除”算法实现,它的运作过程如下:
-
初始标记
-
并发标记
-
重新标记
-
并发清除
初始标记、从新标记这两个步骤仍然需要“stop the world”,初始标记仅仅只是标记一下GC Roots能直接关联到的对象,熟读很快,并发标记阶段就是进行GC Roots Tracing,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生表动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长点,但远比并发标记的时间短。
CMS是一款优秀的收集器,主要优点:并发收集、低停顿。
缺点:
-
CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
-
CMS收集器无法处理浮动垃圾,可能会出现“Concurrent Mode Failure(并发模式故障)”失败而导致Full GC产生。
浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随着程序运行自然就会有新的垃圾不断产生,这部分垃圾出现的标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC中再清理。这些垃圾就是“浮动垃圾”。
-
CMS是一款“标记--清除”算法实现的收集器,容易出现大量空间碎片。当空间碎片过多,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。
G1是一款面向服务端应用的垃圾收集器。G1具备如下特点:
-
并行于并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
-
分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。它能够采用不同的方式去处理新创建的对象和已经存活了一段时间,熬过多次GC的旧对象以获取更好的收集效果。
-
空间整合:与CMS的“标记--清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
-
可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,
-
G1运作步骤:
1、初始标记;2、并发标记;3、最终标记;4、筛选回收
//--------------------------------------------------------------------------------------------------
G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:
-
G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。
-
CMS采用的是标记清除垃圾回收算法,可能会产生不少的内存碎片
-
-
G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
各种GC的触发条件
首先明确gc的发生区域
这是JDK8之前的,JDK8上没有最右边的Perm区。
从图来看Minor GC发生在Eden区;Young GC发生在Eden、S0、S1区;Major GC发生在Old区,Full GC针对整个堆。
一般情况下,将Minor GC和YoungGC视为一个东西也行。
(1)Minor GC(YoungGC)的触发条件
- 当Eden区满时,触发Minor GC。Eden区清空,全部转移到Survivor区中。
(2)Full GC的触发条件
- 调用System.gc()方法,这只是通知或者是建议虚拟机进行Full GC,虚拟机可以根据情况选择是否执行。
- 大对象或老龄对象在老年代中放不下时
- 方法区(1.8)不足,持久代(1.8之前)不足
当然,虚拟机在进行minorGC之前会判断老年代最大的可用连续空间是否大于新生代的所有对象总空间
1、如果大于的话,直接执行minorGC
2、如果小于,判断是否开启HandlerPromotionFailure,没有开启直接FullGC
3、如果开启了HanlerPromotionFailure, JVM会判断老年代的最大连续内存空间是否大于历次晋升(晋级老年代对象的平均大小)平均值的大小,如果小于直接执行FullGC
4、如果大于的话,执行minorGC
如何判断一个对象是否存活
(1)引用计数法——被引用1次,则计数器加1,取消引用则减1。没被JVM采用,因为无法解决对象之间的循环引用的问题。
(2)可达性分析算法
从以GC Roots为根节点的树向下搜索,能搜索到则代表对象可用。
哪些可以作为GC Roots对象?
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(即一般说的Native方法)引用的对象。
与GC Roots不可达的对象,是立即被回收吗?
不是立即被回收的
- 如果该对象没有覆盖finalize方法或finalize已经被调用过,则被判定为死亡,等待回收。
- 将没有被调用finalize的不可达对象放入一个队列中,之后jvm创建一个低优先级的线程去挨个执行这些对象的finalize方法。如果某个对象在执行完finalize方法后,仍然没有与引用链上的对象建立关系,则被判定为死亡,等待回收。如果建立了关系,则被判定为存活。
三色标记
CMS与三色标记算法
https://mp.weixin.qq.com/s/FMq2T2OCD-YWHi0TmMYh7A
与其千篇一律,不如一篇文章搞懂三色标记是如何处理漏标
https://mp.weixin.qq.com/s/pswlzZugDhNeZKerTTsCnQ
三色标记算法问题讲解
https://juejin.cn/post/6952829010850086949
类加载
深刻理解java类的加载以及ClassLoader源码分析
https://blog.csdn.net/qq_44543508/article/details/102983363
JVM必问知识点:类加载过程
https://mp.weixin.qq.com/s/eHqFONXXNc-LD4ugaKM6UA
这一篇文章,可以把Java中的类加载器了解的七七八八了
https://mp.weixin.qq.com/s/_apuwN4OkaJoHIXFIUtRtw
Java类加载器:坑爹是我的特色
https://mp.weixin.qq.com/s/BRs3GM2cvvSSmm4CDsEEqQ
双亲委派
双亲委派机制
https://mp.weixin.qq.com/s/6nJ-6cDLW6TfysWV5ZB3Iw
双亲委派模型:大厂高频面试题,轻松搞定
读者美团五面:Java历史上有三次破坏双亲委派模型,是哪三次?
https://mp.weixin.qq.com/s/zZmsi7lpuQHECOHA2ohOvA
如何打破双亲委派
https://blog.csdn.net/cy973071263/article/details/104129163
Tomcat是如何打破双亲委派机制的
https://www.jianshu.com/p/7706a42ba200
哪些区域会OOM
哪些地方会发生OOM
(1)堆溢出
堆在GC后,仍然放不下新创建的对象时,则会抛出“java.lang.OutOfMemoryError:Java heap space”
如果不存在内存泄露的话,调大-Xmx(初始堆大小),-Xmx(最大堆大小)即可
如果存在内存泄漏,则需要定位到导致内存泄露的代码。
(2)方法区溢出
(方法区是规范,JDK8之前的实现为永久代,JDK8及之后的实现为元数据区)
方法区中主要是类信息,静态变量等,当类过多,静态变量过多,就会发生OOM。
越来越多的动态代理技术也会产生大量代理类,占用大量空间。
抛出的异常为“java.lang.OutOfMemoryError:Metaspace”
可以通过-XX:MaxMetaspaceSize调大元数据区的最大容量
(3)虚拟机栈与本地方法栈溢出
这两个区域的区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务,在内存分配异常上是相同的。
栈上可能会出现两个异常
- 线程请求的栈深度超过最大深度,则抛出StackOverFlowError错误,比如进行了一个不会停止的递归调用
- 如果虚拟机栈是可以动态拓展的,拓展时无法申请到足够的内存,则抛出OutOfMemoryError错误,比如循环创建大量线程。
(4)直接内存溢出
直接内存虽然不是虚拟机运行时数据区的一部分,但既然是内存,就会受到物理内存的限制。
在JDK1.4中引入的NIO使用Native函数库在堆外内存上直接分配内存,但直接内存不足时,也会导致OOM。
(5)字符串常量池溢出
JDK6时,字符串常量池还存在与持久代中,不断的调用String.intern()则会抛出“java.lang.OutOfMemoryError:PermGen space”
使用-XX:MaxPermSize限制持久代大小即可限制字符串常量池大小
PS:
- Java6时,内存溢出区域为永久代。因为在Java6及之前,字符串常量池在永久代中
- Java7时,内存溢出区域为堆中。因为在Java7时,字符串常量池被移到堆中了。
- Java8时,内存溢出区域仍然为堆中,不过此时已经没有永久代了。
哪些场景会产生OOM?怎么解决?
https://juejin.cn/post/6873299829784018952
关于内存安全问题,你应该了解的几点!
https://mp.weixin.qq.com/s/BJUgnDPP2wtUUEjPnCX0oA
new Object() jvm层面发生了什么
- 首先去常量池寻找该类的符号应用,找不到,则执行类加载
- 在堆上分配内存,分配机制有指针碰撞与空闲列表
- 多线程下进行分配内存时,有cas失败重试与本地线程分配缓冲区(Thread Local Allocation Buffer, TLAB)
- 将分配到的内存空间中的数据类型都 初始化为零值(不包括对象头)
- 对对象头进行必要的设置 ,例如这个对象是哪个类的实例、对象的哈希码、对象的GC分代年龄等信息
- 调用对象的init()方法 ,根据传入的属性值给对象属性赋值
- 栈中新建对象引用 ,并指向堆中刚刚新建的对象实例
简单来讲,就是分配内存、初始化与栈中变量指向堆,在需要单例的对象时,需要加上DCL与volatile
为什么使用元空间替换永久代
方法区与持久带、元空间的区别
《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。 也就是说,永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。
JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。
https://blogs.oracle.com/poonam/about-g1-garbage-collector,-permanent-generation-and-metaspace
下面是一些常用参数:
-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
https://plumbr.io/handbook/garbage-collection-in-java
1.整个永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
当你元空间溢出时会得到如下错误:
java.lang.OutOfMemoryError: MetaSpace
你可以使用 -XX:MaxMetaspaceSize
标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。-XX:MetaspaceSize
调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。
2.元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 MaxPermSize
控制了, 而由系统的实际可用空间来控制,这样能加载的类就更多了。
3.在 JDK8,合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代的地方了。
主要进行 gc 的区域是堆,就 HotSpot 虚拟机来说,永久代会发生 gc (full gc),但是,元空间使用的是直接内存不会发生 gc。
JVM 为什么使用元空间替换了永久代?
https://www.bilibili.com/read/cv5021445/
ZGC
新一代垃圾回收器ZGC的探索与实践
https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html
美团面试官问我:ZGC 的 Z 是什么意思?
https://mp.weixin.qq.com/s/5trCK-KlwikKO-R6kaTEAg
why哥带你看看ZGC到底是个什么鬼玩意?
https://mp.weixin.qq.com/s/dEGRVC9eHevWi86D7040wQ
GC日志
一个简单案例,带你看懂GC日志
https://mp.weixin.qq.com/s/VwDzXz_-_EAKegdfmXEo3A
JVM日志参数十全大补丸
https://mp.weixin.qq.com/s/XZFEgf1ZS7gNt7lku3TF4g
垃圾回收-实战篇
https://mp.weixin.qq.com/s/jS0QHc9-zUSarRIoYbfCLw
对象的分配位置
对象都是分配在堆区吗
https://www.jianshu.com/p/8377e09971b8
对象并不一定都是在堆上分配内存的
面试官问我平时写的Bug的存储位置(逃逸分析、标量替换、锁消除)
JDK 1.8 下的 java.lang.Class 对象和 static 成员变量在堆还是方法区?
https://blog.csdn.net/xu_jl1997/article/details/89433916
常量池都有哪些
class常量池、字符串常量池和运行时常量池的区别
https://blog.csdn.net/xiaojin21cen/article/details/105300521
Java中几种常量池的区分
https://www.cnblogs.com/holos/p/6603379.html
对象的大小
聊聊Java对象在内存中的大小
https://segmentfault.com/a/1190000012354736
JOL工具分析java对象大小
https://www.jianshu.com/p/6d62c3ee48d0
直接内存
直接内存是什么
https://www.cnblogs.com/xjwhaha/p/14086814.html#_labelTop
直接内存
https://mp.weixin.qq.com/s/C10lKDJo7zQKW0bkmslL7w
JIT是什么
聊聊JIT是如何影响JVM性能的
https://mp.weixin.qq.com/s/DE3mgtQlnCPqodgVY6seFg