JVM性能优化------垃圾回收算法

在前面的文章JVM性能优化------可达性分析算法与四种引用,提到过四种算法。但是只是提了一下概念。今天我们来具体理解一下。

回收算法
先提及一个最大的误区,引用计数算法,在java并没有使用,而是python的回收算法!!!
好了,现在我们看看java的四种回收算法。

  1. 标记清除算法
    在这里插入图片描述
    我们可以看到堆内存中有A、B、C、D、E、F六个对象,当B、C、E对象不再被引用的时候,那么会被直接标记,在需要回收时,进行回收。我们可以看到右边的堆内存的结构,幸存的对象的地址不是连续的,那么会产生碎片化的问题,一旦添加新的对象,那么就必须要按照碎片的大小来计算是否可以存储。而不是按照总剩余空间来计算是否可以存储。
    总结:
    算法实现简单;但空间不连续、空间利用率不高、容易产生碎片化

  2. 标记整理算法
    在这里插入图片描述
    可以看到这个算法和标记清除算法有一点点变化,那就是三个幸存对象的内存地址是连续的。这样很好的解决了碎片化的问题。但是D、F的内存地址发生了变化,所以就需要改变引用地址,那么在更改引用地址的过程中,用户的线程在这一段时间里必须要睡眠,因为这段时间无法读取内存信息。
    总结:
    空间具有连续性、不会产生碎片化的问题;但是对象的引用内存地址有可能会发生变化。Stop-the-World,触发回收机制时,可能会暂停用户的线程

  3. 标记复制算法
    在这里插入图片描述
    可以看到这张图片并不是变化了,而是一个堆内存变成了两个小的堆内存,一旦启动回收机制,那么左边不需要回收的对象,就会被复制到右边的内存中,并清空右边的内存,所以这样也保证了空间的连续性,但是,它把内存一分为二,利用空间来换取时间。
    总结:
    效率比较高,可以保证空间连续性;以空间换时间

  4. 分代算法
    在这里插入图片描述
    在这里我们就可以看到这里分为了两个区,新生代、老年代。
    我们先了解新生代,可以发现在里面他有分为了3个区块eden、s0、s1。
    那为什么需要三个区域呢?eden区主要是存放刚创建对象,如果此区满了,则幸存的对象则会晋升到s0区。如果s0区满了,则幸存的对象则会晋升到to区。s0和s1区大小一样,主要使用标记复制算法,在新生代使用标记复制算法,主要是因为新生代的回收频繁,三个区的默认占比为8:1:1。
    老年代区。存放条件:在新生代的对象被引用达到一定的阈值,则放到老年代
    或者存放大于新生代总内存的对象。在触发FullGC会把MinorGC也触发。
    新生代与老年代的存储空间比例为1:2

那么我们经常提到的内存溢出
原因:是因为存放对象空间大于老年代可用的存储空间。

说了这么多,那么什么时候触发垃圾回收机制呢?
当新生代或者老年代内存满的情况下,开始触发垃圾回收

GC核心参数

  1. -Xms
    初始大小内存,默认为物理内存 1/64,等价于 -XX:InitialHeapSize
  2. -Xmx
    最大分配内存,默认为物理内存的 1/4,等价于 -XX:MaxHeapSize
  3. -Xss
    设置单个线程栈的大小,一般默认为 512-1024k,等价于 -XX:ThreadStackSize
  4. -Xmn
    设置年轻代的大小
    整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小
    持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
  5. -XX:MetaspaceSize
    设置元空间大小
    元空间的本质和永久代类似,都是对 JVM 规范中的方法区的实现。
    元空间与永久代之间最大区别:元空间并不在虚拟机中,而是使用本地内存
    因此默认情况下,元空间的大小仅受本地内存限制,元空间默认比较小,我们可以调大一点
  6. -XX:+PrintGCDetails
    输出详细GC收集日志信息
  7. -XX:SurvivorRatio
    设置新生代中 eden 和 S0/S1 空间比例,默认 -XX:SurvivorRatio=8,Eden : S0 : S1 = 8 : 1 : 1
  8. -XX:NewRatio
    配置年轻代和老年代在堆结构的占比,默认 -XX:NewRatio=2 新生代占1,老年代占2,年轻代占整个堆的 1/3
  9. -XX:MaxTenuringThreshold
    设置垃圾最大年龄

GC日志参数分析
代码:

import sun.misc.Launcher;

import java.util.*;

/**
 * @author 龙小虬
 * @date 2021/4/15 21:17
 * -Xms20m -Xmx20m  -XX:+PrintGCDetails -verbose:gc
 */
public class Test01 {
    public static void main(String[] args) {

        ArrayList<Object> objects = new ArrayList<>();
        objects.add(new byte[12 * 1024 * 1024]);
    }
}

设置最大和初始内存均为20M。
看一下内存信息。
在这里插入图片描述
这里我们可以看到新生代和老年代分别为6656K,13824K,两者比例差不多为1:2,这也验证力王我们前面说的概念,在新生代中,eden:from(s0):to(s1) = 11:1:1???这个问题有待研究。但是我们可以看到老年代的内存占比达到88%,这样也证明了,大对象直接存入了老年代。
我们再来看看这样一段代码:

import sun.misc.Launcher;

import java.util.*;

/**
 * @author 龙小虬
 * @date 2021/4/15 21:17
 * -Xms20m -Xmx20m  -XX:+PrintGCDetails -verbose:gc
 */
public class Test01 {
    public static void main(String[] args) {

        ArrayList<Object> objects = new ArrayList<>();
        objects.add(new byte[12 * 1024 * 1024]);
        objects.add(new byte[15 * 1024 * 1024]);
    }
}

第一次的Add(),存入是内存足够的。第二次需要进行垃圾回收。
在这里插入图片描述
可以看到发生了两次的Full GC 回收,我么来看一个。

[Full GC (Allocation Failure) 
[PSYoungGen: 0K->0K(6144K)] 
[ParOldGen: 12919K->12901K(13824K)] 12919K->12901K(19968K), 
[Metaspace: 3294K->3294K(1056768K)], 0.0060761 secs] 
[Times: user=0.00 sys=0.00, real=0.01 secs] 

当发生老年代GC时,也会触发新生代GC,新生代堆回收前占用0K,回收后占用0K。ParOldGen 老年代回收前占用125919K,回收后占用12901K。Times表示用时
在这里插入图片描述
最后因为内存不够的原因,发生了内存溢出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙小虬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值