『Effective Java』第6条:期限切れのオブジェクト参照の削除

27625 ワード

原文住所:https://itweknow.cn/detail?id=68 ああ、いらっしゃいませ.
JavaといえばGCを知っている方が多いかもしれません.Javaには自動的なゴミ回収メカニズムがあります.もちろん、この文章ではGCの具体的な実現を深く研究しません.では、これからごみが自動的に回収されるので、メモリの漏洩の心配もないのではないでしょうか.このような問題の答えは一般的に否定的だ.では、この文章では「Effective Java」について、この問題を理解してみましょう.
期限切れの参照
本の中でまず言及したのは、期限切れの引用によるメモリの漏洩であり、Stackの例を挙げた.
public class Stack {

    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_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);
        }
    }
}

本の説明によると、私たちはこの例で自分でメモリを管理しており、ゴミ回収器はelementの中で活発ではない部分が回収できることを知らない.この問題を解決する方法は、これらの要素を手動で空にすることで、ゴミ回収期間を回収できることをゴミ回収期間に伝えることだ.
public Object pop() {
    if (size == 0) {
        throw new EmptyStackException();
    }
    Object obj = elements[--size];
    elements[size] = null;
    return obj;
}

次のコードを使って、問題のあるコードをテストしてみました.
public class Main {

    public static void main(String[] args) throws InterruptedException {
        Stack stack = new Stack();
        for (long i=0; i<Long.MAX_VALUE; i++) {
            doSomething(stack);
        }
    }
    private static void doSomething(Stack stack) {
        for (int i=0; i<5; i++) {
            stack.push(new User(1, "itweknow.cn"));
        }
        for (int i=0; i<5; i++) {
            stack.pop();
        }
    }
}

残念ながらメモリオーバーフローのエラーは検出されませんでしたが、ここで感謝します.https://bbs.csdn.net/topics/391991081この招待状は私に答えをあげて、興味のある学生は見に行ってもいいです.さらに本の説明をよく見ると、メモリの消費量が増加するにつれて、プログラムの性能の低下が徐々に表現されています.極端な場合、メモリがオーバーフローする可能性があります.このように、一般的にStackはメモリオーバーフローを招くことはありませんが、失効した要素はGCでタイムリーに回収されないため、メモリがゼロ境界値に近づくとプログラムの性能に大きな影響を及ぼすため、メモリの管理はできるだけ避けるべきです.
メモリの別の漏洩ソース-キャッシュ
実際、参照オブジェクトをキャッシュに配置すると、キャッシュの蓄積に伴ってオーバーフローが発生する可能性が高いことが忘れられやすくなります.例えば、次のコードを実行すると異常が発生します.しかし、現在のコンピュータは一般的に構成が高いので、実行時にJVMパラメータを設定しました.
public class Question {
    /**
     *            ,   java                
     *     HashMap   ,       1000000            。
     *            10M
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        HashMap<Integer, String> codeCache = new HashMap<Integer, String>();
        //     ,       。
        String code = "000000";
        for (int i=0; i<1000000; i++) {
            codeCache.put(i, code);
            Thread.sleep(1);
        }
    }
}

Listenerおよびその他のコールバック
これを見る前に私はリスナーとコールバックにあまり接触していなかったので、今回はリスナーのセットが何なのかを検討することにしました.実際には、リスナーとコールバックは非同期呼び出しの方法と言える. の質問が1+1=?であると仮定すると、 に質問する必要があり、回答者がこの質問を計算するのに時間がかかる可能性があります.単位時間当たり でより多くのことができるように、 クラスでコールバックインタフェースを実現し、 者は問題計算が完了したときにこのコールバックメソッドを呼び出して の答えを通知します.では、リスニングも実際には似ています.私たちの実際の仕事では、特にフロントエンドが接触する可能性が多いです.例えば、ボタンのクリックイベントのリスニングは、ボタンにリスナーを設定し、クリックイベントが発生した場合、リスナーは対応するコールバック方法を呼び出し、対応する処理を行います.次に、この仮定のコード実装を示します.
  • コールバックインタフェース
  • /**
     * @author ganchaoyang
     * @date 2018/11/15 17:45
     * @description     
     */
    public interface CallBack {
    
        /**
         *          
         * @param result
         */
        public void solve(String result);
    
    }
    
  • 質問者
  • public class Questioner implements CallBack{
    
        /**
         *       
         */
        private String name;
    
        /**
         *          ,          
         */
        private Author author;
    
        public Questioner(String name) {
            this.name = name;
        }
    
        /**
         *      ,        
         * @return
         */
        public Questioner setAuthor(Author author) {
            this.author = author;
            return this;
        }
    
        public void doRequest() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    author.execRequest(Questioner.this, "question");
                }
            }).start();
            //       ,      。
            doOtherThings();
        }
    
        private void doOtherThings() {
            System.out.println("do other things...");
        }
    
        /**
         *                        
         * @param result
         */
        @Override
        public void solve(String result) {
            System.out.println(author.getName() + "  " + getName()
                    + "   :" + result);
        }
    
        public String getName() {
            return name;
        }
    
        public Questioner setName(String name) {
            this.name = name;
            return this;
        }
    }
    
  • 回答者
  • public class Author {
    
        private String name;
    
        public Author(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public Author setName(String name) {
            this.name = name;
            return this;
        }
    
        /**
         *     
         * @param callBack
         * @param qeustion
         */
        public void execRequest(CallBack callBack, String qeustion) {
            //      ,      。
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //     ,          。
            callBack.solve("solved");
        }
    }
    
  • 呼び出し方式
  • public class Main {
    
        public static void main(String[] args) throws InterruptedException {
            //      10000    ,10000    ,           。
            for (int i=0; i< 10000 ; i++) {
                Questioner questioner = new Questioner("Q" + i);
                Author author = new Author("A" + i);
                questioner.setAuthor(author);
                questioner.doRequest();
                Thread.sleep(100);
            }
        }
    }
    

    私たちは被傍受者にリスナーを設置しました.もし私たちが被傍受者が破壊されたときに傍受を抹消しなかったら、リスナーはずっと被傍受者の参照を持っていて、この時GCは被傍受者を回収しないで、長い間メモリの漏れが発生する可能性があります.