JVM

153 阅读7分钟

JVM的内存区域

ea5d08f5e6bd059f5c9fb3305dac5fc.png

新生代何时晋升到老年代?

NmcYaMowyK.jpg 326cb3577f1a81984c932731d881717.png NmcYaMowyK.jpg 空间分配担保是为了确定在MinorGC前确保老年代本身还有容纳新生代所有对象的剩余空间

类加载过程

(下图为类的生命周期) 28e963e4b9ca1922bdbe4590e925efb.png

类加载器

作用:所有的类都由类加载器加载,作用是将class文件加载到内存。

05yxlcN2em.jpg

双亲委派模型

每当一个类加载器接收到加载请求时,它会先将请求转发给父类加载器。在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试去加载。使用委派模型的目的为了避免类的重复加载。 应用程序类加载器->拓展类加载器->启动类加载器

c6f1ad30-2c4b-4cfd-9e14-b30591b9a2ae.jpeg

自定义类加载器? 自定义类加载器需要继承ClassLoader()方法,如果要打破双亲委派模型重写loadClass(), 如果不想打破重写就重写ClassLoader()类中的findClass()方法。

加载即获取类的二进制字节流,是可控性最强的阶段

死亡对象的判断方法

3d8f9b7d-791b-4da8-a385-b5c2b69d13b1.jpeg

垃圾收集算法

c8591148-ba59-453d-a88f-d2b9f0139432.jpeg

jvm调优

(对于系统的优化思路一般是,先排查是否是数据库的问题,包括,索引是否合理,是否需要引进分布式缓存,是否需要分库分表),然后考虑是否是硬件能力不足导致,然后再是在应用层代码上进行排查并优化, 最后考虑jvm调优。

3d8f9b7d-791b-4da8-a385-b5c2b69d13b1.jpeg 在理解JVM内存结构和各种垃圾收集器前提下,结合业务,调整参数来使应用正常运行。

指标:吞吐量,停顿时间,垃圾回收频率

基于这三个指标,我们可能需要调整:

垃圾回收分类

984d66c049ffa577f3d6facee4a9bbb.png

内存区域的大小以及相关策略

堆内存大小、新生代占多少、老年代占多少、Survivor占多少、晋升老年代的条件等

参数(-Xmx:设置堆的最大值、-Xms:设置堆的初始值、-Xmn:表示年轻代的大小、-XX:SurvivorRatio:伊甸区和幸存区的比例等等)

(按经验来说:IO密集型的可以稍微把「年轻代」空间加大些,因为大多数对象都是在年轻代就会灭亡。 内存计算密集型的可以稍微把「老年代」空间加大些,对象存活时间会更长些)

选择合适的垃圾回收器,以及设置合适的参数

一、针对新生代的垃圾收集器:Serial New,Parallel Scavenge 和 Parallel New

Serial(siry) New:应用复制算法,简单高效,但会暂停程序导致停顿。
ParNew:是Serial的多线程版本,可以配合老年代的CMS工作
Parallel(拍落) Scavenge(死凯位置)应用复制算法,并行收集器,追求高吞吐量,高效利用CPU。
(吞吐量就是运行用户代码时间/(运行用户代码时间+gc时间)

二针对老年代的垃圾收集器:Serial Old 和 Parallel Old,以及CMS

 Serial Old:Serial GC的老年代版本,采用标记整理算法。
 Parallel Old:Parallel Scavenge的老年代版本,可配合Parallel Scavenge收集器达成在整体应用上吞吐量最大化
 CMS是基于标记清除算法实现的。
 优点:并发收集、低停顿。
 缺点:CMS对cpu资源敏感,在cpu数量较少时,可能因为占用一部分cpu资源导致程序变慢。
      cms无法处理浮动垃圾,可能出现”Concurrent Mode Failure“失败而导致Full GC
      基于清除算法,会产生内存碎片。
      

585bcb755a555aa7c2644d529efef05.png 三横跨新生代和老年代的垃圾收集器G1

89288122d0d3ec79ae8d04babd2c43d.png

四、ZGC(可伸缩低延迟垃圾收集器)

特点:

一、并发收集:吞吐量不会下降超过15%。(与G1相比)

二、低延迟:GC的停顿时间不会超过10ms。

三、大内存支持:既能处理几百MB的小堆,也能处理几个TB的堆。

四、动态空间压缩:会对堆进行动态的空间压缩,避免堆内存碎片化的问题,并减少内存浪费。

比如(-XX:+UseG1GC:指定 JVM 使用的垃圾回收器为 G1、-XX:MaxGCPauseMillis:设置目标停顿时间、-XX:InitiatingHeapOccupancyPercent:当整个堆内存使用达到一定比例,全局并发标记阶段 就会被启动等等)

遇到问题进行调优,可使用工具

通过jps命令查看Java进程基础信息(进程号、主类)。

通过jstat命令查看Java进程相关的信息,如类加载、编译相关信息,各个区域的GC情况。

通过jinfo命令来查看和调整Java进程的运行参数。

通过jmap查看进程的内存信息,并且将信息保存到文件,可利用MAT进行分析。

通过jstack命令来查看JVM线程信息,可用来排查死锁相关问题。

Arthas(阿里开源的诊断工具)涵盖命令并有可视化界面。

1a840e9dae8a068a9057f0571e33990.png

OOM的原因和解决

定义:

当JVM内存不足,没有空闲内存,并且垃圾收集器也不能提供更多内存,就会发生OOM。

原因

一、堆空间不足。存在内存泄漏问题;堆大小设置不合理;JVM处理引用不及时,导致内存无法回收。

二、对于虚拟机栈和本地方法栈,类似于不断递归且没有返回条件,不断压栈就会导致StackOverFlowError。 如果JVM试图拓展堆空间的时候失败,就会抛出OOM。

三、在老版JDK中,由于JVM堆永久代垃圾回收不积极,容易出现OOM。元数据区引入有所改善。

四、直接内存不足,会导致OOM。

解决

一、使用jps,jmp,MAT,等工具分析出频繁full gc的原因并定位到代码。

频繁full gc

原因:

一、高并发,数据量过大,每次Young GC过后存活对象过多,内存分配不合理,Survivor区过小,导致对象频繁进入老年代,触发Full gc。

二、系统一次性加载大量数据到内存,导致大对象过多,大对象进入老年代,触发FULL GC。

三、系统内存泄漏,大量对象无法回收,一直占用老年代,触发FULL GC。

四、永久代加载类过多触发FUll GC。

五、代码或者第三方依赖包中有system.gc()操作,可设置JVM禁止执行该方法。

解决:

一、通过命令查看进程、线程情况、dump出内存快照,用MAT工具进行分析,确定原因。

二、调整JVM的参数,适当增大新生代的大小。

三、控制并发数量。

CPU打满?

原因:

一、代码中某个位置读取数据量较大,内存耗尽,频繁full gc,系统缓慢。

二、代码中有比较耗CPU的操作,导致CPU过高,系统运行缓慢。可通过命令查看当前CPU消耗高的进程和线程是哪一个,再查看线程的堆栈信息。

系统缓慢的情况

一、FULL GC次数过多

二、CPU打满

三、不定期出现的接口耗时情况。

eg:接口访问需要2、3秒才会返回,一般来说消耗的cpu和占用的内存也不高。思路是首先找到该接口,通过压测工具不断加大访问力度,由于访问力度大,大多数线程都会阻塞在该阻塞点,可以定位到接口中比较耗时的代码位置。

四、某个线程处于waiting状态。

比如CountDownLatch的不合理使用。

五、出现死锁。这个可以直接通过jstack日志分析得到。

三色标记法

作用:提高标记对象的效率

初始标记时STW时间较短,但并发标记时时间较长,因此应提高标记效率。

在三色标记中,从GC ROOT标记为以下三种颜色

白:在开始遍历时,所有对象都为白

灰:被垃圾回收器扫描过,但还有引用没有被扫描,为灰

黑:被垃圾回收器扫描过,并且这个对象的引用也全部被扫描,可存活对象,为黑。

全部扫描过后,回收为白的对象。

强软弱虚引用?

8a6e2a1c9bfd570ca3ac4f1a6386bbd.png