看懂了gc日志、可能心中还有小小的疑惑?
为什么需要垃圾回收
在内存的动态分配和回收机制十分成熟的情况下、仍然没办法避免排查各种内存溢出、内存泄露的问题, 当垃圾收集称为系统达到更高并发量的瓶颈时、就需要了解gc的细节、对gc进行必要的监控和调节
程序计数器、虚拟机栈、本地方法栈 随线程运行结束而消亡、这几个区域的内存分配和回收具有确定性、不需要过多的考虑内存回收问题
而Java堆和方法区、则在运行时才知道要创建的对象、内存的分配是动态的、垃圾回收主要关注的是这部分的内存
如何判断对象已死?
- 引用计数法
给对象添加一个引用计数器、当有新的引用时、计数器的值+1, 引用失效是、计数器的值-1, 计数器的值为0的对象就是不再有引用的对象
这种方式的判断效率很高、实现也比较简单、但是不太好判断 循环引用的问题
A->B, B->A
- 可达性分析
通过GC Roots
作为分析的起点、从这些节点向下搜索、若一个对象通过任意引用链不可达、则会被判断为可回收对象
1 | 可作为 `gc root` 的对象: |
为什么gc的过程要stw ?
为了避免分析过程中对象的引用关系还在不断的发生变化、导致分析结果的准确性无法保证
系统停顿下来后如果得知对象引用关系呢 ?
在hotspot的实现中、有一个OopMap的数据结构、在类加载的时候, 它就把对象内什么偏移量位置上是什么类型的数据计算出来、在jit编译的过程中也会在特定的位置记录桟和寄存器哪些位置是引用、这样gc扫描时就可以直接得到了
但是、为什么是特定位置 ?
如果每条指令都生成对应OopMap、会需要大量的额外空间、所以只是在特定位置(安全点safepoint
)记录这些信息notice
: 设置标记等待线程运行到安全点再gc解决了大部分问题、但如果线程是sleep
或者block
状态呢 ?线程无法响应jvm的中断请求、运行到安全点去中断挂起、jvm也不可能得到线程被重新分配CPU时间、so、有了安全区域
-safe region
来解决safe region
: 在一个代码片段之内、引用关系不会发生变化、在这个区域内的任意位置进行gc都是安全的,线程在进入安全区域的时候、会设置自己进入安全区域的标记、jvm在gc时、会忽略这类线程
在线程离开安全区域时会检测系统是否已经完成了gc、ok则可以继续运行、否则需要等待可以离开安全点的信号
晋升为老年代的判断?
- 对象优先在Eden区分配、若Eden区无足够的空间、虚拟机会发起一次Minor GC, 经过一次Minor gc、
age
会+1
、达到指定age(-XX:MaxTenuringThreshold指定
)时、对象会进入老年代- Minor gc时、无法放入
survivor
区的对象、会通过分配担保机制
提前转移到old generation
- 大对象直接进入老年代, 为避免提前触发gc 大于
-XX:PreTenureThreshold
指定值得对象会提前进入直接在老年代分配- 若survivor区 相同年龄的所有对象的大小的总和>survivor区的一半、则 age >= 该age值得对象可以直接进入老年代、无需等到
-XX:MaxTenuringThreshold
指定年龄值