JVM的内存区域
新生代何时晋升到老年代?
空间分配担保是为了确定在MinorGC前确保老年代本身还有容纳新生代所有对象的剩余空间
类加载过程
(下图为类的生命周期)
类加载器
作用:所有的类都由类加载器加载,作用是将class文件加载到内存。
双亲委派模型
每当一个类加载器接收到加载请求时,它会先将请求转发给父类加载器。在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试去加载。使用委派模型的目的为了避免类的重复加载。 应用程序类加载器->拓展类加载器->启动类加载器
自定义类加载器? 自定义类加载器需要继承ClassLoader()方法,如果要打破双亲委派模型重写loadClass(), 如果不想打破重写就重写ClassLoader()类中的findClass()方法。
加载即获取类的二进制字节流,是可控性最强的阶段
死亡对象的判断方法
垃圾收集算法
jvm调优
(对于系统的优化思路一般是,先排查是否是数据库的问题,包括,索引是否合理,是否需要引进分布式缓存,是否需要分库分表),然后考虑是否是硬件能力不足导致,然后再是在应用层代码上进行排查并优化, 最后考虑jvm调优。
在理解JVM内存结构和各种垃圾收集器前提下,结合业务,调整参数来使应用正常运行。
指标:吞吐量,停顿时间,垃圾回收频率
基于这三个指标,我们可能需要调整:
垃圾回收分类
内存区域的大小以及相关策略
堆内存大小、新生代占多少、老年代占多少、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
基于清除算法,会产生内存碎片。
三横跨新生代和老年代的垃圾收集器G1
四、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(阿里开源的诊断工具)涵盖命令并有可视化界面。
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标记为以下三种颜色
白:在开始遍历时,所有对象都为白
灰:被垃圾回收器扫描过,但还有引用没有被扫描,为灰
黑:被垃圾回收器扫描过,并且这个对象的引用也全部被扫描,可存活对象,为黑。
全部扫描过后,回收为白的对象。