Java内存区堆和栈
Java虚拟机在运行时在内存中开辟一片内存区域,并且把这一片内存区域进行了更加细致的划分
JVM内存的划分有五片:
-
寄存器
-
本地方法区
-
方法区;
-
栈内存;
-
堆内存。
我们谈一下堆内存和栈内存
- 栈内存
栈内存用于存放在函数中定义的一些基本数据类型的变量和对象的引用变量。
当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。
栈的优点是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。
缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
- 堆内存
堆内存用于存放类,由new创建的对象,数组等。
在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。
在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量——引用变量,引用变量指向堆内存中的数组或对象。
引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
当引用变量修改指向的对象时,如果之前指向的对象没有任何引用,那么就会被下一次垃圾回收器运行时回收。
堆的优点是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。
缺点是,由于要在运行时动态分配内存,存取速度较慢。
- 引用与对象
写一个小例子看一下引用和对象的关系:
String str = "abcd";
str = "abc";
System.out.println(str);
运行结果是abc
String对象不可变,但是其引用可变
String str = "abcd";
这行代码是声明一个String类型的引用变量叫做str,str储存在栈内存中,然后在堆内存中创建一个String对象(值为"abcd"),再把引用a指向String对象。
str = "abc";
这行代码是在堆内存中另外创建了一个String对象(值为"abc"),然后将引用a指向新创建的String对象,而不是把原来的内存中的那个“abcd”的String对象值变为“abc”。
这时在堆内存中有两个String对象,如果之后内存中那个值为“abcd”的String对象没有任何引用,那么就会在下一次垃圾回收器运行时被回收。
String和所有的基本类型包装类的对象都是不变量。堆内存中的不变量对象的值是不能被改变的。
- 堆、栈、方法区和常量池
String s1 = "abcd";
String s2 = "abcd";
boolean bo = s1==s2;//true
boolean bo1 = s1.equals(s2);//true
System.out.println("s1==s2 "+bo);
System.out.println("s1.equals(s2) "+bo1);
String s3 = new String("hello");
String s4 = new String("hello");
boolean b = s3==s4;//false
boolean b1 = s3.equals(s4);//true
System.out.println("s3==s4 "+b);
System.out.println("s3.equals(s4) "+b1);
String s5 = "布拉布拉";
String s6 = new String("布拉布拉");
boolean ba = s5==s6;//false
boolean ba1 = s5.equals(s6);//true
System.out.println("s5==s6 "+ba);
System.out.println("s5.equals(s6) "+ba1);
- 栈的特性
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。
假设我们同时定义:
int a = 3;
int b = 3;
编译器先处理int a = 3;
首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。
接着处理int b = 3;
在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。
这样,就出现了a与b同时均指向3的情况。
这时,如果再令a=4;
那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。
因此a值的改变不会影响到b的值。
要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享
是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。
而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量
- 内存分配策略
按照编译原理的观点,程序运行时的内存分配有三种策略,分别是
- 静态存储分配
- 栈式存储分配
- 堆式存储分配
- 静态存储分配
静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间。
这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.。
- 栈式存储分配
栈式存储分配也称为动态存储分配,是由一个类似于堆栈的运行栈来实现的。
和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存。
和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。
- 堆式存储分配
堆式存储分配专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例。
堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.
4.小结
静态存储分配要求在编译时知道所有变量的存储要求
栈式存储分配要求在过程的入口处知道所有的存储要求
堆式存储分配负责以上两种都未知的数据