• JVM(十八)—— 垃圾回收(四)


    JVM(十八)—— 垃圾回收(四)

    System.gc()

    在默认情况下,通过System.gc()或者Runtime.getRuntime().gc()的调用,会显式触发Full GC。 同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。

    然而 System.gc()调用并不能保证马上对垃圾收集器的调用。

    public class Test1 {
    
        public static void main(String[] args) {
           new Test1();
           System.gc();
        }
    
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("调用了finalize");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    通过之前的章节我们知道, 如果触发了垃圾回收,就会调用一次重写的finalize方法。我们执行main方法,却发现有时可以输出调用了finalize,有时候不能输出,也就验证了我们虽然调用了System.gc(),但是不能保证证马上对垃圾收集器的调用。

    public class Test1 {
    
        public static void main(String[] args) {
           new Test1();
           System.gc();
    	   // 强制调用使用引用的对象finalize方法。
           System.runFinalization();
        }
    
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("调用了finalize");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    如上的代码,当我们加上System.runFinalization();每次都可以调用finalize方法,runFinalization方法可以强制调用使用引用的对象finalize方法。

    通过查看System.gc()的代码:

    public static void gc() {
            Runtime.getRuntime().gc();
        }
    
    • 1
    • 2
    • 3

    说明System.gc()和Runtime.getRuntime().gc()是等价的。

    JVM实现者可以通过System.gc()调用来决定JVM的GC行为,而一般情况下,垃圾回收应该是自动进行的,无须手动触发,否则就太麻烦了。在一些特殊情况下,如我们正在编写一个性能基准,我们可以在运行之间调用System.gc()。

    内存溢出

    内存溢出是引发程序崩溃的罪魁祸首之一。由于GC一直在发展,所以一般情况下,除非应用程序占用的内存增长速度非常快,造成垃圾回收已经跟不上内存消耗的速度,否则不太容易出现OOM的情况。

    大多数情况下,GC会进行各种年龄段的垃圾回收,实在不行了就放大招,来一次独占式的full gc操作,这时候会回收大量的内存,供应用程序仅需使用。

    Javadoc对OOM的解释是,没有空闲内存,并且垃圾收集器也无法提供更多内存

    首先说没有空闲内存的情况:说明Java虚拟机的堆内存不够。原因有二:

    • Java虚拟机的堆内存设置不够。
      比如可能存在内存泄漏的问题:也很有可能是堆的大小不合理,比如我们要处理比较可观的数据量,但是没有显示指定JVM堆大小或者指定数值偏小。我们可以通过参数-Xms,-Xmx来调整。
    • 代码中创建了大量大对象,并且长时间不能够被垃圾收集器回收(存在被引用)
      对于老版本的JDK,引用永久代的大小是有限的,并且JVM对永久代垃圾回收(如常量池回收,卸载不再使用的类型)非常不积极,所以当我们不断添加新类型时,永久代出现OOM也非常多见,尤其是在运行时存在大量动态类型生成的场合:类似intern字符串占用太多空间,也会导致OOM。
      随着元数据区的引入,方法区内存已经不再那么窘迫,所以相应的OOM有所改观,直接内存不足,也会导致OOM。

    再抛出OOM之前,通常垃圾收集器会被触发,尽可能的去清理空间。比如在引用机制分析中,涉及到JVM回去尝试回收软引用指向的对象等,在java.nio.Bits.reserveMemory()方法中,我们能清楚的看到,System.gc()会被调用,以清理空间。

    当然,也不时在任何情况下垃圾收集器都会被触发,如果我们分配的是一个超大对象。已经超过堆的最大值,JVM可以判断出垃圾收集并不能解决这个问题,所以直接抛出OOM。

    内存泄漏

    内存泄漏也称作存储泄漏。严格来说,只有对象不再被程序用到了,但是GC不能回收他们的情况才叫做内存泄漏。

    但是实际情况很多时候一些不太好的实践或疏忽会导致对象的生命周期变得很长甚至导致OOM,也可以叫做宽泛意义上的内存泄漏。

    尽管内存泄漏不会立刻引起程序崩溃,但是一旦发生内存泄漏,程序中的可用内存就会逐步蚕食,直至耗尽所有内存,最终出现OOM异常,导致程序崩溃。

    在这里插入图片描述
    如上图,红框部分是有一个指针没有断开,导致这些对象都不会被回收。

    开发中内存泄漏的例子

    • 单例模式
      单例的声明周期和应用程序是一样长的,所以单例程序中,如果持有对外部对象的引用的话,那么这个外部对象是不能被回收的,则会导致内存泄漏的产生。
    • 一些提供close的资源未关闭导致内存泄漏
      数据库连接,网络连接,IO连接必须手动close,否则时不能被回收的。
  • 相关阅读:
    软件工程第八周
    02强化学习基本概念
    阿里云轻量应用服务器月流量限制说明(部分套餐不限流量)
    【计算机网络】HTTPS
    2022爱分析·数字人厂商全景报告 | 厂商征集
    Netty——搭建一个聊天室(笔记)
    视口 css
    java计算机毕业设计个人连锁民宿信息管理系统设计与开发系统(修改)MyBatis+系统+LW文档+源码+调试部署
    Qt-QImage-convertTo-copy-convertToFormat-格式转换
    Skype与Teams的混合部署--意义,效果,注意事项以及建议
  • 原文地址:https://blog.csdn.net/weixin_40920359/article/details/127818879