JVM重点知识总结
一、JVM内存模型
- JVM将内存分为5大区域:程序计数器、虚拟机栈、本地方法栈、堆、方法区
1. 程序计数器
- 线程私有的,是一块很小的内存空间。作为当前线程的行号指示器,用于记录当前虚拟机正在执行的线程指令。
- 唯一一个不会发生OOM的区域
2. 虚拟机栈
- 线程私有的,每个方法执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法的返回等信息。
- 当线程请求的栈深度超过了虚拟机允许的最大深度时,跑出*Error异常
3. 本地方法栈
- 线程私有的,保存的是native方法的信息。
- 当一个jvm创建的线程调用native方法后,jvm不会在虚拟机栈中为该线程创建栈帧。而是简单的动态链接并直接调用该方法。
4. 堆
- 线程共享。几乎所有对象的实例和数组都要在堆上分配内存
5. 方法区
-
存放已经被加载的类信息、常量、静态变量和即时编译器编译后的代码数据,即永久代。
-
在jdk1.8之后,被元数据区取代。原方法区分成两部分:
- 加载的类信息:保存在元数据区
- 运行时常量池:保存在堆中
二、JVM垃圾回收器
1. 串行垃圾回收器
-
串行垃圾回收器只会使用一个CPU或一个收集线程完成垃圾收集工作。并且在垃圾回收的时候,必须暂停其他所有的工作线程(Stop the World),直到收集完成。
-
串行垃圾回收器有两种:Serial、Serial Old,一般搭配使用。通过**-XX:+UseSerialGC开启**
-
Serial:新生代、复制算法
-
Serial Old:老年代、标记整理算法
2. 并行垃圾回收器
-
并行垃圾回收器通过多线程运行垃圾收集,也会Stop the World。适合Server模式以及多CPU环境,一般会和CMS搭配使用。
-
ParNew:新生代、复制算法。Serial的多线程版本,默认开启的收集线程数和CPU数量一样。通过-XX:+UseParNewGC开启和Serial Old收集器组合进行内存回收
-
Parallel Scavenge:**新生代、复制算法。**关注吞吐量(吞吐量=代码运行时间 / (代码运行时间+垃圾手机时间))。高效率利用CPU时间。
-
Parallel Old:老年代、标记整理法。Parallel Scavenge的老年代版本。一般使用Parallel Scavenge+Parallel Old组合使用
3. CMS收集器
- CMS(Concurrent Mark Sweep)收集器是一种以获得最短回收停顿时间为目的的收集器。采用标记清除算法。
- 运作过程:初始标记、并发标记、重新标记、并发清除。收集结束会产生大量空间碎片。
- 初始标记:标记一下GC Roots能直接关联到的对象,会Stop the World
- 并发标记:GC Roots Tracing,可以和用户线程并发执行
- 重新标记:标记期间产生的对象存活的再次判断,修正对这些对象的标记,执行时间相对并发标记短,会Stop the World
- 并发清除:清除对象,可以和用户线程并发执行
由于垃圾回收线程可以和用户线程同时运行,也就是说它是并发的,那么它会对CPU的资源非常敏感,CMS默认启动的回收线程数是(CPU数量+3)/ 4,当CPU<4个时,并发回收是垃圾收集线程就不会少于25%,而且随着CPU减少而增加,这样会影响用户线程的执行。而且由于它是基于标记-清除算法的,那么就无法避免空间碎片的产生。CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。
所谓浮动垃圾,在CMS并发清理阶段用户线程还在运行着,伴随程序运行自然还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只能留待下一次GC时再清理掉。
4. G1垃圾收集器
-
G1从整体上来看是基于标记-清除算法的,但是局部上基于复制算法。这样的好处是空间整合做的比较好,不会产生空间碎片。
-
G1也是并发与并行的,充分利用多CPU、多核的硬件环境来缩短Stop the World的时间
-
G1还是分代收集的,但是是需要分代配合不同的垃圾收集器。因为G1中的垃圾收集器区域是分区的。
运作流程:初始标记、并发标记、最终标记、筛选回收,不会产生空间碎片,可以精确控制停顿。
G1将整个堆分为大小相等的多个Rigion区域,G1跟踪每个区域的垃圾大小,在后台维护一个优先级列表。每次根据允许的收集时间,优先回收价值最大的区域,以达到在有限时间内获取尽可能高的回收效率。
三、产生OOM的原因
- 文件描述符数目(fd)超限,可能的发生场景有:
- 短时间内大量请求导致socket的fd数激增,大量(重复)打开文件等
- 线程数超限
- app内多线程使用不合理,多个不共享线程池的OKhttpclient等
- 传统的java堆内存超限
- 32位系统进程逻辑空间被占满导致OOM
- 其他