变量定义在for循环外面还是里面

结论:个人推荐,变量定义在for循环内部

原因:
1.在10亿次的数量级上,耗时上,两者没有区别
2.用javap进行反汇编后,指令码上基本无区别,所以在执行时也无区别(变量定义在for循环外没有赋值为null时,区别只是临时变量表中的位置不同;并赋值为null时,只是多了一个null压栈和出栈)
2.50.用gc日志彻底粉碎,变量定义在for循环内,会‘循环创建引用,浪费内存’的错误说法
3.jdk源码中,变量定义在for循环内部

过程:
原本用javap分析完反汇编后就可以结束并得出结论
结果我突发奇想打算实际证伪下‘循环创建引用,浪费内存’
然后就有了下面代码中描述的操作,结果分析了半天gc打印的日志,发现两种方式也一样,并没有多出来的‘大量引用占用空间’
但是又发现,gc日志中显示占用的内存空间与预估占用的内存空间不符(预估方式请参考另一篇文档,计算对象的大小:https://blog.csdn.net/jt781861965/article/details/114813576
后来才突然惊醒到,经过逃逸分析,这些对象都被直接在栈上分配了,并没有在堆上分配,所以也不存在gc的问题(逃逸分析默认是打开的,具体逃逸分析可参考:https://www.cnblogs.com/gnivor/p/6028001.html

**关闭逃逸分析后,**1000w次的数量级上,耗时上,两者没有区别(相较于开启逃逸分析,差距还是很大);javap进行反汇编后,指令码与开启逃逸分析时无区别;gc日志中显示的占用内存也是一样多,最后通过计算得出一个对象占用17byte(因为有jvm自身消耗的内存,计算存在误差,因此结果与实际的一个User对象16byte吻合)
至此,彻底粉碎了,变量定义在for循环内,会‘循环创建引用,浪费内存’的错误说法

测试代码(vm参数在代码中):

package com.myspringboot.jvm.foru;

public class ForUserTest {
    //开启逃逸分析使用下面的代码
    //vm参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+PrintGC  -XX:+PrintGCTimeStamps  -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+PrintGCApplicationStoppedTime
    public static void main(String[] args) {
        //填充内存
        byte[] bytes1 = new byte[5 * 1024 * 1024];
        long start = System.currentTimeMillis();
        //考虑到业务场景,在for中获取到对象后,没有后续操作的话,没有意义;所以在new完之后,还进行了简单的操作
        outFor();
        //inFor();
        System.out.println(System.currentTimeMillis() - start);
        //强制gc
        byte[] bytes2 = new byte[5 * 1024 * 1024];
    }

    public static void outFor() {
        User user = null;
        for (int i = 0; i < 999999999; i++) {
            user = new User();
            user.setI(i);
            user.getI();
        }
    }

    public static void inFor() {
        for (int i = 0; i < 999999999; i++) {
            User user = new User();
            user.setI(i);
            user.getI();
        }
    }

//分隔线=====================================================================================================

    //关闭逃逸分析使用下面的代码
    //vm参数:-server -XX:-DoEscapeAnalysis -verbose:gc -Xms400M -Xmx400M -Xmn300M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+PrintGC  -XX:+PrintGCTimeStamps  -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+PrintGCApplicationStoppedTime
//    public static void main(String[] args) {
//        long start = System.currentTimeMillis();
//        //考虑到业务场景,在for中获取到对象后,没有后续操作的话,没有意义;所以在new完之后,还进行了简单的操作
//        outFor();
//        //inFor();
//        System.out.println(System.currentTimeMillis() - start);
//        //强制gc
//        byte[] bytes2 = new byte[200 * 1024 * 1024];
//    }
//
//    public static void outFor() {
//        User user = null;
//        for (int i = 0; i < 9999999; i++) {
//            user = new User();
//            user.setI(i);
//            user.getI();
//        }
//    }
//
//    public static void inFor() {
//        for (int i = 0; i < 9999999; i++) {
//            User user = new User();
//            user.setI(i);
//            user.getI();
//        }
//    }
}
package com.myspringboot.jvm.foru;

public class User {
    private int i;

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }
}

用javap对class文件进行反汇编:

javap -c -l -v ForUserTest.class 

运行时常量池
在这里插入图片描述

outFor()
在这里插入图片描述

inFor()
在这里插入图片描述

对其中的outFor()方法的反汇编指令码进行了解释

         0: aconst_null
(将null压栈)
(栈:null)
         1: astore_0
(弹出,将null放到局部变量表‘0’位置)
(栈:)
(局部变量表‘0’位置:null)
         2: iconst_0
(0压栈)
(栈:0)
         3: istore_1
(弹出,将0放到局部变量表‘1’位置)
(栈:)
(局部变量表‘1’位置:0)
         4: iload_1
(将局部变量表‘1’位置的值压栈)
(栈:0)
         5: ldc           #8                  // int 999999999
(将运行时常量池中8位置的值999999999压栈)
(栈:0999999999)
         7: if_icmpge     34
(弹出栈中中的两个值,并进行比较,当局部变量表‘1’位置的值大于等于999999999时,跳转到34行开始执行;否则接着顺序执行)
(栈:)
        10: new           #9                  // class com/myspringboot/jvm/foru/User
(从运行时常量池中9位置的值找到class对象,分配空间,类属性赋初始值后压栈)
(栈:0xFF(引用地址))
        13: dup
(将栈顶的元素复制,并重新压栈(此时栈中有两个相同的元素))
(栈:0xFF0xFF)
        14: invokespecial #10                 // Method com/myspringboot/jvm/foru/User."<init>":()V
(从运行时常量池中10位置的值找到init方法,弹出一个元素,对类属性赋默认值,并执行构造方法)
(栈:0xFF)
        17: astore_0
(弹出,将0xFF放到局部变量表‘0’位置)
(栈:)
        18: aload_0
(将局部变量表‘0’位置的值压栈)
(栈:0xFF)
        19: iload_1
(将局部变量表‘1’位置的值压栈)
(栈:0xFF0)
        20: invokevirtual #11                 // Method com/myspringboot/jvm/foru/User.setI:(I)V
(从运行时常量池中11位置的值找到setI方法,弹出两个元素,执行setI方法,因为没有返回值,所以不需要压栈)
(栈:)
        23: aload_0
(将局部变量表‘0’位置的值压栈)
(栈:0xFF)
        24: invokevirtual #12                 // Method com/myspringboot/jvm/foru/User.getI:()I
(从运行时常量池中12位置的值找到getI方法,弹出一个元素,执行getI方法,并将结果压栈)
(栈:0)
        27: pop
(将栈顶元素弹出)
(栈:)
        28: iinc          1, 1
(将局部变量表‘1’位置的值加1,注意此操作没有压栈、弹出的动作)
(栈:)
        31: goto          4
(转到4)
        34: return
(返回)

开启逃逸分析的vm参数和gc日志
在这里插入图片描述
在这里插入图片描述

关闭逃逸分析的vm参数和gc日志
在这里插入图片描述
在这里插入图片描述

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值