RunTimeDataArea(运行时数据区)ProgramCounterRegister(程序计数器)程序计数器是一块很小的内存区,可以看做是一个行号当前线程正在执行的字节码的指示器。功能字节码解释器通过改变程序计数器顺序读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。在多线程的情况下,程序计数器用来记录当前线程的执行位置,这样当线程切换回来的时候,可以知道线程上次跑到哪里去了。注意,程序计数器是唯一不会引起OutOfMemoryError的内存区域,它的生命周期随着线程的创建而产生,随着线程的结束而消亡。当执行Java方法时,程序计数器记录正在执行的虚拟机字节码指令的行号。Native方法执行时,程序计数器的值为空(Undefined)。JavaVirtualMachineStacks(虚拟机栈)功能为线程提供了私有内存空间,用于存放方法的局部变量、操作数栈、动态链接、方法导出等信息。每个线程都有自己的Java虚拟机栈,随着线程的创建而创建,随着线程的销毁而销毁。生命周期与线程相同。栈帧Java虚拟机栈中的每一个栈帧(StackFrame)都对应着各个方法的调用。每个栈帧由以下四部分组成:LocalVariables(局部变量表)主要存放编译时已知的各种变量数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(引用类型,不同于对象本身,可能是一个引用指针,指向对象的起始地址,或者指向一个代表对象的句柄或其他相对于对象的位置)。OpreandStacks(操作数栈)操作数栈用来存放计算过程中的中间结果,比如两个变量相加的结果。它是一种后进先出(LIFO)堆栈结构。操作数栈中的每个元素都可以是Java原始数据类型或对对象的引用。动态链接(DynamicLinking)主要服务于一个方法需要调用其他方法的场景。当一个Java源文件被编译成字节码文件时,所有的变量和方法引用都作为符号引用(SymbilicReference)存储在Class文件的常量池中。当一个方法要调用其他方法时,需要将常量池中指向该方法的符号引用转换为内存地址中的直接引用。动态链接的作用是将符号引用转换为对调用方法的直接引用。ReturnAddress(返回地址)返回地址实际上是一个指向代码行号的指针,用于标识方法返回后需要执行的下一条指令。当方法调用结束时,Java虚拟机根据返回地址返回调用方法的位置,继续执行程序。注意虚拟机栈的StackOverFlowError:如果栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度时,就会出现StackOverFlowError错误抛出。虚拟机栈的OutOfMemoryError:如果栈的内存大小可以动态扩展,如果动态扩展栈时虚拟机无法申请到足够的内存空间,则会抛出OutOfMemoryError异常。Java方法的返回有两种方式,一种是通过return语句正常返回,另一种是抛出异常。无论返回方法如何,都会弹出堆栈帧。换句话说,堆栈帧在调用方法时创建,在方法结束时销毁。无论该方法是正常完成还是异常完成,该方法都会结束。NativeMethodStack(本地方法栈)本地方法栈和虚拟机栈起到的作用非常相似。机器使用的本机方法服务。结合HotSpot虚拟机中的Java虚拟机栈。本地方法执行时,也会在本地方法栈上创建一个栈帧,用于存放本地方法的局部变量表、操作数栈、动态链接、退出信息。该方法执行后,相应的栈帧也会被弹出,内存空间也会被释放,同时也会出现StackOverFlowError和OutOfMemoryError两个错误。堆(heap)Java虚拟机管理的最大的一块内存。Java堆是所有线程共享的内存区域,在虚拟机启动时创建。这块内存区的唯一用途就是存放对象实例,几乎所有的对象实例和数组都在这里分配内存。“几乎”Java世界中的所有对象都分配在堆上。但是,随着JIT编译器的发展和逃逸分析技术的逐渐成熟,栈上的分配和标量替换优化技术会带来一些微妙的变化。所有分配到堆上的对象都逐渐变得不那么“绝对”了。从JDK1.7开始默认启用逃逸分析。如果某些方法中的对象引用没有被返回或被外部使用(即没有转义),那么这个对象就可以直接在栈上分配内存。Java堆是垃圾收集器管理的主要区域,因此也称为GC堆(GarbageCollectedHeap)。从垃圾回收的角度来看,由于目前的回收器基本都采用分代垃圾回收算法,所以Java堆也可以细分为:新生代和老年代;更详细:Eden、Survivor、Old等空间。进一步划分的目的是更好地回收内存,或者更快地分配内存。注意,在JDK7前后,堆内存通常分为新生代、老年代和永久代,但在JDK8版本之后,PermGen(永久)已经被元空间(metaspace)所取代,元空间使用直接内存。大多数情况下,对象会先分配到伊甸区。新生代垃圾回收后,如果对象还活着,则进入S0或S1,对象年龄加1(Eden区->Survivor区)。初始age变成1),当它的age增长到一定程度(默认15岁),就会被提升到老年代。对象晋升到老年代的年龄阈值可以通过参数-XX:MaxTenuringThreshold设置。堆最容易出现OutOfMemoryError错误:java.lang.OutOfMemoryError:GCOverheadLimitExceeded:当JVM花费太多时间执行垃圾收集而只能回收很少的堆空间时,就会出现此错误。java.lang.OutOfMemoryError:Javaheapspace:如果在创建新对象时,堆内存中没有足够的空间来存放新创建的对象,就会抛出这个错误。(与配置的最大堆内存有关,受制于物理内存的大小。最大堆内存可以通过-Xmx参数配置,如果没有特殊配置,会使用默认值。方法区(方法区)当虚拟机要使用一个类时,需要读取并解析Class文件获取相关信息,然后将这些信息存储在方法区中,方法区会存储类信息、字段信息、方法信息、常量、静态变量以及虚拟机加载的即时编译器编译代码缓存等数据。注:JDK1.8前后的方法区有什么区别?方法区、永久代和元空间的关系很像Java中接口和类的关系。类实现了接口,这里的类可以看作是永久代和元空间,而接口可以看作是方法区,也就是说永久代和元空间是方法区的两种实现HotSpot虚拟机到虚拟机规范。而且永久代在JDK1.8之前是方法区的实现,JDK1.8之后方法区的实现变成了元空间。方法区常用的参数有哪些?#1.8之前-XX:PermSize=N//方法区初始大小(永久代)-XX:MaxPermSize=N//方法区最大大小(永久代),超过这个值会抛出OutOfMemoryError异常:java.lang.OutOfMemoryError:PermGen##相对来说,该区域的垃圾回收行为比较少见,但数据进入方法区后并不会“永久存在”。#1.8之后-XX:MetaspaceSize=N//设置Metaspace的初始(和最小大小)-XX:MaxMetaspaceSize=N//设置Metaspace的最大大小#和permanentgeneration比较大的区别是如果不指定大小,它将随着创建更多的类,虚拟机用完所有可用的系统内存。运行时常量池的方法区保存了类的元数据。类的元数据来自类文件。除了类的版本、字段、方法、接口等描述信息外,Class文件还用于存储编译过程中产生的信息。各种字面量(Literal)和符号引用(SymbolicReference)的常量池表(ConstantPoolTable),运行时常量池是常量池在类加载后存储在方法区的内存表示。由于运行时常量池是方法区的一部分,自然受到方法区内存的限制。当常量池不能再申请内存时,会抛出OutOfMemoryError错误。字符串常量池(stringpool)字符串常量池是JVM为了提高性能,减少内存消耗,专门为字符串(String类)开辟的一块区域。主要目的是避免重复创建字符串。为什么JDK1.7将字符串常量池移到堆中?主要原因是永久代(方法区实现)的GC回收效率太低,GC只会在全堆收集(FullGC)时执行。在Java程序中,通常会有大量创建好的字符串等待回收。将字符串常量池放到堆上,可以更高效及时的回收字符串内存。DirectMemory(直接内存)直接内存不属于虚拟机运行时数据区,也不是虚拟机规范中定义的内存区,但这部分内存也经常被使用。并且它也可能导致OutOfMemoryError错误。JDK1.4引入了NIO类,可以使用Native函数直接读写堆外内存,避免了Java堆和Native堆之间来回拷贝数据,提高了读写效率。传统IO:Disk-》SystemMemoryBuffer-》JVMMemoryBuffer-》JVMMemoryNIO:Disk-》DirectMemoryBuffer-》JVMMemory直接内存的申请和释放由Unsafe对象管理。
