内存使用内联Java上的本地变量

时间:2015-03-09 07:44:14

标签: java memory-management garbage-collection

当另一位开发人员讨论代码清晰度时。他说使用局部变量会增加内存使用量。我们认为他们将被垃圾收集。如果日志语句调用一个命中db / other外部资源的函数,那么尤其是个坏主意。

但是下面的示例代码似乎支持他所说的 - 即使在使用jconsole调用GC之后,仍然看到类Worker使用的内存少于Worked2。有什么想法吗?

免费记忆: 124629976 - 工人 124784720

package test;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class ObjectLife {

    public static void main(String[] args) {
        // simple multi thread to mimic web app
        // ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 3, 110, null, null);
        Executor pool = Executors.newFixedThreadPool(3);
        String type = "1";

        if (args.length > 0) {
            type = args[0];
        }
        Work w = null;
        if ("1".equals(type)) {
            w = new Worker();
        } else {
            w = new Worker2();
        }
        w.init(2);
        System.out.println("w type " + w.getClass().getName());
        Watch.me.print();
        pool.execute(w);
        pool.execute(Watch.me);

    }

}

class Watch implements Runnable {
    long prev = 0;
    static Watch me = new Watch();

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1100);
            } catch (InterruptedException e) {
                System.out.println("Intrpt thread " + e);
            }
            print();
        }
    }

    public void print() {
        Runtime r = Runtime.getRuntime();
        long free = r.freeMemory();
        System.out.println("Free " + free + ", delta " + (free - prev));
        System.out.println(", av " + r.maxMemory());
        prev = free;
    }

}

class Work implements Runnable {
    double val = 0;

    public void init(double val) {
        this.val = val;
    }

    void do2() {
    }

    @Override
    public void run() {
        int cnt = 0;
        while (++cnt < 175) {
            do2();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                System.out.println("Intrpt thread " + e);
            }
        }
        System.gc();
        System.out.println("Type, v3, : " + this.getClass().getName());
        Watch.me.print();

    }

}

class Worker extends Work {

    public void do2() {
        double local = ++val;
        double local2 = local * 2;
        System.out.println(" local " + local + " double " + local2);
    }
}

class Worker2 extends Work {

    public void do2() {
        System.out.println(" local " + ++val + " double " + (val * 2));

    }

}

为什么类Worker会占用更多内存 - 即使在多次调用GC之后再将Jconsole与进程断开连接并让几秒钟通过? (每2秒检查并打印一次。

可以看到代码与超类工作相同而且worker除了do2()方法之外是相同的

注意: 我在工作循环完成后从jconsole连接并调用GC。此GC调用确实有效。调用MX bean,可以看到可用的内存丢失。

旁注:我甚至注意到如果我从我的应用程序断开jconsole然后等待4-5秒 - 可用内存再次上升(我想连接到jconsole的头部)。这时我做了测量。重点是我做了一些工作,等待JVM安定下来然后测量。

运行此程序并使用jconsole https://www.youtube.com/watch?v=MadBdryX8uk&

释放内存的视频

jconsole是bin文件夹中JDK的免费文件。

编辑类,这里增加了循环次数,现在无论使用哪个类,内存都是相同的!

其他人可以运行吗?

package test;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * Run with param 1 and 2 or change type to "2" and "1" Available memory changes, used jconsole to change force garbage collection.
 * 
 * See video https://www.youtube.com/watch?v=MadBdryX8uk
 */
public class ObjectLife {

    public static void main(String[] args) {
        // simple multi thread to mimic web app
        // ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 3, 110, null, null);
        Executor pool = Executors.newFixedThreadPool(3);
        String type = "1";

        if (args.length > 0) {
            type = args[0];
        }
        Work w = null;
        if ("1".equals(type)) {
            w = new Worker();
        } else {
            w = new Worker2();
        }
        w.init(2);
        System.out.println("w type " + w.getClass().getName());
        Watch.me.print();
        pool.execute(w);
        pool.execute(Watch.me);

    }

}

class Watch implements Runnable {
    long prev = 0;
    private int dieCnt = -1;
    static Watch me = new Watch();

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1100);
            } catch (InterruptedException e) {
                System.out.println("Intrpt thread " + e);
            }
            if (dieCnt > -1) {
                dieCnt--;

                if (dieCnt == 0) {
                    System.exit(0);
                }
            }
            print();
        }
    }

    public void print() {
        Runtime r = Runtime.getRuntime();
        long free = r.freeMemory();
        System.out.println("Pr v6 Free " + free + ", delta " + (free - prev) + ", av " + r.maxMemory());
        prev = free;
    }

    public void countDown() {
        dieCnt = 3;

    }

}

class Work implements Runnable {
    double val = 0;
    double val3 = 0;

    public void init(double val) {
        this.val = val;
    }

    void do2() {
    }

    @Override
    public void run() {
        int cnt = 0;
        while (++cnt < 475) {
            do2();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                System.out.println("Intrpt thread " + e);
            }
        }
        System.gc();
        System.out.println("Type : " + this.getClass().getName());
        Watch.me.print();
        System.out.println("oink");
        try {
            Thread.sleep(9100);
        } catch (InterruptedException e) {
            System.out.println("Intrpt thread " + e);
        }
        Watch.me.countDown();

    }

}

class Worker extends Work {

    public void do2() {
        double local = ++val;
        double local2 = local * 2;
        System.out.println(" local " + local + " double " + local2);
        val3 = local2 + 1;
    }
}

class Worker2 extends Work {

    public void do2() {
        System.out.println(" local " + ++val + " double " + (val * 2));
        val3 = (val * 2) + 1;

    }

}
  • 我知道本地变量更具可读性。我喜欢他们。我为他们辩护。
  • 只是好奇这种行为。

3 个答案:

答案 0 :(得分:3)

首先,System.gc()可能会运行垃圾收集器;或不。这实际上不是测试内存消耗的可靠方法。

有关详细信息,请参阅When does System.gc() do anything

下一步:请记住,有一个即时编译器。 JIT最适合“普通Java”代码。很多时候,当开发人员试图“聪明”并编写“更高效的Java代码”时 - 他们最终会比以前更糟糕 - 因为JIT无法有效地处理这种“特殊”代码。

将您的程序编写为清晰易读。他们一生中可能会阅读数百次;可能是由许多不同的人。所以,我所说的是:让代码易于理解更为重要。保持效率是公平的;但是当你遇到真正的问题时才开始“优化” - 然后进行适当的分析;例如使用分析器。 “避免使用局部变量来节省内存”并不是“一般”应该做的事情。

答案 1 :(得分:2)

本地变量存在于堆栈上,而不是堆,因此与垃圾收集无关。术语“变量”和“垃圾收集”并不是真正属于同一领域。对于引用类型,对象变量指向的是垃圾回收。由于您使用原始类型(double),这无关紧要。

是的,局部变量会增加用于堆栈帧的内存很少(doublelong为8个字节,其余4个字节)但基于任何一种优化都是荒谬的。您应该始终根据代码的可读性来决定是否使用局部变量。

答案 2 :(得分:0)

我同意局部变量的足迹非常小。我为他们辩护。整个练习的目的是向客户代码审查员表明他们并不昂贵。他很担心。

无论如何,我的例子表现得如预期的那样 - 当运行超过n次时,局部变量没有多余的空头。就我而言,它大概是500。

所以我想自己回答:即使使用jconsole(使用它来调用GC)也没有看到GC上的内存丢失。可能是因为有GC级别(全部和部分)。

但经过一定次数的迭代后,JVM稳定下来,然后两个示例中使用的内存相同(有和没有局部变量)。就像其他人已经注意到的那样:这不是可以研究的,但是人们如何在一个关于代码标准应该是什么的大型项目上争论呢?

当真正的问题出现在其他地方时,一些开发人员喜欢指出这些小事。因此,一些数据有助于显示局部变量是面向本地的并且收集垃圾。

如果你的记忆力已经很高并且你担心它会被推到极限,那么你需要进行一些其他的重构或扩展。