JVM | 垃圾回收
JVM | 垃圾回收
1. Java垃圾回收的概念
1-1. 如何判断哪些是垃圾对象,引用计数法,根搜索算法
1-2. 哪些是GC Roots
- JVM栈中的引用
- 方法区静态变量的引用
- JNI(即native方法)中的引用
1-3. 方法区(永久代)会发生GC吗,会回收哪些对象?
- 方法区的垃圾回收主要回收两部分内容:废弃常量和无用类
- 废弃常量:常量池中没有被其他地方引用的对象,在GC时,有必要的情况下会被回收
- 无用类:无用的类必须满足三个条件
- 该类的所有实例对象都被回收了,jvm中不存在该类的任何实例
- 加载该类的ClassLoader已经被回收
- 该类的Class对象没有任何引用,没有任何地方通过反射引用该类
- 虚拟机可以对满足上述条件的无用类进行回收,可以通过
-Xnoclassgc
关闭对类的回收(拓展阅读:JVM参数详解) - 在大量使用反射,动态代理,自定义ClassLoader的场景,会有大量的类被创建,需要JVM具备类的卸载功能.
1-4. 什么时候会发生minor GC和full GC
- minorGC时机:
- 当JVM无法为一个新创建的对象分配空间时,会触发minor GC,比如新创建的对象大于Eden区剩余空间
- full GC之前会调用一次minor GC
- 如果设置了
-XX:+CMSScavengeBeforeRemark
参数,CMS就会在重新标记之前执行一次minor GC
- fullGC时机:
- minor GC每次从新生代晋升到老年代的对象的平均大小 > 老年代的剩余空间
- minor GC后新生代存活的对象总大小 > 老年代的剩余空间
-XX:CMSInitiatingOccupancyFaction
cms中可以配置老年代已使用空间超过了这个参数指定的比例,也会触发Full GC- 大对象直接进入老年代,这时对象的大小超过了老年代剩余空间
- 通过
-XX:PretenureSizeThreshold=xxx
可以设置大对象的标准,超过这个值,会被直接放入到老年代,注意这个时候是不会触发minor GC的 - PretenureSizeThreshold参数只对Serial和ParNew两款收集器有效,Parallel Scavenge收集器不认识这个参数,Parallel Scavenge收集器一般并不需要设置。如果遇到必须使用此参数的场合,可以考虑ParNew加CMS的收集器组合
- 通过
- 永久带(PermGen jdk7)或元空间(metadata jdk8)不足会触发full GC
- 永久带和元空间都是Hotspot对方法区的实现方式.
- JDK8中元空间默认最大值为21M,可以通过
-XX:MetaspaceSize=xxM
来设置元空间大小 - 查看JVM初始化参数默认值:
java -XX:+PrintFlagsInitial
2. 垃圾回收器
![](https://img2020.cnblogs.com/blog/1620190/202003/1620190-20200324150413686-1793895584.png)
垃圾回收器
3. 安全点和安全区域
我对于安全点的理解,把GC的过程看成街道消毒的过程,用户线程就是在街上跑步前进的人,安全点就是街道上每隔一段距离设置的一个防空洞,街上的人会有三种状态:跑步(running),在防空洞等待消毒完成(block),在街外面等人(in native),每个人状态修改时,必须将自己的状态写在一个本子上(serialization page内存).消毒的时候,必须确保所有人都已经躲进地下的防空洞中了或者在街外等人.这要怎么做呢,首先我准备消毒的时候,会设置一个信号灯(sync_state),在街上跑步的人每路过一个防空洞,都要看一下这个预备消毒的信号灯有没有亮,如果亮了,就躲进防空洞里面,停止跑步,并修改自己的状态为阻塞(block),等消毒完成后在出来继续跑.在消毒的过程中,街道办事处就不允许人修改自己的状态了(设置serialization page只读).
安全区域就是一开始我在防空洞里面睡觉(线程调用sleep),或者我到街外面等人去了(等待IO),在睡觉或者等待的时候,我会把我的状态修改好(block或者in native),而且在我睡醒或者等到人了之后,在回到街道之前,我要去修改我的状态,如果当前街道还在消毒,办事处不会让我改状态的(serialization page只读),只有等到消毒完成,我才可以重新回到街道.
-
安全点的位置
- 方法返回前
- 调用方法之后
- 抛出异常的位置
- 循环的末尾
- 如果for循环中有明确的循环计数器变量,而且该变量有明确的起始值、终止值、步进长度的循环,它有可能被优化为循环末尾没有safepoint,这种循环被称为'counted loop'于是如果这个循环的循环次数很多、循环体里又不调用别的方法或者是调用了方法但被内联进来了,就有可能会导致进入safepoint非常耗时。
- 解决办法: 把单层循环拆成等价的双重嵌套循环,这样其中一层循环末尾的safepoint就可能会留下来,减少进入safepoint的等待时间。
-
为啥要在这四个地方设置安全点
JVM要等所有应用线程进入到安全点后才会分派GC任务,如果有线程一直没有进入到安全点,就会导致GC的停顿时间延长. -
安全点应用实战
- 生产环境推荐使用
-XX:+PrintGCApplicationStoppedTime
打印JVM停顿时间,如果GC日志前面有较大的停顿,要就要考虑是不是代码里有较大的循环操作. - 在测试环境开启安全点统计日志
-XX:+UnlockDiagnosticVMOptions //diagnostic [ˌdaɪəɡˈnɑstɪk] 诊断的,判断的 -XX:+LogVMOutput -XX:LogFile=/dev/shm/vm.log -XX:+PrintSafepointStatistics //打印安全点分析 -XX:PrintSafepointStatisticsCount=1
- 生产环境推荐使用
-
看看大神是怎么解决问题的 无法进入安全点导致GC停顿时间过长问题
-
推荐 JVM安全点介绍及实战
-
推荐 安全点图解
-
推荐 CMS垃圾收集器详细介绍