文章内容主要是自己学习过程中整理的笔记内容,有些地方可能还不太完善
预备知识
1、JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots?
什么是垃圾:简单说就是内存中已经不再被使用到的空间就是垃圾;
要进行垃圾回收,如何判断一个对象是否可以被回收?
(1)引用计数法:
(2)GC Roots(枚举根节点做可达性分析,即根搜索路径算法)
从GC Roots的对象开始自顶向下进行链路的扫描和访问,如果引用可达,说明对象是活跃的,可存活;否则链路覆盖不到就是引用不可达,被判定为死亡;所以必须从GC Roots的对象开始,就算是其他对象之间互相引用,也是不算的;
Java中那些可以作为GC Roots的对象呢?
(1)虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中引用的对象;
(2)方法区中类静态属性引用的对象;static
(3)方法区中常量引用的对象;static final
(4)本地方法栈中JNI(即一般说的Native方法)中引用的对象;
2、谈谈强引用、软引用、弱引用、虚引用分别是什么?
Book b1=new Book();等号左边叫引用,在栈里面;等号右边叫对象,在堆里面;栈管运行,堆管存储
1、强引用
当内存不足时,JVM开始进行垃圾回收,对于强引用的对象,就算出现OOM也不会堆该对象进行回收,死都不收;因此强引用是造成Java内存泄漏的主要原因之一;
2、软引用SoftReference
当系统内存充足时,它不会被回收;到系统内存不足时,它会被回收;通常用在堆内存敏感的程序,比如高速缓存就有用到软引用;
3、弱引用WeakReference
不管内存是否够,GC一律回收;
WeakHashMap:
HashMap的key=null后,不会被GC回收,但WeakHashMap的key=null后,会被GC回收
4、虚引用PhantomReference
如果一个对象仅仅持有虚引用,那和没有任何引用一样,在任何时候都可能被垃圾回收器回收;他不能单独使用,也不能通过它访问对象,虚引用必须和引用队列联合使用;
主要作用是跟踪对象被垃圾回收的状态,仅仅提供一种机制,通过finalize()方法做某些事情的机制;get()方法总是返回null,因此无法访问对应的引用对象;
5、ReferenceQueue引用队列
弱引用、虚引用在被GC回收之前,会被放到引用队列保存下来;相当于一种通知机制。当关联的引用队列中有数据的时候,意味着引用指向的堆内存中的对象被回收,通过这种方式,JVM允许我们在对象被销毁后,做一些我们自己想做的事;
允许通过finalize()方法在垃圾回收器将对象从内存中清楚出去之前做必要的清理工作;
四种垃圾回收算法
1、引用计数法(不怎么用)
每个地方引用它,计数器值就+1;每当一个引用失效时,计数器值-1;任何时刻计数器值为0的对象就是不可能再被使用的,那么这个对象就是可回收对象;
为什么主流的JVM不使用这种算法?原因:很难解决对象之间的相互循环引用的问题;
缺点:
- 每次对对象赋值时均要维护引用计数器,且计数器本身也有一定的消耗;
- 较难处理循环引用的问题;
2、复制算法(新生代)
复制之后要交换,谁空谁是to,幸存者0区和幸存者1区
流程:复制->清空->互换
优点:没有产生内存碎片
缺点:浪费空间,有些大对象复制起来比较耗时;
3、标记清除(老年代)
过程:分成标记和清除两个阶段,先标记要回收的对象,然后统一回收这些对象;
优点:没有大面积去复制,节约了内存空间
缺点:产生了内存碎片
4、标记压缩(老年代)
过程:第一步标记清除,第二步,再次扫描,并往一端滑动存活对象。产生连续空闲的内存区域;
优点:没有了内存碎片
缺点:需要移动对象的成本,比较耗时间
5、总结:没有完美的算法,所以每代使用最合适的算法,就是GC分代收集算法;
四种垃圾回收器
四种垃圾回收器
1、串行垃圾回收器(Serial)工作中几乎不用
它是单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境;
会导致STW(Stop-the-world):暂停整个应用,时间可能会很长;
2、并行垃圾回收器(Parallel)
多个垃圾收集线程并行工作,此时用户线程是暂停的,适用于科学计算、大数据处理首台处理等弱交互场景。
3、并发垃圾收集器(CMS),也叫并发标记清除ConcMarkSweep
用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行,即一边垃圾回收,一边执行应用程序),不需要停顿用户线程,互联网公司多用它,适用对响应时间有要求的场景;适用于在互联网或B/S系统的服务器,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短;
并发(Concurrent):更为复杂,GC可能会抢占应用的CPU
CMS的过程:
1、初始标记:STW,只是标记一下GC Roots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程;
2、并发标记:进行GC Roots跟踪过程,和用户线程一起,不需要暂停工作线程。主要标记过程,标记全部对象;
3、重新标记:STW,为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程;由于并发标记时,用户线程仍然运行,因此在正式清理之前,再做修正;
4、并发清除:清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清理对象。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户一起并发工作,所以总体上看,CMS收集器的内存回收和用户线程一起执行;
优点:并发收集低停顿;
缺点:并发执行,对CPU资源压力大;采用标记清除算法会导致大量碎片;
七种GC具体算法实现与JVM对应参数
(1)UseSerialGC
对应JVM参数:-XX:+UseSerialGC
开启后会使用:Serial(Young区用)+SerialOld(Old区用)的收集器组合
表示:新生代和老年代都会使用串行垃圾收集器,新生代使用复制算法,老年代使用标记-整理算法
(2)UseSerialOldGC(被废弃)
理论知道即可,实际中已经被优化掉了,没有了,不推荐
(3)UseParallelGC
对应JVM参数:-XX:+UseParallelGC
(默认)或-XX:+UseParallelOldGC
,可互相激活
表示:串行收集器在新生代和老年代的并行化,两个代都用并行,新生代用Parallel,老年代用ParallelOld
(4)UseParallelOldGC,可互相激活
对应JVM参数:-XX:+UseParallelOldGC
表示:两个代都用并行,新生代用Parallel,老年代用ParallelOld
(5)UseConcMarkSweepGC
对应JVM参数:-XX:+UseConcMarkSweepGC
,
表示:开启后会自动将-XX:+UseParNewGC打开,使用ParNew(Young区用)+CMS(Old区用)+Serial Old收集器组合,Serial Old作为CMS出错的后备服务器;
(6)UseParNewGC
对应JVM参数:-XX:+UseParNewGC
,不推荐
表示:会使用ParNew(Young区用)+Serial Old()的收集器组合,只影响新生代的收集,不影响老年代,新生代使用复制算法,老年代使用标记-整理算法;这种组合可以用,但是已经不推荐了;
怎么查看服务器默认的垃圾收集器是哪个?生产上怎么配置垃圾收集器?
使用命令java -XX:+PrintCommandLineFlags -version
,得出默认是-XX:+UseParallelGC
# 使用命令java -XX:+PrintCommandLineFlags -version,得出默认是-XX:+UseParallelGC
java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=525931008 -XX:MaxHeapSize=8414896128 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_311"
Java(TM) SE Runtime Environment (build 1.8.0_311-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.311-b11, mixed mode)
如何选择合适的垃圾收集器?
一般生产环境都为多CPU,追求低停顿时间,需要快速响应如互联网应用
所以选择-XX:+UseConcMarkSweepGC
G1(Garbage-First)垃圾收集器
上面没有介绍G1,只介绍了其他三种,下面具体介绍GC垃圾收集算法
横跨两层,garbage-first heap+Metaspace
于java1.7出现,于Java9将G1作为默认的垃圾收集器,以代替Java8之前的CMS;是一款面向服务端应用的收集器,主要应用于多CPU和大内存服务器环境下,极大减少垃圾回收的停顿时间,全面提升服务器性能;
JVM参数:-XX:+UseG1GC
G1同比CMS相比,在以下方面更加出色:
(1)G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片;而CMS还存在内存碎片问题;
(2)G1的STW更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间;
通过JVM参数:-XX:MaxGCPauseMillis=n
:设置GC最大停顿时间,这是个软目标,JVM将尽量保证不超过这个值
特点:
(1)G1能充分利用多CPU、多核环境硬件优势,尽量缩短STW;
(2)G1整体上采用标记-整理算法,局部通过复制算法,不会产生内存碎片;
(3)宏观上看G1之中不再区分年轻代和老年代,把整个堆内存划分成多个独立的子区域(Region),可以近似的理解为一个棋盘;在堆的使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可;每个分区也不会固定的为某个代服务,可以按需在年轻代和老年代之间切换。启动时通过-XX:G1HeapRegionSize=n
指定分区大小(1MB-32MB,且必须是2的幂),默认将堆划分为2048个分区;也即能够支持的最大内存为:32MB*2048=64G内存
(4)G1虽然是分代收集器,但整个内存分区不存在物理上的年轻代和老年代的区别,G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;
最大好处:化整为零,避免全内存扫面,只需要按照区域来进行扫描即可;
文章完!!!希望我的文章对大家能有所帮助!!!