【深入理解JVM】垃圾收集器&内存分配策略

目录

gc root对象有哪些

oopMap

安全点(safe point)

安全区域

卡表

伪共享问题

三色标记法

垃圾收集器

CMS

G1


gc root对象有哪些

  • 虚拟机栈中引用的对象(虚拟机栈中的引用的对象可以作为GC Root。我们程序在虚拟机的栈中执行,每次函数调用调用都是一次入栈。在栈中包括局部变量表和操作数栈,局部变量表中的变量可能为引用类型(reference),他们引用的对象即可作为GC Root。不过随着函数调用结束出栈,这些引用便会消失。)
  • 方法区中类静态属性引用的对象(简单的说就是我们在类中使用的static声明的引用类型字段)
  • 方法区中常量引用的对象(简单的说就是我们在类中使用final声明的引用类型字段)
  • 本地方法栈中引用的对象(就是程序中native本地方法引用的对象)
  • Java虚拟机内部的引用(类型对应的Class对象,常驻的异常对象等等)
  • 所有被synchronized持有的对象
  • 反映 Java 虚拟机内部情况的 JMXBean、JVMTI中注册的回调、本地代码缓存等等

oopMap

oop (Ordinary Object Pointer) 普通对象指针,oopmap就是存放这些指针的map,OopMap 用于枚举 GC Roots,记录栈中引用数据类型的位置。迄今为止,所有收集器在根节点枚举这一步骤都是必须暂停用户线程的

一个线程为一个栈,一个栈由多个栈桢组成,一个栈桢对应一个方法,一个方法有多个安全点。GC发生时,程序首先运行到最近的一个安全点停下来,然后更新自己的OopMap,记录栈上哪些位置代表着引用。枚举根节点时,递归遍历每个栈桢的OopMap ,通过栈中记录的被引用的对象内存地址,即可找到这些对象(GC Roots)

可以避免全栈扫描,加快枚举根节点的速度;可以帮助HotSpot实现准确式GC

安全点(safe point)

HotSpot JVM不仅在 GC 的时候使用 safepoints,还有许多其他操作也使用 safepoints。特别是当有清理任务要做时,它会周期性地停止 Java 线程。

通过oopMap 可以快速进行GCROOT枚举,但是随着而来的又有一个问题,就是在方法执行的过程中, 可能会导致引用关系发生变化,那么保存的OopMap就要随着变化。如果每次引用关系发生了变化都要去修改OopMap的话,这又是一件成本很高的事情。所以这里就引入了安全点的概念。

并不需要一发生改变就去更新这个映射表。只要这个更新在GC发生之前就可以了。所以OopMap只需要在预先选定的一些位置上记录变化的OopMap就行了。这些特定的点就是SafePoint(安全点)。由此也可以知道,程序并不是在所有的位置上都可以进行GC的,只有在达到这样的安全点才能暂停下来进行GC

安全点太少,会让GC等待的时间太长,太多会浪费性能

我们搞明白了安全点被放置的位置之后,考虑另外一个问题:如何使虚拟机中的线程跑到安全点?

有两种方式可供选择,分别为:

抢先式中断(Preemptive Suspension),直接粗暴的暂停全部的用户线程,如果发现用户线程并不在安全点上,则继续恢复这条线程继续执行,让他一会再重新中断,直到跑到安全点上为止。(现在几乎没有虚拟机采用这种实现)。

主动式中断(Voluntary Suspension),设置一个标志位,用户线程执行过程中,不停的主动轮询这个标志,一旦发现中断标志为真,自己就在最近的安全点上主动中断挂起。这个轮询是否需要进入安全点的动作在每个安全点时发生,这个动作被称之为polling point,polling也有开销,这也是上文中我们提到的HotSpot并没每个字节码指令都放置一个safepoint的原因。

安全点的选取是以是否让程序长时间执行的特征为标准的,“长时间执行”最明显的特征就是指令复用,如:方法调用、循环跳转(非可数循环)、异常跳转等都属于指令复用

这里有一个特别有意思的例子:

//    private static int num = 0;
    public static AtomicInteger num = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            for (int i = 0; i < 200000000; i++) {
                num.getAndAdd(1);
            }
        };
        Thread t1 = new Thread(runnable, 测试01线程);
        Thread t2 = new Thread(runnable, 测试02线程);
        t1.start();
        t2.start();
        System.out.println(主线程开始睡觉);
        //记录睡眠时间
        long start = System.currentTimeMillis();
        Thread.sleep(1000);

        System.out.println(睡醒了, 一共睡了 :  + (System.currentTimeMillis() - start
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值