JAVA JVM垃圾回收 JVM调优
总所周知,Java中垃圾是由JVM自动回收,而不需要程序员自己动手,这样编码难度确实降低了,但是其回收的性能成为问题
1.什么是垃圾
没有任何引用指向的一个对象或者多个对象(循环引用)会被JVM认为是垃圾
2.如何定位垃圾
1.引用计数法(单个对象):引用的增加和减少都被记录,而引用为0的时候,就认为是垃圾
2.根可达算法(多个对象):以根为起点顺藤摸瓜,能摸到的都不是垃圾。那么Java中那些被认为是根呢?
JVMstack:JVM栈里面的(因为栈里面的东西由操作系统自动回收,所有栈里面还有的对象认为是存活的对象)
native method stack:本地方法栈里面的
runtime constant pool:常量池里面的
static:静态的引用
class:加载到内存中的类(类会在new、getstatic、putstatic、invokestatic时加载到内存中,即new类、获取和设置静态变量、调用静态方法时被加载,加载之后将不会被自动回收,也不再重新加载)
3.垃圾回收算法
1.mark sweep:标记清除法,把堆中的所有垃圾标记出来,然后清除,这个简单也粗暴,因为没有重新整理存储空间,会产生很多碎片
2.copying:拷贝算法,把堆一分为二,存一半,空一半,把非垃圾按顺序copy到空的那一半内存中(内存copy效率非常高),下一次回收时又copy回去。这样不会产生碎片,效率也很高,但是空间浪费
3.mark compact:标记压缩法,相当于一栋楼里面很多房间,一人住一间,把高楼层的人移动到底楼层来住。从高往底找,找到第一个人就放到最底下的第一个空房间或者废弃房间(垃圾房间),一直找下去。这样也没有碎片,空间也不浪费,但是效率低
4.JVM份代模型
JVM把堆内存分为两个大区:新生代和老年代
1.新生代(只用copying算法回收):新生代又分为eden区、两个survivor区,其比例为 8:1:1,eden区存储新进内存的对象,survivor区开始不存东西。为什么要这么分区,因为java中很多东西生命周期都是很短的,比如for循环,下一次循环时上一次的东西就变成垃圾,再比如方法执行完毕,留下的几乎是垃圾,这些都需要频繁的回收,而且会回收大部分的内容,所以就适合用copying算法,把eden区和survivor2区存活的东西,全部放到survivor1区(放不下的直接去老年代),然后把eden区和survivor2区全部清空;第二次回收时,把eden区和survivor1区存活的东西,全部放到survivor2区,这样就能高效的回收垃圾。这也是YGC(新生代垃圾回收,当eden区满了就进行YGC)的策略。
2.老年代(用mark sweep和mark compact算法回收):这个区专门装顽固分子,只有三种情况会进入老年代:某次YGC时survivor区装不下;某次YGC移动了大于survivor区总大小的50%;对象的年龄达到某个标准(对象年龄:一个对象经历了一次YGC而没有被清除,则年龄上涨一岁,经历了15次YGC则为15岁,也就步入老年),根据垃圾回收器不同,步入老年的年龄也不一样(CMS为6岁,其他为15岁)。而老年代只能通过FGC(full GC:全局GC,新老代都要GC,当老年代满了就进行FGC)来回收。
ps:另外eden区,为了防止多线程抢地盘,比如你看上这块区域了,我也看上这块区域了,我们就会抢,一个抢到另一个就会存失败,重新去找地方存;所有eden区会为线程分配独享的空间,自己优先往自己的空间存东西
5.垃圾回收器
1.serial:分为serial和serial old,分别运行在新老生代;串行收集器,单线程叫停型垃圾回收器,它是单线程的,工作时会叫停其他所有线程
2.parallel(注重吞吐量,延长YGC间隔时间):分为parallel scavenge和parallel old,分别运行在新老生代;并行收集器,多线程叫停型垃圾回收器,它是多线程的,可以工作到新生代和老年代,工作时会叫停其他所有线程
3.CMS:多线程并发型垃圾回收器,这个回收器只能工作在老年代(因为新生代效率高,不需要它),它分为四个步骤:
1.CMS介入:叫停所有线程,自己介入,找到根,然后其他线程继续运行(时间很短,找根而已)
2.标记垃圾:根据自己找到的根,并发的去标记垃圾
3.再叫停所有线程,多线程核对垃圾(我是这样理解:根是会变的,增加的根是否指向要删除的垃圾?所以需要核对一次;而如果有的不是垃圾而后面变成垃圾了(浮动垃圾),这问题不大,大不了下一次再来回收,所以不用核对),叫停时间相对parallel old回收器来说也不长,因为只需要点名道姓的核对垃圾,而不是重头排查垃圾
4.并发删除,是MarkSweep算法
4.ParNew(注重相应时间,缩短YGC过程的时间):用在年轻代,parallel的升级版,主要是为了能配合CMS的并行回收(parallel不能配合CMS)
5.G1:新型垃圾回收器,很牛,逻辑上分区,物理上不分区
6.ZGC:新型垃圾回收器,更牛,没有分区
ps:1.CMS用很多问题,以至于没有任何一版本的Java默认是CMS回收器,它删除是用MarkSweep算法,那么就会留下很多碎片,当有一个较大的对象存不进来的时候,它就要清理碎片,然而,它竟然是用serial old来清理,也就是说大家全部别干事了,就看着单线程的serial old来清理,内存越大清理时间越长,有的甚至会停几十个小时
2.Java1.8就能用G1 垃圾回收器了
3.Serial 几十兆,PS 上百兆 - 几个G,CMS - 20G,G1 - 上百G(200ms - 10ms) ,ZGC - 4T - 16T(JDK13)(10ms - 1ms)
6.JVM调优
借助阿里开源工具:Arthas,用法直接看文档:https://alibaba.github.io/arthas/;这玩意中文文档,而且写得很清楚,又开源好用(能把class热部署,也就是发现一个方法有点写错了,直接把类反编译回来,改了编译好,然后拿去替换掉内存中的类)
7.内存泄露和内存溢出
内存溢出 : out of memory 指程序在申请内存时,没有足够的内存空间供其使用
内存泄露 : memory leak 指程序在申请内存后,无法释放已经申请的内存空间
8.JVM内存模型