目录
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