JVM:gc什么时候开始?System.gc()能保证gc一定发生吗?

版权声明:本博客为记录本人学习过程而开,内容大多从网上学习与整理所得,若侵权请告知! https://blog.csdn.net/Fly_as_tadpole/article/details/85046997

gc什么时候开始?

“你能不能谈谈,java GC是在什么时候,对什么东西,做了什么事情?”

在什么时候:

1.新生代有一个Eden区和两个survivor区,首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放,如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中,然后清空Eden和之前的那个survivor区的内存。在某次GC过程中,如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去。

2.大对象以及长期存活的对象直接进入老年区。

3.当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析,如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次Full GC以尽可能地获得老年区的空间。

对什么东西:从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象。

做什么: 
新生代:复制清理; 
老年代:标记-清除和标记-压缩算法; 
永久代:存放Java中的类和加载类的类加载器本身。

GC Roots都有哪些: 
1. 虚拟机栈中的引用的对象 
2. 方法区中静态属性引用的对象,常量引用的对象 
3. 本地方法栈中JNI(即一般说的Native方法)引用的对象。

友情链接:Java GC的那些事(上)

友情链接:Java GC的那些事(下)

友情链接:CMS垃圾收集器介绍

垃圾回收算法有哪些?

 

答:

  1. 引用计数 :原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为 0 的对象。此算法最致命的是无法处理循环引用的问题;

  2. 标记-清除 :此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除;

    此算法需要暂停整个应用,同时,会产生内存碎片;

  3. 复制算法 :此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中;

    此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现 “碎片” 问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间;(8:1:1需要内存担保)

  4. 标记-整理 :此算法结合了 “标记-清除” 和 “复制” 两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象 “压缩” 到堆的其中一块,按顺序排放。

    此算法避免了 “标记-清除” 的碎片问题,同时也避免了 “复制” 算法的空间问题。

类似-Xms、-Xmn 这些参数的含义:

答:

堆内存分配:

  1. JVM 初始分配的内存由-Xms 指定,默认是物理内存的 1/64;

  2. JVM 最大分配的内存由-Xmx 指定,默认是物理内存的 1/4;

  3. 默认空余堆内存小于 40% 时,JVM 就会增大堆直到-Xmx 的最大限制;空余堆内存大于 70% 时,JVM 会减少堆直到 -Xms 的最小限制;

  4. 因此服务器一般设置-Xms、-Xmx 相等以避免在每次 GC 后调整堆的大小。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。

非堆内存分配:

  1. JVM 使用-XX:PermSize 设置非堆内存初始值,默认是物理内存的 1/64;

  2. 由 XX:MaxPermSize 设置最大非堆内存的大小,默认是物理内存的 1/4;

  3. -Xmn2G:设置年轻代大小为 2G;

  4. -XX:SurvivorRatio,设置年轻代中 Eden 区与 Survivor 区的比值。

System.gc()能保证gc一定发生吗? 

查看源码

当我们调用System.gc()的时候,其实并不会马上进行垃圾回收,甚至不一定会执行垃圾回收,查看系统源码可以看到


    /**
     * Indicates to the VM that it would be a good time to run the
     * garbage collector. Note that this is a hint only. There is no guarantee
     * that the garbage collector will actually be run.
     */
    public static void gc() {
        boolean shouldRunGC;
        synchronized(lock) {
            shouldRunGC = justRanFinalization;
            if (shouldRunGC) {
                justRanFinalization = false;
            } else {
                runGC = true;
            }
        }
        if (shouldRunGC) {
            Runtime.getRuntime().gc();
        }
    }

 

也就是justRanFinalization=true的时候才会执行


查找发现当调用runFinalization()的时候justRanFinalization变为true
下面是runFinalization()的源码


/**
* Provides a hint to the VM that it would be useful to attempt
* to perform any outstanding object finalization.
*/
public static void runFinalization() {
        boolean shouldRunGC;
        synchronized(lock) {
            shouldRunGC = runGC;
            runGC = false;
        }
        if (shouldRunGC) {
            Runtime.getRuntime().gc();
        }
        Runtime.getRuntime().runFinalization();
        synchronized(lock) {
            justRanFinalization = true;
        }
}
1234567891011121314151617181920

 

其实当我们直接调用System.gc()只会把这次gc请求记录下来,等到runFinalization=true的时候才会先去执行GC,runFinalization=true之后会在允许一次system.gc()。之后在call System.gc()还会重复上面的行为。
所以System.gc()要跟System.runFinalization()一起搭配使用才好。
查看ZygoteInit.java 里面 gc()和runFinalizationSync()是配合使用的,这样才有效果

static void gcAndFinalize() {
    final VMRuntime runtime = VMRuntime.getRuntime();

    /* runFinalizationSync() lets finalizers be called in Zygote,
    * which doesn't have a HeapWorker thread.
    */
    System.gc();
    runtime.runFinalizationSync();
    System.gc();
}12345678910

 

解决方案

由此可见,当我们需要调用的System.gc()的时候 要这样才会执行

System.gc();
runtime.runFinalizationSync();
System.gc();123

其实这个gc()函数的作用只是提醒虚拟机:程序员希望进行一次垃圾回收。但是它不能保证垃圾回收一定会进行,而且具体什么时候进行是取决于具体的虚拟机的,不同的虚拟机有不同的对策。
 

不过个人建议不到万不得已不要调用,因为jvm有自己的gc策略,根本不需要我们来手动

展开阅读全文

没有更多推荐了,返回首页