一、代码展示
import java.util.HashMap;
import java.util.Map;
/**
* @Package: raymond
* @ClassName: Test
* @Author: tanp
* @Description: ${description}
* @Date: 2020/8/10 14:12
*/
public class Test {
public static void main(String[] args) {
TestThread2 testThread = new TestThread2();
testThread.start();
}
static class TestThread extends Thread {
@Override
public void run() {
for(int i=0; i<10; i++){
Map<String,Object> map = new HashMap<>();
map.put(i+"",i);
}
}
}
static class TestThread2 extends Thread {
Map<String,Object> map;
@Override
public void run() {
for(int i=0; i<10; i++){
map = new HashMap<>();
map.put(i+"",i);
}
}
}
}
二、问题
从上面的代码可以看到,TestThread和TestThread2的区别在于map的定义是放在for循环外面还是里面,那么就有一个问题,在写代码的时候到底是要将变量定义在外面还是里面呢。
三、JVM运行时数据区
首先我们要知道JVM运行时数据区中有几大块,其中的两块分别为栈和堆
JVM栈
JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址
堆(Heap)
它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。
(1) 堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的
(2) Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配
(3) TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
具体的jvm知识可以查看我的这篇博客https://blog.csdn.net/bird_tp/article/details/88560468
四、循环内和循环外定义变量的本质区别
结合上面的堆、栈信息,我们可以明白变量的定义是放在栈里面的,变量的实例化对象是放在堆里面的,所以我们前面两个线程中定义map的本质区别在于
线程TestThread在栈声明了10个栈内存地址,10个堆内存地址,而TestThread2只声明了1个栈内存地址,10个堆内存地址。TestThread2线程在循环实例化map时,只是通过一个栈内存地址移动引用指向了的10个堆内存地址。
总结起来就是
1、在循环外面的定义的变量,只在栈中声明了一次;
2、在循环里面的定义的变量,循环多少次就在栈中声明了多少次;
五、循环时具体如何定义变量
放在循环内部定义,确实会多次申请栈帧的内存空间,但其实话说回来,这样对性能的影响其实可以忽略不计,没什么大的问题。因为栈内存的分配非常之快,仅次于寄存器,所以,可以忽略不计。
总的来说,循环内的话,每次循环内部的局部变量在每次进for循环的时候都要重新定义一遍变量,也就是执行申请内存空间,变量压入堆栈的过程。
循环外定义的话,for循环一直用的是同一块内存空间,效率比较高,但是变量的作用域就大了,耗内存
所以,其实内外都可以的,总之就是空间和时间的权衡,看实际情况了,局部变量的数据类型、大小什么的都有关
下面粘贴下jdk7的hashmap的get方法,我们可以看到Object k就是定义在循环里面的。
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
六、总结
在for循环外面和里面定义变量,对于内存,性能影响其实没有什么多大区别。看个人使用情况而言,对于我个人使用而言,还是习惯定义在外面