内存使用局部变量而不是内联 Java
Memory use local vars over inline Java
另一位开发人员正在讨论代码清晰度。他说使用局部变量会增加内存使用。我们认为它们将被垃圾收集。如果日志语句调用一个命中数据库/其他外部资源的函数,尤其是一个坏主意。
但下面的示例代码似乎支持他所说的 - 即使在使用 jconsole 调用 GC 之后,仍然看到 class 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));
}
}
为什么 class Worker 会占用更多内存 - 即使在多次调用 GC 然后断开 Jconsole 与进程的连接并等待几秒钟之后? (每 2 秒检查并打印一次。
可以看到代码和super是一样的classwork和worker除了do2()方法是一样的
注意:
我正在从 jconsole 连接并在工作循环完成后调用 GC。这个 GC 调用确实有效。调用 MX bean,可以看到可用内存下降。
旁注:我什至注意到,如果我从我的应用程序断开 jconsole 然后等待 4-5 秒 - 可用内存再次增加(我想连接到 jconsole 的开销)。这时我进行测量。重点是我做了一些工作,等待 JVM 稳定下来然后测量。
运行使用 jconsole 启动此程序并释放内存的视频 https://www.youtube.com/watch?v=MadBdryX8uk&
jconsole 是一个免费工具,在 bin 文件夹中 JDK。
修改了class,这里增加了循环次数,现在不管用哪个class,内存都是一样的!
别人可以运行吗?
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;
}
}
- 我知道本地变量更易读。我喜欢他们。我为他们辩护。
- 只是对这种行为感到好奇。
首先,System.gc() 可能 运行 垃圾收集器;或不。这确实不是测试内存消耗的可靠方法。
详情见When does System.gc() do anything。
下一步:记住有一个即时编译器。 JIT 最适合 "normal Java" 代码。很多时候,当开发人员试图成为 "smart" 并编写 "more efficient Java code" - 他们最终会比以前更糟 - 因为 JIT 无法有效地处理此 "special" 代码。
编写的程序要清晰易读。他们一生中可能会被阅读数百次;可能是由许多不同的人。所以,我的意思是:让你的代码易于理解更为重要。牢记效率是公平的;但只有当您 运行 遇到真正的问题时才开始 "optimize" - 然后进行适当的分析;例如使用分析器。 "Avoiding local variables to save memory" 不是你应该做的事情 "in general"。
局部变量存在于栈中,而不是堆中,因此与垃圾回收没有任何关系。 "variable" 和 "garbage collection" 这两个词实际上并不在同一个领域。对于引用类型,对象变量指向的是垃圾收集对象。由于您使用原始类型 (double
),所以这无关紧要。
是的,局部变量会稍微增加用于堆栈帧的内存(double
和 long
为 8 个字节,其余为 4 个字节),但是围绕它进行任何类型的优化太可笑了。您应该始终根据代码的可读性来决定是否使用局部变量。
我同意局部变量的占用空间非常小。我为他们辩护。整个练习的目的是向客户代码审阅者展示它们并不昂贵。他很担心。
无论如何,我的示例的行为符合预期 - 当 运行 超过 n 次时,局部变量没有额外的开销。就我而言,大约是 500。
所以我想自己回答:即使使用 jconsole(用它来调用 GC)也没有看到 GC 上的内存下降。可能是因为存在 GC 级别(全部和部分)。
但经过一定次数的迭代后,JVM 稳定下来,然后两个示例中使用的内存相同(有和没有局部变量)。就像其他人指出的那样:这无法描述研究,但是人们如何在一个大型项目中争论代码标准应该是什么?
有些开发人员喜欢指出这些小问题,而真正的问题出在别处。所以一些数据有助于表明局部变量在本地并得到垃圾收集。
如果您的内存使用率已经很高并且您担心它会被推到极限,那么您需要进行一些其他重构或横向扩展。
另一位开发人员正在讨论代码清晰度。他说使用局部变量会增加内存使用。我们认为它们将被垃圾收集。如果日志语句调用一个命中数据库/其他外部资源的函数,尤其是一个坏主意。
但下面的示例代码似乎支持他所说的 - 即使在使用 jconsole 调用 GC 之后,仍然看到 class 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));
}
}
为什么 class Worker 会占用更多内存 - 即使在多次调用 GC 然后断开 Jconsole 与进程的连接并等待几秒钟之后? (每 2 秒检查并打印一次。
可以看到代码和super是一样的classwork和worker除了do2()方法是一样的
注意: 我正在从 jconsole 连接并在工作循环完成后调用 GC。这个 GC 调用确实有效。调用 MX bean,可以看到可用内存下降。
旁注:我什至注意到,如果我从我的应用程序断开 jconsole 然后等待 4-5 秒 - 可用内存再次增加(我想连接到 jconsole 的开销)。这时我进行测量。重点是我做了一些工作,等待 JVM 稳定下来然后测量。
运行使用 jconsole 启动此程序并释放内存的视频 https://www.youtube.com/watch?v=MadBdryX8uk&
jconsole 是一个免费工具,在 bin 文件夹中 JDK。
修改了class,这里增加了循环次数,现在不管用哪个class,内存都是一样的!
别人可以运行吗?
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;
}
}
- 我知道本地变量更易读。我喜欢他们。我为他们辩护。
- 只是对这种行为感到好奇。
首先,System.gc() 可能 运行 垃圾收集器;或不。这确实不是测试内存消耗的可靠方法。
详情见When does System.gc() do anything。
下一步:记住有一个即时编译器。 JIT 最适合 "normal Java" 代码。很多时候,当开发人员试图成为 "smart" 并编写 "more efficient Java code" - 他们最终会比以前更糟 - 因为 JIT 无法有效地处理此 "special" 代码。
编写的程序要清晰易读。他们一生中可能会被阅读数百次;可能是由许多不同的人。所以,我的意思是:让你的代码易于理解更为重要。牢记效率是公平的;但只有当您 运行 遇到真正的问题时才开始 "optimize" - 然后进行适当的分析;例如使用分析器。 "Avoiding local variables to save memory" 不是你应该做的事情 "in general"。
局部变量存在于栈中,而不是堆中,因此与垃圾回收没有任何关系。 "variable" 和 "garbage collection" 这两个词实际上并不在同一个领域。对于引用类型,对象变量指向的是垃圾收集对象。由于您使用原始类型 (double
),所以这无关紧要。
是的,局部变量会稍微增加用于堆栈帧的内存(double
和 long
为 8 个字节,其余为 4 个字节),但是围绕它进行任何类型的优化太可笑了。您应该始终根据代码的可读性来决定是否使用局部变量。
我同意局部变量的占用空间非常小。我为他们辩护。整个练习的目的是向客户代码审阅者展示它们并不昂贵。他很担心。
无论如何,我的示例的行为符合预期 - 当 运行 超过 n 次时,局部变量没有额外的开销。就我而言,大约是 500。
所以我想自己回答:即使使用 jconsole(用它来调用 GC)也没有看到 GC 上的内存下降。可能是因为存在 GC 级别(全部和部分)。
但经过一定次数的迭代后,JVM 稳定下来,然后两个示例中使用的内存相同(有和没有局部变量)。就像其他人指出的那样:这无法描述研究,但是人们如何在一个大型项目中争论代码标准应该是什么?
有些开发人员喜欢指出这些小问题,而真正的问题出在别处。所以一些数据有助于表明局部变量在本地并得到垃圾收集。
如果您的内存使用率已经很高并且您担心它会被推到极限,那么您需要进行一些其他重构或横向扩展。