栈、堆、方法区之间的关系

先放一张图:

1.栈内存中放哪些东西?

  • 基本类型的变量,例如int a=3中的a;
  • 对象的引用变量,例如Thread t=new Thread();中的t。

当在代码块中定义一个变量时,Java就在栈中为这个变量分配内存空间;当超过变量的作用域后,Java会自动 释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。

2.堆内存中存放哪些东西?

存放由new创建的对象和数组。

在堆中存放的内存,由Java虚拟机垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量持有的内容等于数组或者对象在堆内存中的首地址。在栈中的这个特殊的变量,就成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

3.静态区/方法区:

方法区(method)也叫做静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。

方法区中包含的都是在整个程序中永远唯一的元素,例如class,static变量。

全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量放在相邻的另一块区域。

堆内存和栈内存,两者的区别?

(1)引用变量是普通变量,定义时在栈内存中分配,引用变量在程序运行到作用域外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在的代码块之外,数组和对象本身占用的堆内存也不会被释放。数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉,这个也是Java比较占内存的主要原因。实际上,栈中的引用变量指向堆内存中的变量,这就是Java中的指针。

(2)通俗来讲,堆是用来存放对象的,而栈是用来执行程序的。

(3)jvm只有一个堆区(heap),被所有线程共享;

每个线程包含一个栈区(stack),每个栈中的数据都是私有的,其他的栈不能访问,但是同一个栈中变量是共享的;分为3个部分:基本类型变量区,执行环境上下文,操作指令区。

为什么要有堆和栈?这样设计有什么好处?

(1)Java自动管理堆和栈,程序员不能直接地设置栈和堆。

(2)Java的堆是一个运行时数据区。堆是由JVM的垃圾回收器自动管理的。堆的优势是可以在程序运行时,动态地分配内存
大小,但是正是由于这个原因,它的存取速度较慢。

(3)栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小和生存期是必须确定的,缺乏灵活性。

栈有一个很重要的特性,就是存在栈中的数据可以共享。假设我们同时定义:

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, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。

下面看这段代码在内存中的分配流程:

    package test01;
     
    public class StackHeadMethod {
     
    	public static void main(String[] args) {
     
    	int a = 1;                // 在栈中分配内存空间,A
        String b = "testBName";   // 在栈和方法区中分配内存空间, B
        Test test = new Test();   // 在栈中堆中分配内存空间, C

        test.testMethod(a, b);    // , D
	}
}
 
class Test {
	
	private int testA;                    // 在堆中分配内存空间, E 
	private String testB;                 // 在堆中分配内存空间, F
	private static final int testC = 2;   // 在堆和方法区中分配内存空间, G

        public void testMethod(int testA, String testB){
    		this.testA = testA;
    		this.testB = testB;
    	}
    }

首先,JVM将StackHeadMethod.class、Test.class装载到方法区(JVM会执行启动类装载器、扩展类装载器和类路径装载器,在此只详细讲解针对本测试代码的字节码文件的执行)。其中方法区中的2是Test.java中的静态变量,在类加载的时候就在方法区中的静态存储空间分配内存。

然后,在StackHeadMethod.class文件中,找到main方法,开始执行main方法。将main方法在栈中开辟一个空间,称为栈帧。执行下面A、B、C、E、F、G行代码。

再然后,执行test.testMethod(a, b);这一行,在栈中新分配一个栈帧,调用test中的testMethod方法。

最后,testMethod方法执行完之后,testMethod栈帧从栈中释放空间,然后main方法执行完之后,main栈帧也释放空间,最后堆中的对象和方法区中的静态变量、字符串和字节码指令都没被使用时,根据java虚拟机的垃圾回收机制,进行对垃圾的回收。

以上,是执行一段代码,对内存进行开辟和释放的整个过程。

参考:栈,堆,方法区关系图

发布了77 篇原创文章 · 获赞 15 · 访问量 6万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览