​ JVM类加载器将类数据的字节码.Class文件加载到内存,并对数据进行验证、准备、解析、初始化。最终加载到运行数据区。JVM运行时会分配好方法区和堆,每遇到一个线程,就会为其分配一个程序计数器、Java栈、本地方法栈。当线程终止时,程序计数器、Java栈、本地方法栈所占用的内存空间会被释放掉。
方法区、堆 是线程共享; 程序计数器、Java栈和本地方法栈 是非线程共享。GC只发生在线程共享的区域(大部分发生在堆上)

方法区

​ 有时候称为永久代,存储类信息(名称、修饰符、方法及属性)、静态变量、final类型常量、编译器编译后的代码等, 当使用Class对象中的getName()getInterface等方法时,这些数据都是来自方法区。
方法区也是全局共享的,一定条件下也会发生GC,这里的GC主要是方法区里的常量池和类型的卸载。

​ 在方法区中,还有个重要的部分运行时常量池,用于存储静态编译产生的字面量和符号引用(字面量是Java中常量的意思,比如文本字符串、final修饰的常量;符号引用则包括类和接口的全限定名、方法名、字段名等)。运行时产生的常量也会存储在常量池中(比如String的intern方法)。

​ Java中的堆主要用来存储对象实例及数组内容。堆是线程共享的,因此在其上进行对象内存的分配均需要加锁,因此会导致new对象的开销比较大。 JVM中只有一个堆,是Java垃圾回收的主要区域。

​ Sun Hotspot JVM 为提高对象内存分配效率,对所创建的每个线程都会分配一块独立的空间 TLAB(Thread Local Allocation Buffer),具体大小由JVM根据运行情况计算得到。在TLAB上分配对象时不需要加锁,性能比较高。但是如果对象过大的话仍然是直接使用堆空间分配。

​ 堆空间分为 老年代年轻代,刚创建的放在年轻代,老年代中存放生命周期长久的实例对象。年轻代中存储的对象,经过多次GC后仍然存活的对象会移动到老年代中进行存储。

Java栈

​ Java栈也叫虚拟机栈,是线程私有的(生命周期与线程相同)。每个线程创建的同时会创建自己的JVM栈,互不干扰。Java栈是Java方法执行的内部模型

​ Java栈中存放的是一个个战帧,每个栈帧对应一个被调用的方法。在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。

线程每执行一个方法,就会随之创建一个栈帧,并将新建的栈帧压栈。当方法执行完毕后,栈帧便会出栈。

  • 局部变量表:用来存储方法中的局部变量。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译期就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。
  • 操作数栈:程序中的所有计算过程都是在借助于操作数栈来完成的。
  • 指向运行时常量池的引用:指向运行时常量
  • 方法返回地址:当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

本地方法栈

​ 存储每个native方法调用的状态。本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的

注:在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。

程序计数器

​ PC 寄存器。程序计数器是每个线程所私有的。在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的。每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置。

由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。