深入理解JVM-内存模型(jmm)和GC的几个疑问

几个疑问

1.GC是怎么判断对象是被标记的

通过枚举根节点的方式,通过jvm提供的一种oopMap的数据结构,简单来说就是不要再通过去遍历内存里的东西,而是通过OOPMap的数据结构去记录该记录的信息,比如说它可以不用去遍历整个栈,而是扫描栈上面引用的信息并记录下来。

总结:通过OOPMap把栈上代表引用的位置全部记录下来,避免全栈扫描,加快枚举根节点的速度,除此之外还有一个极为重要的作用,可以帮HotSpot实现准确式GC【这边的准确关键就是类型,可以根据给定位置的某块数据知道它的准确类型,HotSpot是通过oopMap外部记录下这些信息,存成映射表一样的东西】。


2.什么时候触发GC

简单来说,触发的条件就是GC算法区域满了或将满了。


minor GC(young GC):当年轻代中eden区分配满的时候触发[值得一提的是因为young GC后部分存活的对象会已到老年代(比如对象熬过15轮),所以过后old gen的占用量通常会变高] 

full GC: 

①手动调用System.gc()方法 [增加了full GC频率,不建议使用而是让jvm自己管理内存,可以设置-XX:+ DisableExplicitGC来禁止RMI调用System.gc] 

②发现perm gen(如果存在永久代的话)需分配空间但已经没有足够空间 

③老年代空间不足,比如说新生代的大对象大数组晋升到老年代就可能导致老年代空间不足。 

④CMS GC时出现Promotion Faield[pf] 

⑤统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间。这个比较难理解,这是HotSpot为了避免由于新生代晋升到老年代导致老年代空间不足而触发的FUll GC。比如程序第一次触发Minor GC后,有5m的对象晋升到老年代,姑且现在平均算5m,那么下次Minor GC发生时,先判断现在老年代剩余空间大小是否超过5m,如果小于5m,则HotSpot则会触发full GC(这点挺智能的)


Promotion Faield:minor GC时 survivor space放不下[满了或对象太大],对象只能放到老年代,而老年代也放不下会导致这个错误。Concurrent Model Failure:cms时特有的错误,因为cms时垃圾清理和用户线程可以是并发执行的,如果在清理的过程中 

可能原因:

1 cms触发太晚,可以把XX:CMSInitiatingOccupancyFraction调小[比如-XX:CMSInitiatingOccupancyFraction=70 是指设定CMS在对内存占用率达到70%的时候开始GC(因为CMS会有浮动垃圾,所以一般都较早启动GC)]

2 垃圾产生速度大于清理速度,可能是晋升阈值设置过小,Survivor空间小导致跑到老年代,eden区太小,存在大对象、数组对象等情况

3.空间碎片过多,可以开启空间碎片整理并合理设置周期时间


full gc导致了concurrent mode failure,而不是因为concurrent mode failure错误导致触发full gc,真正触发full gc的原因可能是ygc时发生的promotion failure。


3.cms收集器是否会扫描年轻代


会,在初始标记的时候会扫描新生代。


虽然cms是老年代收集器,但是我们知道年轻代的对象是可以晋升为老年代的,为了空间分配担保,还是有必要去扫描年轻代。


4.什么是空间分配担保

在minor gc前,jvm会先检查老年代最大可用空间是否大于新生代所有对象总空间,如果是的话,则minor gc可以确保是安全的,


如果担保失败,会检查一个配置(HandlePromotionFailire),即是否允许担保失败。

如果允许:继续检查老年代最大可用可用的连续空间是否大于之前晋升的平均大小,比如说剩10m,之前每次都有9m左右的新生代到老年代,那么将尝试一次minor gc(大于的情况),这会比较冒险。

如果不允许,而且还小于的情况,则会触发full gc。【为了避免经常full GC 该参数建议打开】

这边为什么说是冒险是因为minor gc过后如果出现大对象,由于新生代采用复制算法,survivor无法容纳将跑到老年代,所以才会去计算之前的平均值作为一种担保的条件与老年代剩余空间比较,这就是分配担保。

这种担保是动态概率的手段,但是也有可能出现之前平均都比较低,突然有一次minor gc对象变得很多远高于以往的平均值,这个时候就会导致担保失败【Handle Promotion Failure】,这就只好再失败后再触发一次FULL GC,


5.为什么复制算法要分两个Survivor,而不直接移到老年代

这样做的话效率可能会更高,但是old区一般都是熬过多次可达性分析算法过后的存活的对象,要求比较苛刻且空间有限,而不能直接移过去,这将导致一系列问题(比如老年代容易被撑爆)

分两个Survivor(from/to),自然是为了保证复制算法运行以提高效率。


6.各个版本的JVM使用的垃圾收集器是怎么样的

准确来说,垃圾收集器的使用跟当前jvm也有很大的关系,比如说g1是jdk7以后的版本才开始出现。

并不是所有的垃圾收集器都是默认开启的,有些得通过设置相应的开关参数才会使用。比如说cms,需设置(XX:+UseConcMarkSweepGC)

这边有几个实用的命令,比如说server模式下


#UnlockDiagnosticVMOptions解锁获取jvm参数 PrintFlagsFinal用于输出xx相关参数,以Benchmark类测试,这边会有很多结果 大都看不懂 - - 在这边查(usexxxxxxgc会看到jvm不同收集器的开关情况) 

java -server -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal Benchmark


#后面跟| grep ":"获取已赋值的参数[加:代表被赋值过] 

java -server -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal Benchmark| grep ":" 


#获得用户自定义的设置或者jvm设置的详细的xx参数和值 

java -server -XX:+PrintCommandLineFlags Benchmark

深入理解JVM-内存模型(jmm)和GC的几个疑问

深入理解JVM-内存模型(jmm)和GC的几个疑问


7 stop the world具体是什么,有没有办法避免

stop the world简单来说就是gc的时候,停掉除gc外的java线程。


无论什么gc都难以避免停顿,即使是g1也会在初始标记阶段发生,stw并不可怕,可以尽可能的减少停顿时间。


8 新生代什么样的情况会晋升为老年代

对象优先分配在eden区,eden区满时会触发一次minor GC


对象晋升规则

1 长期存活的对象进入老年代,对象每熬过一次GC年龄+1(默认年龄阈值15,可配置)。

2 对象太大新生代无法容纳则会分配到老年代

3 eden区满了,进行minor gc后,eden和一个survivor区仍然存活的对象无法放到(to survivor区)则会通过分配担保机制放到老年代,这种情况一般是minor gc后新生代存活的对象太多。

4 动态年龄判定,为了使内存分配更灵活,jvm不一定要求对象年龄达到

MaxTenuringThreshold(15)才晋升为老年代,若survior区相同年龄对象总大小大于survior区空间的一半,则大于等于这个年龄的对象将会在minor gc时移到老年代


8.怎么理解g1,适用于什么场景


G1 GC 是区域化、并行-并发、增量式垃圾回收器,相比其他 HotSpot 垃圾回收器,可提供更多可预测的暂停。增量的特性使 G1 GC 适用于更大的堆,在最坏的情况下仍能提供不错的响应。G1 GC 的自适应特性使 JVM 命令行只需要软实时暂停时间目标的最大值以及 Java 堆大小的最大值和最小值,即可开始工作。

g1不再区分老年代、年轻代这样的内存空间,这是较以往收集器很大的差异,所有的内存空间就是一块划分为不同子区域,每个区域大小为1m-32m,最多支持的内存为64g左右,且由于它为了的特性适用于大内存机器。

深入理解JVM-内存模型(jmm)和GC的几个疑问


适用场景:

1.像cms能与应用程序并发执行,GC停顿短【短而且可控】,用户体验好的场景。

2.面向服务端,大内存,高cpu的应用机器。【网上说差不多是6g或更大】

3.应用在运行过程中经常会产生大量内存碎片,需要压缩空间【比cms好的地方之一,g1具备压缩功能】。


参考博文:https://www.jianshu.com/p/76959115d486



上一篇:java中的mvc和三层结构究竟是什么关系


下一篇:MongoDB秒级备份恢复(SDCC上海站数据库核心技术与应用实战峰会分享PPT)