
其中堆和元数据区(方法区)是多线程共享的,程序计数器、本地方法栈以及虚拟机栈是线程私有的。
用来存储指向下一条指令的地址,也就是即将执行的指令代码。
同时由于CPU需要不断切换各个线程,经过切换过后,每个线程都要记录当前线程执行到的位置,这个位置是通过PC寄存器进行存储的,这也是PC寄存器必须要线程私有的原因。
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
它是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
栈是运行是的单位,而堆是存储的单位。------即栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在那里。
主管Java程序的运行,它保存着方法的局部变量、部分结果,并参与方法的调用和返回。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧,对应着一次次的Java方法调用。它是线程私有的,生命周期与线程一致。
虚拟机栈中的数据都是以栈帧的格式存在的。在这个线程上正在执行的每个方法都各自对应一个栈桢。栈帧是一个内存区块,是一个数据集,维系着方法执行过程的各种数据信息。
在一条活动线程中,在某一个时间点上,只会有一个活动的栈帧。即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧,与当前栈帧相对应的方法就是当前方法,定义这个方法的类就是当前类。如果在该方法中调用了其他方法,对应的新的栈帧就会被创建出来,放在栈的顶端,成为新的当前帧。

如果当前方法调用了其他方法,方法返回之际,当前栈帧回传会此方法的执行结果给另一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧成为当前栈帧。
Java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令;另外一种是抛出异常。不管使用哪种方式,都会导致栈帧被弹出。
定义为一个数字数组,主要存储方法参数和定义在方法体内的局部变量。局部变量表的大小是在编译器确定下来的,在方法运行期间是不会改变的。
局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈桢的销毁,局部变量表也会随之销毁。
局部变量表中存放编译器可知的八种基本数据类型,引用类型,returnAddress类型的变量。局部变量表中,最基本的存储单位是Slot(变量槽)。32位以内的类型(包括returnAddress)占一个slot,64位的类型(double和long)占用两个slot。

JVM会为局部变量表中的每一个Slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值。
如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会被存放在index为0的slot处,其余的参数按照参数表顺序继续排列。
栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了期作用域,那么在其作用域之后生命的新的局部变量就可能会复用过期局部变量的槽位,从而起到节省资源的目的。
局部变量表中的变量式GC阶段重要的GCRoot。
本地方法就是一个Java调用非Java代码的接口。它并不是由Java语言实现的,JVM中的方法是由C语言实现的。
与Java环境外交互
当我们需要和操作系统进行交互或者是和某些硬件交换信息时,本地方法提供了一个简洁的接口,我们无需去了解Java应用以外的细节就可实现我们想要达到的效果。
本地方法栈用来管理本地方法的调用,是线程私有的。它的具体做法是本地方法栈中登记本地方法,在执行引擎执行时加载本地方法库。

一个JVM实例只存在一个堆内存,堆是Java内存管理的核心区域。堆区在JVM启动时被创建,其空间大小此时确定,它是JVM管理的最大的一块内存空间。
所有的线程共享Java堆,在这里还可以划分线程私有的缓存区(TLAB)。
所有的对象实例都在堆中分配内存。在方法结束之后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
堆,是垃圾回收的重点区域。

以以下代码为例:
Person person=new Person();
关于Person类的相关信息存放在方法区内,person引用变量被存放在虚拟机栈中,被创建出来的Person实例被存放在堆中。

JDK7之前堆被分为:新生代、老年代、永久代(新生代又分为Eden、S0、S1区)
JDK8之后堆被分为:新生代、老年代、元空间


在jdk8之后的堆中,新生代和老年代大小比例一般为1:2。而在新生代中,Eden区和S0、S1区的大小比例为8:1:1。

所有的Java对象都是在Eden区被new出来的。绝大部分的对象销毁都是在新生代发生的。

当年轻代中的Eden区空间不足时,会触发Minor GC。需要注意的是S0和S1满不会触发。 Minor GC会暂停用户线程,等GC结束之后,用户线程恢复运行。
指发生在老年代的GC,使不需要的对象从老年代消失。
若老年代空间不足,会先尝试触发Minor GC。若空间还不足,则会触发Major GC。若Major GC后,内存仍然不足,则会产生OOM。
优化GC性能。新生代中存放使新创建的对象,而大部分对象都是会在新生代中被回收利用。而老年代存放的是经历多次GC仍然存活的对象。对不同特点的对象采用不同的处理方法可极大的提升内存的利用率和性能。
如果对象在出生后并经历一次Minor GC后仍然存活,就会被转移到Survivor区中,年龄被设置为1。在Survivor区中每经过一次Minor GC,年龄就增长一岁。当年龄到达一定程度时,就会被晋升到老年代中。
特殊情况:大对象直接分配到老年代中。目的是为了避免在新生代中被多次复制影响内存性能。
堆区是线程共享区域,JVM为每个线程在Eden区中分配一个私有缓存区域。
使用TLAB可以避免一系列非线程安全问题,同时还能提升内存分配的吞吐量。

注意;一旦对象在TLAB空间分配内存失败,JVM可以通过加锁机制确保数据操作的原子性,从而直接在Eden空间内分配内存。

方法区与堆一样,都是线程共享的内存区域。
在JDK7以前,习惯把方法区叫做永久代。JDK8之后,使用元空间取代了永久代,并且将元空间放在本地内存之中。

它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存和运行时常量池等。
对于每个加载的类型(类,接口,枚举,注解),JVM必须在方法区存储以下类型信息:
随着类的加载而加载,被类的所有实例共享。
需要注意的是被static final修饰的变量在编译时就会被分配。
方法区内包含运行时常量池;字节码文件中,内部包含常量池。常量池表中包括各种对类型、方法的符号引用。
由于字节码需要数据支持,而这些数据往往比较大,无法直接存到字节码里。则通过将数据存放到常量池里,字节码中存放指向常量池的引用。
常量池表中的内容会在类加载后存放到方法区的运行时常量池中。运行时常量池中包含多种不同的常量,包括编译器就已经明确的数值字面量,也包括运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址,而是转换为真实地址。
JDK1.6之前,有永久代,静态变量存放在永久代中
JDK1.7,由永久代,但是已经逐步”去永久代“,字符串常量池、静态变量移除,保存在堆中
JDK1.8及以后,无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆中
对常量的回收策略:常量池中的常量没有被任何地方所引用
对类型的回收策略比如满足如下三个条件: