线程私有:程序计数器、虚拟机栈、本地方法栈程序员
线程共享:堆、方法区、直接内存算法
字节码解释器经过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。数组
在多线程的状况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候可以知道该线程上次运行到哪儿了。缓存
注意:程序计数器是不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的建立而建立,随着线程的结束而死亡。多线程
Java 虚拟机栈也是线程私有的,每一个线程都有各自的Java虚拟机栈,并且随着线程的建立而建立,随着线程的死亡而死亡,描述的是 Java 方法执行的内存模型。函数
Java 内存能够粗糙的区分为堆内存(Heap)和栈内存(Stack)其中栈就是如今说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。 (实际上,Java虚拟机栈是由一个个栈帧组成,而每一个栈帧中都拥有局部变量表、操做数栈、动态连接、方法出口信息)性能
局部变量表主要存放了编译器可知的各类数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不一样于对象自己,多是一个指向对象起始地址的引用指针,也多是指向一个表明对象的句柄或其余与此对象相关的位置)。spa
Java 虚拟机栈会出现两种异常: StackOverFlowError 和 OutOfMemoryError。线程
Java 虚拟机所管理的内存中最大的一块,Java 堆是全部线程共享的一块内存区域,在虚拟机启动时建立。此内存区域的惟一目的就是存放对象实例,几乎全部的对象实例以及数组都在这里分配内存。设计
Java 堆是垃圾收集器管理的主要区域,所以也被称做GC堆(Garbage Collected Heap).从垃圾回收的角度,因为如今收集器基本都采用分代垃圾收集算法,因此Java堆还能够细分为:新生代和老年代:再细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。
方法区与 Java 堆同样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,可是它却有一个别名叫作 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上二者并不等价。仅仅是由于 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就能够像管理 Java 堆同样管理这部份内存了。可是这并非一个好主意,由于这样更容易遇到内存溢出问题。
相对而言,垃圾收集行为在这个区域是比较少出现的,但并不是数据进入方法区后就“永久存在”了。
直接内存并非虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,可是这部份内存也被频繁地使用。并且也可能致使OutOfMemoryError异常出现。
JDK1.4中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它能够直接使用Native函数库直接分配堆外内存,而后经过一个存储在 Java 堆中的 DirectByteBuffer 对象做为这块内存的引用进行操做。这样就能在一些场景中显著提升性能,由于避免了在 Java 堆和 Native 堆之间来回复制数据。
本机直接内存的分配不会收到 Java 堆的限制,可是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
这部分主要与虚拟机用到的 Native 方法相关,通常状况下, Java 应用程序员并不须要关心这部分的内容。
(1)引用计数法
引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每一个对象实例都有一个引用计数。当一个对象被建立时,就将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例能够被看成垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。
优势:引用计数收集器能够很快的执行,交织在程序运行中。对程序须要不被长时间打断的实时环境比较有利。
缺点:没法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。
(2)可达性算法
在Java语言中,可做为GC Roots的对象包括下面几种:
a) 虚拟机栈中引用的对象(栈帧中的本地变量表)
b) 方法区中类静态属性引用的对象
c) 方法区中常量引用的对象
d) 本地方法栈中JNI(Native方法)引用的对象
标记-清除算法(Mark-Sweep)
标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,以下图所示。标记-清除算法不须要进行对象的移动,只需对不存活的对象进行处理,在存活对象比较多的状况下极为高效,但因为标记-清除算法直接回收不存活的对象,所以会形成内存碎片。
复制算法(Copying)
即将内存分为两部分 复制算法的提出是为了克服句柄的开销和解决内存碎片的问题。它开始时把堆分红 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集合(GC Roots)中扫描活动对象,并将每一个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。
标记-整理算法(Mark-compact)
标记-整理算法采用标记-清除算法同样的方式进行对象的标记,但在清除时不一样,在回收不存活的对象占用的空间后,会将全部的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,所以成本更高,可是却解决了内存碎片的问题。
分代收集算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不一样的区域。通常状况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区以外还有一个代就是永久代(Permanet Generation)。老年代的特色是每次垃圾收集时只有少许对象须要被回收,而新生代的特色是每次垃圾回收时都有大量的对象须要被回收,那么就能够根据不一样代的特色采起最适合的收集算法。
年老代(Old Generation)的回收算法(回收主要以Mark-Compact为主)
年轻代(Young Generation)的回收算法 (回收主要以Copying为主)