zoukankan      html  css  js  c++  java
  • Java 垃圾回收

    当程序创建对象、数组等引用类型的实体时,系统会在堆内存中为这一对象分配一块内存,对象就保存在这块内存中,当这块内存不再被任何引用变量引用时,这块内存就变成垃圾,等待垃圾回收机制进行回收。垃圾回收机制具有三个特征:

    • 垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(例如数据库连接,打开的文件资源等),也不会回收以某种创建对象的方式以外的方式为该对像分配的内存,(例如对象调用本地方法中malloc的方式申请的内存)
    • 程序无法精确控制垃圾回收的运行,只可以建议垃圾回收进行,建议的方式有两种System.gc() 和Runtime.getRuntime().gc()
    • 在垃圾回收任何对象之前,总会先调用它的finalize()方法,但是同垃圾回收的时机一致,调用finalize()方法的时机也不确定。

    针对以上三个特征,有三个问题:

    1、必须手动的进行清理工作,释放除创建对象的方式以外的方式分配的内存和其它的物理资源。并且要注意消除过期的对象引用,否则可能引起OOM。

    手动清理通常用到try...finally...这样的代码结构。

    示例如下:

    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    
    public class ManualClear {
    
        public static void main(String[] args) {
            FileInputStream fileInputStream = null;
            try {
                fileInputStream = new FileInputStream("./src/ManualClear.java");
            } catch (FileNotFoundException e) {
                System.out.println(e.getMessage());
                e.printStackTrace();
                return;
            }
    
            try {
                byte[] bbuf = new byte[1024];
                int hasRead = 0;
                try {
                    while ((hasRead = fileInputStream.read(bbuf)) > 0) {
                        System.out.println(new String(bbuf, 0, hasRead));
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } finally {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
    }

    对于过期对象的引用,引起的OOM通常有三种常见的情况,这三种情况通常都不易发现,短时间内运行也不会有什么问题,但是时间久了后,泄漏的对象增加后终会引起程序崩溃。

    • 类自己管理内存时,要警惕内存泄漏

    示例如下:

    import java.util.Arrays;
    import java.util.EmptyStackException;
    
    class Stack{
        private Object[] elements;
        private int size;
        private static final int DEFAULT_INITAL_CAPACITY = 16;
        
        public Stack() {
            elements = new Object[DEFAULT_INITAL_CAPACITY];
        }
        
        public void push(Object e){
            ensureCapacity();
            elements[size++] = e;
        }
        
        public Object pop() {
            if (size == 0) {
                throw new EmptyStackException();
            }
            
            return elements[--size];
        }
        
        private void ensureCapacity() {
            if (elements.length == size) {
                elements = Arrays.copyOf(elements, 2 * size + 1);
            }
        }
    }
    
    public class StackDemo {
        
        public static void main(String[] args) {
            Stack stack = new Stack();
            
            for (int i = 0; i < 10000; i++) {
                stack.push(new Object());
            }
            
            for(int i = 0; i < 10000; i++) {
                stack.pop();
            }
        }
    
    }

    之所以会内存泄漏,是因为那些出栈的对象即使程序其它对象不再引用,但是Stack类中的elements[]数组依然保存着这些对象的引用,导致这些对象不会被垃圾回收所回收,所以,当需要类自己管理内存事,要警惕内部维护的这些过期引用是否被及时解除了引用,本例中只需在出栈后,显示的将

    elements[size] = null;即可。

    • 缓存是要警惕内存泄漏

    出现这样情况通常是一旦将对象放入缓存,很可能长时间不使用很容易遗忘,通常可以用WakeHashMap代表缓存,在缓存中的项过期后,他们可以被自动删除。或者可以由一个后台线程定期执行来清除缓冲中的过期项。

    • 监听器或回调的注册,最好可以显示的取消注册。

    2、不要手动调用finalize(),它是给垃圾回收器调用的

    3、避免使用finalize()方法,除非用来作为判断终结条件以发现对象中没有被适当清理的部分;用来作为安全网在手动清理忘记调用的情况下清理系统资源,延后清理总别永不清理要强,并且如果同时记录下忘记清理资源的信息的话,也方便后面发现错误,并及时修改忘记清理的代码;释放对象中本地方法获得的不是很关键的系统资源。

    finalize()方法由于其执行时间以及是否确定被执行都不能准确确保,所以最好不用来释放关键资源,但是可用于上面所说的三种情况。其中第一种情况,示例如下:

    class Book {
        boolean checkout = false;
        public Book(boolean checkout) {
            this.checkout = checkout;
        }
        
        public void checkin(){
            checkout = false;
        }
        
        @Override
        protected void finalize() throws Throwable {
            if (checkout) {
                System.out.println("Error: check out");
            }
        }
    }
    
    public class FinalizeCheckObjectUse {
    
        public static void main(String[] args) {
            new Book(true);
            System.gc();
        }
    
    }

    执行结果:

    Error: check out

    例子中的Book对象,在释放前必须处于checkIn的状态,否则不能释放,finalize中的实现可以帮助及时发现不合法的对象,或者更直接的,在finalize中直接使用某个引用变量引用,使其重新进入reachable的状态,然后再次对其进行处理。

    另一点需要注意的时,子类如果覆盖了父类的finalize方法,但是忘了手工调用super.finalize或者子类的finalize过程出现异常导致没有执行到super.finalize时,那么父类的终结方法将永远不会调到。

    如下:

    class Parent{
        @Override
        protected void finalize() throws Throwable {
            System.out.println(getClass().getName() + " finalize start");
        }
    }
    
    class Son extends Parent{
        @Override
        protected void finalize() throws Throwable {
            System.out.println(getClass().getName() + " finalize start");
        }
    }
    public class SuperFinalizeLost {
    
        public static void main(String[] args) {
            new Son();
            System.gc();
        }
    
    }

    运行结果:

    Son finalize start

    或者

    class Parent{
        @Override
        protected void finalize() throws Throwable {
            System.out.println(getClass().getName() + " finalize start");
        }
    }
    
    class Son extends Parent{
        @Override
        protected void finalize() throws Throwable {
            System.out.println(getClass().getName() + " finalize start");
            int i = 5 / 0;
            super.finalize();
        }
    }
    public class SuperFinalizeLost {
    
        public static void main(String[] args) {
            new Son();
            System.gc();
        }
    
    }

    执行结果:

    Son finalize start

    对于第二种情况,可以使用try...finally...结构解决,但是对于第一种情况,最好使用一种叫终结方法守护者的方式。示例如下

    class Parent2{
        private final Object finalizeGuardian = new Object() {
            protected void finalize() throws Throwable {
                System.out.println("在此执行父类终结方法中的逻辑");
            };
        };
    }
    
    class Son2 extends Parent2{
        @Override
        protected void finalize() throws Throwable {
            System.out.println(getClass().getName() + " finalize start");
            int i = 5 / 0;
            super.finalize();
        }
    }
    
    public class FinalizeGuardian {
    
        public static void main(String[] args) {
            new Son2();
            System.gc();
        }
    
    }

    执行结果:

    在此执行父类终结方法中的逻辑
    Son2 finalize start

    这样可以保证父类的终结方法中所需做的操作执行到。

  • 相关阅读:
    腰颈椎病康复运动治疗
    丹田呼吸简易教程
    丹田呼吸法
    GNU 汇编语言
    openssl命令简介
    AES128 + cbc + pkcs7 编码C语言实现
    一些linux下的性能监测工具
    git 基本使用教程
    【译】UI设计基础(UI Design Basics)--导航(Navigation)(六)
    【译】UI设计基础(UI Design Basics)--启动与停止(Starting and Stopping)(五)
  • 原文地址:https://www.cnblogs.com/zj2012zy/p/5324591.html
Copyright © 2011-2022 走看看