2019/4/2 星期二
深入研究java gc
引出问题和小结!
小结:
1、为什么使用CMS gc回收算法?
//答:
因为在CMS gc算法执行的6个步骤中,只有在第一步(初始标记(STW Initial Mark))和第四步重新标记阶段(STW REMARK)才会暂停整个应用,这样对应用程序所带来的影响非常小,缺点是产生内存碎片过多
2、那CMS GC策略如何导致内存碎片过多?
//答:是因为第二步并发标记(concurrent marking)与回收线程会与应用程序争抢CPU资源,容易产生内存碎片,其二:CMS算法在标记清理之后并没有重新压缩分配存活对象,因此整个老生代会产生很多的内存碎片。
3、那为什么CMS gc策略会耗时比较长呢?
//答:
‘stop-the-world’暂停时间也很短暂,耗时较长的(第二步并发标记)标记和(第三步并发预清理)清理都是并发执行的。
内存碎片过多如何触发Full GC?
//答:
CMS并不是很完美,它会在两种场景下产生严重的Full GC(Concurrent Failure(并发失败),Promotion Failure (促销失败))
具体见:老年代 CMS gc回收算法 对hbase的影响 https://blog.51cto.com/12445535/2373206
HBase在演进的道路上又如何不断优化CMS GC?
具体内容见下详细介绍:
题外话:什么是java程序的执行流程;java运行时数据区;java的内存管理 见如下图:
java程序执行流程:
java运行时数据区:
java的内存管理:
在我们(运行时数据区)之中,内存的分配一共有五块:
1、堆内存(Heap):保存真正的程序的数据的部分;
2、&&&栈内存(Stack):保存堆内存地址、还保存基本数据、方法的执行;(所有的数据都在栈内存之中)
3、方法区:保存所有方法的具体的操作,该区域属于共享;
4、程序计数器:这是一块很小的内存,小到几乎可以忽略的地步,只是做一个程序执行顺序的记录,只是为了标记我们下一步要执行的代码的顺序号;
5、本地方法栈:该栈之中所保存的都是操作系统的原生函数。
我们关心的主要是堆内存、栈内存、方法区
在整个的JVM内存组成过程之中,(栈内存)是一个非常重要的概念,因为在该内存之中,他需要保存的数据是一组内容,
因为所有的方法在进行递归调用的时候都会采用栈的模式。观察递归问题中满栈的原因取决于服务器内存的大小。
内存操作有关的两类异常
stackOverFlowError(栈溢出):如果请求的栈的深度过大,虚拟机可能会抛处。
OutOfMemoryError(内存溢出):如果虚拟机的实现中允许、虚拟机栈动态扩展,当内存不足以扩展栈的时候,会抛出。【内存被沾满,更多情况下表示堆也分配不了了】
实际上上面只是观察到了两类可能出现错误的代码,但是并不是意味着栈中只能够保存一下基本的信息,实际上栈里面保存同样是一组的数据。
总结:
1、造成stackOverFlowError(栈溢出)OutOfMemoryError(内存溢出)的原因是;
2、在JVM栈内存中保存有栈幁的概念,所有的栈内存采用先进后出的数据结构来进行我们的存储。
首先需要了解一下什么是java的堆内内存划分
在实际情况下:java 堆内存划分分为了(jdk1.8以前和jdk1.8之后)【对于这2者的区别,我们后面介绍】
jvm堆内存划分(jdk1.8以前):
jvm堆内存划分(jdk1.8之后):
java堆内存模型
java的垃圾收集主要指的是java堆内存空间,那么在每一次执行GC的时候需要区分出那些堆内存空间需要被回收,那些不应该被回收。 所以为了整个的回收处理方便,JVM将堆内存分为如下的几个组成部分。而这几个组成部分你还需要去考虑JDK的版本,现在的JVM内存划分就必须考虑JDK1.8以前和JDK1.8之后的问题了。
如果简化点来理解的话:
1、新生代:那些刚刚创建的对象,刚刚创建的对象有可能会存在有许多垃圾对象,那么这些对象应该是被优先回收的;
2、老年代:老不死的那类对象,经过了很多次的清理之后你发现该对象依然有用,
3、永久代:intern()方法进行入池的对象实际上就在永久代中,永久代不会被回收。因为其本身属于一个bug性的存在(也就是jdk崩溃了,死了永久代CIA能消失),所以在jdk1.8之后,将其更换为元空间(就是电脑的直接内存)。
举个例子:我电脑有100G内存,80G给了堆内存,那剩下的20G就可以给元空间。
在整个内存的组成过程之中,每一代的内存空间都会有一个伸缩区,那么该区域就可以由JVM根据空间的使用情况,动态扩充。
当我们适当合理的设置了伸缩区的内存大小之后,那么就可以得到良好的性能提升。也就是说最容易的性能提升就是改变伸缩区的内存大小。
首先什么是java gc 、java对象创建流程
java对象创建流程如图:
1、大多数内存对象要么生存周期比较短,很快就会没人引用,比如处理RPC请求的buffer可能只会生存几微秒;
2、要么生存周期比较长,比如Block Cache中的热点Block,可能就会生存几分钟,甚至更长时间。
3、基于这样的事实,JVM将整个堆内存分为两个部分:新生代(young generation)和老生代(tenured generation),除此之外,JVM还有一个非堆内存区-Perm区,主要存放class信息以及其他meta元信息,
4、其中Young区又分为Eden区和两个Survivor 区:S0和S1。
5、一个内存对象在创建之后,首先会为其在新生代申请一块内存空间,如果这个对象在新生代存活了很长时间,会将其迁移到老生代。
6、在大多数对延迟敏感的业务场景下(比如HBase),建议使用如下JVM参数,-XX:+UseParNewGC和XX:+UseConcMarkSweepGC,其中前者表示对新生代执行并行的垃圾回收机制,而后者表示对老生代执行并行标记-清除垃圾回收机制。
7、可见,JVM允许针对不同内存区执行不同的GC策略。
//在 cdh中默认是这样设置的
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled
接下来重点先讨论一下年轻代
年轻代GC实现复制算法:(年轻代GC策略 – Parallel New Collector)
1、对象初始化之后会被放入Young区,更具体的话应该是Eden区,当Eden区满了之后,会进行一次GC。
2、GC算法会检查所有对象的引用情况,如果某个对象还有被引用,表示该对象存活。
3、检查完成之后,会将这些存活的对象移到S0区,并且回收整个Eden区空间,称为一次Minor GC;
4、接着新对象进来,又会放入Eden区,满了之后会检查S0和Eden区存活的对象,将所有存活的对象移到S1区,再回收整个S0和Eden区空间;
5、很容易理解,S0和S1两个区总会有一个区是预留给下次存放存活对象用的。
这种算法称为复制算法,对于这种算法,有两点需要关注:
- 算法会执行’stop-the-world’暂停,但时间非常短。因为Young区通常会设置的比较小(一般不建议不超过512M),而且JVM会启动大量线程并发执行,一次Minor GC一般都会在几毫秒内完成
- 不会产生碎片,每次GC之后都会将存活的对象放入连续的空间(S0或S1)
内存中所有对象都会维护一个计数器,每次Minor GC移动一个对象之后,都会为这个对象的计数器加一。当计数器增加到一定阈值之后,算法就会认为该对象生命周期很长,会将其移入老生代。该阈值可以通过JVM参数XX:MaxTenuringThreshold指定。
提高了解篇
年轻代优化算法
年轻代内存调整参数(重要):
接下来深度研究老年代
什么是老年代 和老年代的full gc:
老生代GC策略 – Concurrent Mark-Sweep(CMS算法)
什么是CMS 为什么CMS?
1、每次执行Minor GC之后,都会有部分生命周期较长的对象被移入老生代,一段时间之后,老生代空间也会被占满。
2、此时就需要针对老生代空间执行GC操作,此处我们介绍Concurrent Mark-Sweep(CMS)算法。
CMS算法整个流程分为6个阶段,其中部分阶段会执行 ‘stop-the-world’ 暂停,部分阶段会和应用线程一起并发执行:
如图:
老年代执行CMS过程:
相应的,对于CMS算法,也需要关注两点:
- ‘stop-the-world’暂停时间也很短暂,耗时较长的标记和清理都是并发执行的。
- CMS算法在标记清理之后并没有重新压缩分配存活对象,因此整个老生代会产生很多的内存碎片。
提高篇
老年代标记清除算法:
老年代标记压缩算法:
老年代内存调整参数:
永久代调整参数:
元空间调整参数:
可用gc方式小结:
年轻代串行GC(copy)
年轻代并行回收GC
年轻代并行GC
老年代串行GC
老年代并行GC
常用gc策略:
GC调整策略、
收集器参数设置
G1收集器介绍:
转载于:https://blog.51cto.com/12445535/2372976