
程序计数器是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。
在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
为什么程序计数器是线程私有的?
由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储。

如果线程正在执行的是Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地方法,这个计数器值应该是空。
这是唯一一个在Java虚拟机规范中没有规定OutOfMemoryError情况的区域。
Java虚拟机栈也是线程私有的,它与线程的生命周期相同。
Java虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法返回地址等信息。

每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
对于栈来说不存在垃圾回收问题(栈存在溢出的情况)。
可以使用参数 -Xss 选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度。
局部变量表主要存放方法的参数和定义在方法体内的局部变量,包括编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等于对象本身,可能是一个指向对象起始地址的指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。

如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列。

这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位的长度的long和double类型的数据会占用两个变量槽,其余的数据类型只占用一个。

局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。
我们知道类变量表有两次初始化的机会,第一次是在“准备阶段”(类加载器子系统中“链接阶段”(加载——>链接(验证、准备、解析)——>初始化)的“准备阶段”),执行系统初始化,对类变量设置零值;另一次则是在“初始化”阶段,赋予程序员在代码中定义的初始值。

和类变量初始化不同的是,局部变量表不存在系统初始化的过程,这意味着一旦定义了局部变量则必须人为的初始化,否则无法使用。


静态链接
当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时,这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。
动态链接
如果被调用的方法在编译期无法被确定下来,只能够在程序运行期将调用的方法的符号转换为直接引用,由于这种引用转换过程具备动态性,因此也被称之为动态链接。
静态链接和动态链接不是名词,而是动词,这是理解的关键。
对应的方法的绑定机制为:早期绑定(Early Binding)和晚期绑定(Late Binding)。绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。
Java中任何一个普通的方法其实都具备虚函数的特征,它们相当于C语言中的虚函数(C中则需要使用关键字virtual来显式定义)。如果在Java程序中不希望某个方法拥有虚函数的特征时,则可以使用关键字final来标记这个方法。
虚方法和非虚方法
如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的。这样的方法称为非虚方法。
静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法。其他方法称为虚方法。
虚拟机中提供了以下几条方法调用指令:
普通调用指令:
动态调用指令:
前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而invokedynamic指令则支持由用户确定方法版本。其中invokestatic指令和invokespecial指令调用的方法称为非虚方法,其余的(fina1修饰的除外)称为虚方法。
存放调用该方法的pc寄存器的值。一个方法的结束,有两种方式:
无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的PC计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。
正常完成出口和异常完成出口的区别在于:通过异常完成出口退出的不会给他的上层调用者产生任何的返回值。
Java 7及之前堆内存逻辑上分为三部分:新生区+养老区+永久区

Java 8及之后堆内存逻辑上分为三部分:新生区+养老区+元空间

Java堆区用于存储Java对象实例,那么堆的大小在JVM启动时就已经设定好了,大家可以通过选项"-Xmx"和"-Xms"来进行设置。
-XX:InitialHeapSize-XX:MaxHeapSize一旦堆区中的内存大小超过“-Xmx"所指定的最大内存时,将会抛出OutOfMemoryError异常。
默认情况下:
Java堆区进一步细分的话,可以划分为==年轻代(YoungGen)和老年代(oldGen)==
其中年轻代又可以划分为Eden空间、Survivor0空间和Survivor1空间(有时也叫做from区、to区)

配置新生代与老年代在堆结构的占比:
-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5几乎所有的Java对象都是在Eden区被new出来的。绝大部分的Java对象的销毁都在新生代进行了。
为新对象分配内存是一件非常严谨和复杂的任务,JVM的设计者们不仅需要考虑内存如何分配、在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还需要考虑GC执行完内存回收后是否会在内存空间中产生内存碎片。
-Xx:MaxTenuringThreshold= N总结:
针对Hotspot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial GC),一种是整堆收集(FullGC)
部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:
整堆收集(Full GC):收集整个java堆和方法区的垃圾收集
年轻代GC(Minor GC)触发机制:
老年代GC(Major GC / Full GC)触发机制:
Full GC触发机制(触发Full GC执行的情况有如下五种:):

-XX:UseTLAB”设置是否开启TLAB空间。-XX:TLABWasteTargetPercent” 设置TLAB空间所占用Eden空间的百分比大小。栈、堆、方法区的交互关系:

java.lang.OutOfMemoryError: PermGen space 或者java.lang.OutOfMemoryError: Metaspace设置方法区内存的大小:
jdk7及以前
-XX:Permsize-XX:MaxPermsizeOutOfMemoryError:PermGen space。JDK8以后
-XX:MetaspaceSize 和 -XX:MaxMetaspaceSize指定-XX:MetaspaceSize=21M -XX:MaxMetaspaceSize=-1//即没有限制。OutOfMemoryError:Metaspace-XX:MetaspaceSize:设置初始的元空间大小。对于一个64位的服务器端JVM来说,其默认的-XX:MetaspaceSize值为21MB。这就是初始的高水位线,一旦触及这个水位线,Full GC将会被触发并卸载没用的类(即这些类对应的类加载器不再存活),然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspaceSize时,适当提高该值。如果释放空间过多,则适当降低该值。-XX:MetaspaceSize设置为一个相对较高的值。
它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。

类型信息
对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:
域(Field)信息
域的相关信息包括:域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient的某个子集)
方法(Method)信息
JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:
方法名称
方法的返回类型(或void)
方法参数的数量和类型(按顺序)
方法的修饰符(public,private,protected,static,final,synchronized,native,abstract的一个子集)
方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native方法除外)
异常表(abstract和native方法除外)
每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引

方法区,内部包含了运行时常量池
字节码文件,内部包含了常量池
一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述符信息外,还包含一项信息就是常量池表(Constant Pool Table),包括各种字面量和对类型、域和方法的符号引用
为什么需要常量池?
一个java源文件中的类、接口,编译后产生一个字节码文件。而Java中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里,换另一种方式,可以存到常量池,这个字节码包含了指向常量池的引用。在动态链接的时候会用到运行时常量池。