关于【禁止new对象时在for循环内定义申明变量】

简介

不知道是谁最先提出了一个不要将变量定义在循环内。

然后我们在代码扫描中有一项是:【禁止new对象时在for循环内定义申明变量】

我也好奇为什么不能?

是影响了性能?还是影响了内存?还是影响了垃圾回收?

我们站在制定这条规则的人的角度来想一想可能的原因:

  1. 变量定义在for循环内,每次都要为变量引用分配空间,效率低
  2. 变量定义在for循环内,有引用,不利于内存回收

真的如此吗?我们先从代码分析一下。

代码分析

我们先从代码的角度分析,局部变量定义在循环外和循环内的区别。

不熟悉局部变量和字节码的朋友可以先看一看:
JVM字节码与局部变量

示例代码如下:

import java.util.HashMap;
import java.util.Map;

public class Main {

    private static final int limit = 1000_0000;

    public static void main(String[] args) {

    }

    public void out() {
        Map<String, String> base;
        for (int i = 0; i < limit; i++) {
            base = new HashMap<>();
            base.put("1", "哦啊好");
            System.out.println(base);
        }
    }

    public void in() {
        for (int i = 0; i < limit; i++) {
            Map<String, String> base = new HashMap<>();
            base.put("1", "哦啊好");
            System.out.println(base);
        }
    }
}

我们可以:

# 编译class
javac Main.java

# 查看字节码
javap -v Main.class

在这里插入图片描述

我们可以看到,无论变量定义在循环外还是循环内,base变量在局部变量表都只占据了一个slot。

自然,也就没有每次循环都要给变量引用分配空间的问题。至于base堆上的内存空间,无论变量定义在循环内外,肯定都要分配,没有区别。

至于垃圾回收,无论变量base在循环内还是循环外,局部变量base引用的都是最新new出来的Map对象,旧的对象不被引用,自然可以被回收,一点也不影响。

基本一模一样,不信我吗可以看看反编译之后的代码。

反编译之后对比

//
// Source code recreated from a .class file
// (powered by FernFlower decompiler)
//

package vip.meet;

import java.util.HashMap;
import java.util.Map;

public class Main {
    private static final int limit = 10000000;

    public Main() {
    }

    public static void main(String[] args) {
    }

    public void out() {
        for(int i = 0; i < 10000000; ++i) {
            Map<String, String> base = new HashMap();
            base.put("1", "哦啊好");
            System.out.println(base);
        }

    }

    public void in() {
        for(int i = 0; i < 10000000; ++i) {
            Map<String, String> base = new HashMap();
            base.put("1", "哦啊好");
            System.out.println(base);
        }

    }
}

我们可以看到反编译之后,out方法的变量被放在循环中了,变得和in一模一样了。

有朋友肯能说,我看到了,他们字节码不一样啊,反编译出来的代码不能说明什么。

把out稍微改一下,将int i定义放在Map的base之前,字节码就一样了。

public void out() {
    int i = 0;
    Map<String, String> base;
    for (; i < limit; i++) {
        base = new HashMap<>();
        base.put("1", "哦啊好");
        System.out.println(base);
    }
}

如果,还觉得有疑问,我们下面来做点性能测试。

性能测试

我们先用benchmark做一些性能测试:

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;

import java.util.HashMap;
import java.util.Map;

public class BenchmarkLocalVarTest {

    private static final int limit = 10000_0000;

    @Benchmark
    public void out() {
        Map<String, String> base;
        for (int i = 0; i < limit; i++) {
            base = new HashMap<>();
            base.put("1", "哦啊好");
        }
    }

    @Benchmark
    public void in() {
        for (int i = 0; i < limit; i++) {
            Map<String, String> base = new HashMap<>();
            base.put("1", "哦啊好");
        }
    }

    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder()
                .include(BenchmarkLocalVarTest.class.getSimpleName())
                .forks(1)
                .timeout(TimeValue.seconds(10))
                .threads(1)
                .warmupIterations(1)
                .warmupTime(TimeValue.seconds(10))
                .measurementTime(TimeValue.seconds(2))
                .measurementIterations(5)
                .build();
        new Runner(options).run();
    }
}
Benchmark                   Mode  Cnt  Score   Error  Units
BenchmarkLocalVarTest.in   thrpt    5  0.349 ± 0.037  ops/s
BenchmarkLocalVarTest.out  thrpt    5  0.335 ± 0.039  ops/s

如果还有朋友觉得这个测试只能说明性能,不能看出内存和垃圾回收,那我们在来看一下内存和垃圾回收。

内存与垃圾回收情况

变量定义在循环内gc情况:
变量定义在循环内gc情况
变量定义在循环内内存情况:
变量定义在循环内内存情况
变量定义在循环外gc情况:
变量定义在循环外gc情况
变量定义在循环外内存情况:
变量定义在循环外内存情况
我们可以看到,没有太大区别,如果觉得上面逻辑太简单,可以自己试一下更复杂的情况。

JDK和常用框架怎么写

如果这样还不放心,那我们再看一下其他大神怎么写的:

JDK LinkedBlockingDeque:
JDK-LinkedBlockingDeque

Spring ResourceUrlProvider:
Spring-ResourceUrlProvider
apache io FileUtils:
apache io FileUtils

guava Files:
guava Files

总结

综上所述,【禁止new对象时在for循环内定义申明变量】并没有什么意义。

甚至,还带来了负面效果:

  1. 让变量作用域变大,存在被无意引用的风险
  2. 更容易产生变量名冲突
  3. 可读性变差

依赖

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.36</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.36</version>
</dependency>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值