Java实现垃圾回收的步骤:
GC是在一个单独的线程,但不管JVM用哪种算法,都会存在一个阶段需要停止所有的用户线程,称Stop The World(STW),SWT大,用户用起来自然卡。
感受下SWT:
public class StopTheWorld {
public static void main(String[] args) {
/**
* 启动用户线程和GC线程
* 查看不同阶段用户线程的执行时间
*/
new PrintTimeThread().start();
new ClearThread().start();
}
}
/**
* 模拟用户代码,这里直接打印这段代码的执行耗时
*/
class PrintTimeThread extends Thread {
@SneakyThrows //lombok的try..catch
@Override
public void run() {
long begin = System.currentTimeMillis();
while (true) {
long now = System.currentTimeMillis();
System.out.println(now - begin);
begin = now;
Thread.sleep(100);
}
}
}
/**
* 模拟GC线程
*/
class ClearThread extends Thread {
@SneakyThrows
@Override
public void run() {
List<byte[]> list = new LinkedList<>();
while (true) {
//存80个100M后就删除里面byte对象的强引用,垃圾回收释放
if(list.size() >= 80){
list.clear();
}
list.add(new byte[1024 * 1024 * 100]);
Thread.sleep(100);
}
}
}
添加JVM参数,使用分代回收的垃圾回收器,输出GC详细信息,并限制堆最大10G:
-XX:+UseSerialGC -Xmx10g -verbose:gc
运行发现用户线程本来100ms左右的事儿,有时候会被拖到2000ms以上:
对象回收算法的评价标准:
以上三个指标,不可兼得。各个算法各有长处,对应着不同的适用场景。
实现:
优点:
缺点:
实现:
完整例子:
GC开始,把GC Root对象和可达的对象搬到To空间
清掉From空间,并把原来的To改为From空间
一句话:将存活的对象搬运到另一块空间,清理掉当前空间,互换名字
优点:
缺点:
也称标记压缩,用来解决标记清除算法的内存碎片化缺点。
实现:
优点:
缺点:
组合使用了上面的几种算法,被主流使用。分代即把内存分为年轻代和老年代:
关于这几块空间的大小设置:
Demo:
public class Gc {
@SneakyThrows
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
int count = 0;
while (true) {
System.in.read();
System.out.println(++count);
list.add(new Byte[1024 * 1024]);
}
}
}
对应JVM的参数:
-XX:+UseSerialGC -Xms60m -Xmn20m -Xmx60m -XX:SurvivorRatio=3 -XX:+PrintGDetails
粗略计算:老年代60m - 20m = 40m,Eden除以随便一块s区 = 3,则Eden:s0:s1 = 12:4:4,使用阿尔萨斯执行memory验证:
补充:如果现在新生代已经满了,Minor GC还是满,再来对象,尽管新生代有的对象没到达年龄阈值,也会被搬到老年代