哪些对象可以作为 GC Roots 的对象:
- 虚拟机栈中局部变量(也叫局部变量表)中引用的对象
- 方法区中类的静态变量、常量引用的对象
- 本地方法栈中 JNI (Native方法)引用的对象
public class GCRootDemo {
private byte[] byteArray = new byte[100 * 1024 * 1024];
private static GCRootDemo gc2;
private static final GCRootDemo gc3 = new GCRootDemo();
public static void m1(){
GCRootDemo gc1 = new GCRootDemo();
System.gc();
System.out.println("第一次GC完成");
}
public static void main(String[] args) {
m1();
}
}
解释:
gc1:是虚拟机栈中的局部变量
gc2:是方法区中类的静态变量
gc3:是方法区中的常量
都可以作为GC Roots 的对象。
root大概有这些 |
---|
所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。 |
VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。 |
JNI handles,包括global handles和local handles(看情况) |
所有当前被加载的Java类(看情况) |
Java类的引用类型静态变量(看情况) |
Java类的运行时常量池里的引用类型常量(String或Class类型)(看情况) |
String常量池(StringTable)里的引用 |
所有Java线程当前活跃的栈帧,静态引用等指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。
假设hashMap 在扩容的时候,正巧发生了gc,在标记阶段,要找出所有的root,这里的 oldTab 就是root,通过这个root,然后遍历他的所有属性,如果它里面的内容不为null 我们继续遍历内容里的对象字段是否为null,标记出所有不为null的节点。继续下图,子节点
静态引用这样的代码也会成为root
private static Object objoct;
这里可以看到Node里还有Node,标记的时候就这这样,遍历每个属性的子属性,一直到遍历完,则这些被标记的为有引用的。gc标记的过程就是一棵树在遍历他所有的节点,这样应该很清楚了吧。最后没有被标记的就给清楚掉。
/**
* @author 铁拳阿牛
* @createTime 2018/7/14 下午3:33
**/
public class Node {
private Object object;
//如果这个Node被引用他的属性又被引用,属性为对象且不为null,这样一直遍历下去,
private Node node;
}
理论扯完了,接下来实践
/**
* @author 铁拳阿牛
* @createTime 2018/7/14 下午3:46
*
* 请把以下参数设置到jvm里
* -Xmx4000M -Xms4000M -Xmn1300M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=75 -XX:+PrintGCDetails
**/
public class Test {
private static final int _1MB = 1024 * 1024;
private static final int LENGTH = 40000000;
public static void main(String[] args) {
Node next = null;
for(int i = 0; i <= LENGTH; i++){
Node node = new Node(i,next);
next = node;
}
//如果不设置为null这里将会又大批量的遍历,打开这里和不打开这里,gc时间完全不一样,现在你直到为什么要设置为null了吗?
// next = null;
triggerGC();
}
/**
* 不触发gc看不见效果
* new 很多小对象。不然直接到 old区去了。
*/
private static void triggerGC(){
// byte[] all = new byte[2000 * _1MB]; //这里为什么没又直接new 一个大对象?它可能直接就到old区去了。
for(int i = 0 ; i < 500 ; i++){
byte[] bytes = new byte[2 * _1MB];
}
}
//POJO 不用看这里
static class Node {
private int valuel;
private Node node;
public Node(int valuel, Node node) {
this.valuel = valuel;
this.node = node;
}
public int getValuel() {
return valuel;
}
public void setValuel(int valuel) {
this.valuel = valuel;
}
public Node getNode() {
return node;
}
public void setNode(Node node) {
this.node = node;
}
}
}
第一次我们要把eden 的空间尽可能的占满将这两行代码注释掉,发现eden已经用了94% 很不错。
// next = null;
// triggerGC();
~~~~~~~~~~~~~~~~~
Heap
par new generation total 1198080K, used 1001063K [0x00000006c6000000, 0x0000000717400000, 0x0000000717400000)
eden space 1064960K, 94% used [0x00000006c6000000, 0x0000000703199e78, 0x0000000707000000)
from space 133120K, 0% used [0x0000000707000000, 0x0000000707000000, 0x000000070f200000)
to space 133120K, 0% used [0x000000070f200000, 0x000000070f200000, 0x0000000717400000)
concurrent mark-sweep generation total 2764800K, used 0K [0x0000000717400000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 2674K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 288K, capacity 386K, committed 512K, reserved 1048576K
然后我们开始测试了,发现gc时间非常的短, [Times: user=0.04 sys=0.00, real=0.01 secs] ,gc基本没有占用我们的时间。
next = null;
triggerGC();
~~~~~~~~~~~~~~~~~
[GC (Allocation Failure) [ParNew: 1063732K->399K(1198080K), 0.0101658 secs] 1063732K->399K(3962880K), 0.0102629 secs] [Times: user=0.04 sys=0.00, real=0.01 secs]
Heap
par new generation total 1198080K, used 933048K [0x00000006c6000000, 0x0000000717400000, 0x0000000717400000)
eden space 1064960K, 87% used [0x00000006c6000000, 0x00000006feeca630, 0x0000000707000000)
from space 133120K, 0% used [0x000000070f200000, 0x000000070f263d38, 0x0000000717400000)
to space 133120K, 0% used [0x0000000707000000, 0x0000000707000000, 0x000000070f200000)
concurrent mark-sweep generation total 2764800K, used 0K [0x0000000717400000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 2674K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 288K, capacity 386K, committed 512K, reserved 1048576K
然后我们对比测试,发现gc时间非常的短,[Times: user=145.85 sys=1.89, real=20.17 secs] ,额这个gc时间。。。
// next = null;
triggerGC();
~~~~~~~~~~~~~~~~~
[GC (Allocation Failure) [ParNew: 1063732K->133119K(1198080K), 20.1710025 secs] 1063732K->938404K(3962880K), 20.1710585 secs] [Times: user=145.85 sys=1.89, real=20.17 secs]
Heap
par new generation total 1198080K, used 1065769K [0x00000006c6000000, 0x0000000717400000, 0x0000000717400000)
eden space 1064960K, 87% used [0x00000006c6000000, 0x00000006feeca630, 0x0000000707000000)
from space 133120K, 99% used [0x000000070f200000, 0x00000007173ffff8, 0x0000000717400000)
to space 133120K, 0% used [0x0000000707000000, 0x0000000707000000, 0x000000070f200000)
concurrent mark-sweep generation total 2764800K, used 805284K [0x0000000717400000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 2676K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 288K, capacity 386K, committed 512K, reserved 1048576K
把这些对象标记后其他的都是要清除的,所以现在知道为什么要设置为null
了吗?就是为了加快标记阶段。如果你有个超大的map或者list的时候这样
做会有一点点帮助。(普通开发过程中基本没有必要考虑)