根据Java虚拟机规范,JVM的内存粗略分为3个区:堆(heap)、栈(stack)和方法区(method area)
栈区(JVM Stack)
每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
堆区(Java Heap)
存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
方法区
又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
为了更清楚地搞明白发生在运行时数据区里的黑幕,我们来准备2个小道具(2个非常简单的小程序)。
分步讲解
OK,让我们开始行动吧,出发指令就是:“java AppMain”,Let’s GO!
第一步:OS → JVM进程 → AppMain.class → 方法区
OS收到java指令,启动一个Java虚拟机进程,该进程首先从classpath列表中找到AppMain.class文件,读取文件中的二进制数据,然后把AppMain的类信息存放到运行时数据区的“方法区“中。这一过程称为AppMain类的加载过程。
第二步:
Java虚拟机定位到方法区中AppMain类的Main()方法的字节码,开始执行它的指令。
这个main()方法的第一条语句就是:Sample test1 = new Sample(“测试1”);
语句很简单,就是让Java虚拟机创建一个Sample实例,并且把实例引用赋值给test1变量。
第三步:
看看Java虚拟机是怎么执行这个语句的。
Java虚拟机接收到创建新实例的语句,直奔方法区去试图找Sample类的类信息,这时方法区并没有Sample的类信息;
Java虚拟机就会尝试自行加载Sample类,把类信息放入方法区;
类信息找到后,Java虚拟机接着在Heap堆区为新Sample实例分配内存。该实例会持有一个指向方法区中Sample类信息的引用,实际上是Sample类信息在方法区中的内存地址。这个引用会存放在Sample实例的数据区。
在Java虚拟机进程中,每个线程都有一份私有的JVM Stack(Java虚拟机栈),用来跟踪线程运行中一系列的方法调用信息。Stack中每个元素是一个Frame(栈帧),当线程调用一个方法时就会向Stack中压入一个新Frame。Frame主要存储方法的参数、局部变量和运行过程的临时数据。接着看JVM的动作。test1是main()方法中定义的局部变量,它会被添加到执行main()方法的主线程的JVM Stack中,而”=“会把Heap堆中的Sample实例引用赋值给test1。
到这里Java虚拟机就完成了这个简单语句的执行任务。
第四步:
Java虚拟机继续执行下一条指令,在Heap堆区继续创建另一个Sample实例,然后依次执行它们的printName()方法。
当JAVA虚拟机执行test1.printName()方法时,JAVA虚拟机根据局部变量test1持有的引用,定位到堆区中的Sample实例,再根据Sample实例持有的引用,定位到方法去中Sample类的类型信息,从而获得printName()方法的字节码,接着执行printName()方法包含的指令。
参考: Java里的堆(heap)栈(stack)和方法区(method)