垃圾回收-浅谈JVM垃圾算法
1. 垃圾判断
1.1 引用计数法
每个堆中的对象都有一个引用计数,当一个对象被创建时候,某一个变量引用指向了这个对象的实例.那么这个对象记数就是1. 当其他变量引用也指向了该对象那么就+1. 如果其他变量改变或者超出存活, 那么这个记数就-1. (有一个很明显的缺点, 当对象出现循环引用时, 那么这个对象引用记数永远不会变为0)
1.2 可达分析法
这个垃圾判断, 会从根集(GC ROOT)出发. 然后寻找调用节点. 找到节点之后, 继续往下找, 依此类推. 那么整条调用链一定是不能被回收的, 相反的, 没有被链到就可以被回收.
那么哪些可以作为根集呢:
(一) 虚拟机栈中的引用对象
(二) 方法区中的静态属性引用的对象
(三) 方法区中常量引用的对象
(四) 本地方法区JNI 的引用对象
2. 回收算法
2.1 标记清除. 先标记出可回收的对象, 然后进行清除. 如下图, 缺点也很明显, 会清除后会出现大量的内存碎
2.2 标记复制. 先标记出可回收的对象, 然后把存活的对象复制到另一侧. 这种情况下, 一般需要两个内存空间配合使用, 类似与分代中的from区和to区.
2.3 标记整理. 先标记出可回收对象, 然后先把不回收对象移动到一端, 再进行回收. 注意这个顺序.
2.4 分代算法. 这种回收算法, 一般将内存空间分为年轻代, 年老代. 堆区之后还有一个元空间(永久代).
年轻代又分为, 一个Eden区, 两个Survivor区, 也就是from区和to区. 特点: 每次young gc时, 会有大量对象回收, 所以这里回收自然就用到了copying, 复制算法.
2.41 许多文章上讲,eden from to的比例是8:1:1 其实默认分配并不是, 先用jmap -heap pid, 如下图
我们发现 比例并不是8:1:1, 如果想用这个比例, 需要设置两个参数.
(一) 关闭内存分配策略自适应(默认开启), -XX:-UseAdaptiveSizePolicy
(二) 手动设置Eden区与Survivor区比例,-XX:SurvivorRatio=8
2.42 年轻代进入年老代是15次, 那么为什么不是20次?
Hotspot提供的mark word 我们可以看到分代年龄标识位,是4个bit
1111转成十进制 (1222) + (122) + (12) + (1*2^0) = 15
年老代, 是年轻代存活一定次数时, 进到年老代的. 所以每次回收, 都是少量对象进行回收, 那么这里就用到了mark-Compact 标记整理算法.
元空间, 之前的博文提到过, 主要就是存放了class文件信息, 静态变量等等
(图中的永久代 是1.8之前的存在形式 1.8及之后 元空间替代了永久代)
3. 垃圾收集器
3.1 Serial(标记复制) 串行 专注于年轻代的回收 GC时需要程序暂停.
(一)指定该收集器参数: -XX:+UseSerialGC
3.2 Serial Old(标记整理) , serial 老版本, 收集时, 需要暂停程序, 底层是标记整理算法.
3.3 ParNew.(复制) Serial多线程版本(年轻代 复制算法 年老代 标记整理算法), 唯一可以和CMS搭配使用的收集器.
(一)指定CMS收集器: -XX:+UseConcMarkSweepGC 指定CMS后 会默认使用ParNew作为年轻代收集器
(二)指定使用ParNew收集器: -XX:+UseParNewGC
(三)指定使用的线程数量, 默认CPU数量: -XX:ParallelGCThreads
3.4 Parallel收集器(复制), 一个专注于吞吐量的收集器.
(一)代码运行时间 / (代码运行时间 + 垃圾收集时间) 从这个公式可以看出来 代码运行时间和垃圾收集器时间 是影(二)响结果的主要参数, 并且这是一个收集器, 那么应该放在垃圾收集器时间这个点上.
(三)这个吞吐量越大越好, 那么也就是垃圾收集时间越小越好.
(四)-XX:MaxGCPauseMillis: GC时间的毫秒数
(五)-XX:GCTimeRatio GC时间占比
(六)一般来讲 如果用到了以上两个参数做限制, -XX:+UseAdaptiveSizePolicy那么一般需要这个参数告诉JVM, 动态变化年轻代的空间大小.
3.5 Parallel Old(复制) 旧版本
3.6 CMS收集器(标记-清除) 后续博客单独写
3.7 G1收集器 后续博客单独写
本文地址:https://blog.csdn.net/weixin_45657738/article/details/109779897