最近看JVM内存模型,看了不少文章,大都讲到JVM将内存区域划分分:Mehtod-Area(No heap) 方法区,Heap(堆)区,Program Counter Register(程序计数器),VM Stack(虚拟机栈),Native Mehtod Stack(本地方法栈),其中方法区和堆区是线程共享的。而虚拟机栈,本地方法栈,程序计数器是非线程共享的。每一个java程序在本身的虚拟机上,而后告知虚拟机程序的运行入口。再被虚拟机字节码解释器加载运行。JVM运行的时候都会 分配好方法区和堆区,每遇到一个线程则分配程序计数器,虚拟机栈,本地方法栈。当线程运行结束时,则程序计数器,虚拟机栈,本地方法栈的内存空间也会被释放掉。这也就是为何把内存区域划分分线程共享和非共享的缘由,非线程共享的那三个区域其生命周期和所属的线程相同,随线程的结束而结束。而线程共享的区域和JAVA程序运行的生命周期相同。这也是垃圾回收老是发生在线程共享区域的缘由。接着就是把各个区域的做业以及运行时存储类的那些数据作了分类概述:html
先引入一张借鉴的图片,有个清晰的轮廓:java
1. 程序计数器:程序员
程序计数器用于保存当前正在执行的程序的内存地。JAVA中程序的分支,跳转,循环,异常处理和多线程环境中线程的恢复,都依赖于这个功能。由于程序执行的轨迹不可能一直是线性执行的,当有多个线程交叉执行时,被中断的线程的程序当前执行到那一条内存地址必然要记录下来。以便被中断的线程获得恢复执行的机会时能够继续按中断时的指令执行下去。因此每一个线程都有一个独立的程序计数器,各个线程之间的计数器互不影响,独立存储,属于线程所私有的。算法
2. JVM 栈:数组
虚拟机栈,也叫栈内存,是线程建立时建立,它的生命周期是跟随线程的生命周期,线程结束栈内存也就释放了。JAVA栈老是线程关联到一块儿,每当建立一个线程,JVM就会为该线程建立对应的栈,这个JAVA栈中有包含有多个栈帧。这个栈帧是和每一个方法关联的,每运行一个方法就建立一个栈帧。每一个栈帧包含局部变量表(包含了对应的方法参数和局部变量),操做栈(Operand Stack,记录出栈、入栈的操做),动态连接和方法出口等信息。每一个方法被调用直到执行完毕的过程,对应这栈帧在虚拟栈的入栈和出栈的过程。因为JVM 栈和线程关联起来的,Java栈数据不是线程共享的,因此不用关心其数据的一致性问题,也不会存在同步锁的问题。但在Java虚拟机规范中,对这个区域规定了两种异常情况:若是线程请求的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError异常;若是虚拟机能够动态扩展,若是扩展时没法申请到足够的内存,就会抛出OutOfMemoryError异常。在Hot Spot虚拟机中,可使用-Xss参数来设置栈的大小。栈的大小直接决定了函数调用的可达深度。缓存
3. Heap 堆:安全
堆,一种数据结构,FIFO,先进先出。堆也是是JVM管理内存中最大的一块,是被全部JAVA线程所共享的,是非线程安全的。在JVM启动时建立,专门用来保存对的实例。例:new Person();出来的对象都存放在堆中,还有数组对象。实际上也只是保存对象实例的属性值,属性的类型和对象自己的类型,并不保存对象的方法(已帧的形式保存在栈中),在堆中分配必定的内存保存对象的实例。对象实例在堆中分配好之后,须要在栈中保存4个字节的heap内存地址,用来定位对象实例在堆中的位置,便宜找到该对象。因此,JAVA堆区也是GC主要工做的场所。从内存回收的角度来看,因为如今的GC都采用分代回收的算法,因此java堆还能够细分为,新生代,老年代;新生代再细分为Eden空间,From survivor,To survivor空间等。数据结构
4. Method Area 方法区:多线程
方法区主要存放了加载类定义的数据(名称,修饰符等),类中的静态常量,类中定义为final类型的常量,类中的Field信息,类中的方法信息,当在程序中经过Class对象的getName.isInterface等方法获取信息时,这些数据都来自方法区。java方法区是全部线程所共享的。不像Java堆中其余部分同样会频繁被GC回收,它存储的信息相对比较稳定,在必定条件下会被GC,当方法区要使用的内存超过其容许的大小时,会抛出OutOfMemory的错误信息。方法区也是堆中的一部分(正由于方法区所存储的数据与堆有一种类比关系,因此它还被称为 Non-Heap),就是咱们一般所说的Java堆中的永久区 Permanet Generation,大小能够经过参数来设置,能够经过-XX:PermSize指定初始值,-XX:MaxPermSize指定最大值。并发
5. Constant Pool常量池:
方法区有一个很是重要的区,叫作运行时常量池(RCP)。常量池中存储了如字符串、final变量值、类名和方法名常量。常量池在编译期间就被肯定,在字节码文件(Class文件)中,除了有类的版本、字段、方法、接口等先关信息描述外,还有常量池(Constant Pool Table)信息。用于存储编译器产生的字面量和符号引用。这部份内容在类被加载后,都会存储到方法区中的RCP。字面量就是字符串、final变量等。类名和方法名属于引用量。引用量最多见的是在调用方法的时候,根据方法名找到方法的引用,并以此定为到函数体进行函数代码的执行。引用量包含:类和接口的权限定名、字段的名称和描述符,方法的名称和描述符。值得注意的是,运行时产生的新常量也能够被放入常量池中,好比 String 类中的 intern() 方法产生的常量。
6.Native Method Stack 本地方法栈:
本地方法栈和Java栈所发挥的做用很是类似,区别不过是Java栈为JVM执行Java方法服务,而本地方法栈为JVM执行Native方法服务。本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。
这里重点要说的是堆里对象实例的分配和存储:
java是面向对象的语言,所以对象的建立无时无刻都存在。在语言层面,使用new关键字便可建立出一个对象。可是在虚拟机中,对象建立的建立过程则是比较复杂的。
首先,虚拟机运到new指令时,会去常量池检查是否存在new指令中包含的参数,好比new People(),则虚拟机首先会去常量池中检查是否有People这个类的符号引用,而且检查这个类是否已经被加载了,若是没有则会执行类加载过程。
在类加载检查事后,接下来为对象分配内存固然是在java堆中分配,而且对象所须要分配的多大内存在类加载过程当中就已经肯定了。为对象分配内存的方式根据java堆是否规整分为两个方法:一、指针碰撞(Bump the Pointer),二、空闲列表(Free List)。指针碰撞:若是java堆是规整的,即全部用过的内存放在一边,没有用过的内存放在另一边,而且有一个指针指向分界点,在须要为新生对象分配内存的时候,只须要移动指针画出一块内存分配和新生对象便可;空闲列表:当java堆不是规整的,意思就是使用的内存和空闲内存交错在一块儿,这时候须要一张列表来记录哪些内存可以使用,在须要为新生对象分配内存的时候,在这个列表中寻找一块大小合适的内存分配给它便可。而java堆是否规整和垃圾收集器是否带有压缩整理功能有关。
1. 在为新生对象分配内存的时候,同时还须要考虑线程安全问题。由于在并发的状况下内存分配并非线程安全的。有两种方案解决这个线程安全问题,一、为分配内存空间的动做进行同步处理;二、为每一个线程预先分配一小块内存,称为本地线程分配缓存(Thread Local Allocation Buffer, TLAB),哪一个线程须要分配内存,就在哪一个线程的TLAB上分配。内存分配后,虚拟机须要将每一个对象分配到的内存初始化为0值(不包括对象头),这也就是为何实例字段能够不用初始化,直接为0的缘由。接来下,虚拟机对对象进行必要的设置,例如这个对象属于哪一个类的实例,如何找到类的元数据信息。对象的哈希吗、对象的GC年代等信息,这些信息都存放在对象头之中。执行完上面工做以后,全部的字段都为0,接着执行<init>指令,把对象按照程序员的指令进行初始化,这样一个对象就完整的建立出来。
二、对象的内存布局
对象在内存的存储布局中包括:对象头、实例数据、对齐填充
对象头(Header):包含两部分信息。一、存储对象自身的运行时数据,好比哈希码、GC分代年龄等;二、类型指针:经过这个指针肯定这个对象属于哪一个类。
实例数据(Instance Data):存储代码中定义的各类类型的字段内容。
对齐填充(Padding):这部分信息没有任何意义,仅仅是为了使得对象占的内存大小为8字节的整数倍。
三、对象的访问定位
建立对象是为了使用对象,java程序须要经过栈上的reference数据来操做栈上的具体对象。目前主流的访问对象方式有使用句柄和直接指针两种。一、使用句柄方式:会在java堆中建立一个句柄池,reference指向的这块句柄池,句柄池中包括两个指针,其中一个指针指向对象实例数据,另一个指针指向对象的类型数据。二、使用指针的方式:reference存储的直接就是对象的地址。
两种方式各有各的特色,若是使用句柄方式的话,最大的好处是reference存放的是稳定的句柄地址,在对象移动时只会改变句柄中的实例数据指针,而reference自己不须要修改。使用指针的方式优点则是速度快,而且省去了一次指针定位的开销。
更详细的能够参考:
https://www.cnblogs.com/lewis0077/p/5143268.html
https://www.cnblogs.com/lingepeiyong/archive/2012/10/30/2745973.html