Java内存区域

运行时数据区域

   JVM再执行Java程序的过程当中会把它锁管理的内存划分为若干个不一样的数据区域。
   这些区域都有本身的用途,以及建立和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则依赖用户线程的启动和结束而建立和销毁。
这里写图片描述java

程序计数器:

1·它是一块较小的内存空间,它能够看做是当前线程所执行的字节码的行号指示器。在JVM中字节码解释器的工做就是改变这个计数器的值来选取下一条须要执行的字节码指令,分支、循环、跳转、异常处理、线程回复等基础功能都须要依赖这个计数器来完成。
2·在JVM的多线程机制中,为了线程切换后能恢复到正确的执行位置,每一条线程都须要有一个独立的程序计数器,每条线程的程序计数器互不影响,独立存储,是线程私有的内存。
3·若是线程正在执行一个Java方法,那么这个计数器记录的就是正在执行的虚拟机字节码指令地址,若是正在执行的是Native方法,这个计数器则为空。
4·这个内存区域是惟一一个在虚拟机规范中没有规定任何OutOfMemoryError的区域。程序员

Java虚拟机栈:

1·与程序计数器同样,虚拟机栈也是线程私有的,它的生命周期与线程相同。
2·虚拟机栈描述的是Java方法执行的内存模型,每一个方法执行的时候都会建立一个帧栈(Stack Frame)用于存放局部变量表、操做数栈、动态连接、方法出口等信息。(每个方法从调用直至执行完成的过程,就对应着一个帧栈在虚拟机栈中入栈到出栈的过程)。
3·大多数人把Java内存分为堆和栈,可是其实他们口中的栈指的是局部变量表。
4·局部变量表存放了在编译期可知的各类基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和returnAddress类型。
5·若是线程请求的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError异常,若是虚拟机栈能够动态扩展,若是扩展时没法申请到足够的内存,就会抛出OutOfMemoryError异常。算法

本地方法栈:

1·它与虚拟机栈的区别:虚拟机栈为虚拟机执行Java方法服务,而本地方法则为虚拟机使用到的Natice方法服务。
2·和虚拟机栈同样,本地方法栈也会抛出StackOverfloError和OutOfMemoryError异常。数组

Java堆:

1·Java堆是被全部线程共享的一块区域,在虚拟机启动时建立
2·该内存区域惟一的做用就是存放对象的实例,几乎全部的对象实例都在这里分配内存,在JVM的规范中是这么描述的:全部对象的实例以及数组都要在堆上分配。可是,随着JIT编译器、逃逸分析、栈上分配、标量替换优化技术的发展,上述的JVM规范也就没有那么绝对了。安全

拓展
JIT编译器:即时编译器,一句一句边运行边翻译执行
逃逸分析:逃逸:就是当咱们建立出来一个对象时,该对象除了在本线程上使用,还要再别的线程上使用,那咱们就不考虑使用栈上分配了
栈上分配:对于那些线程私有的对象,将他们打散分配再栈上,而不是在堆上。优势:函数在调用结束以后能够本身销毁,不须要GC的介入,提升性能。
标量替换:若是把一个Java对象拆散,根据程序访问的状况,将其使用到的成员变量恢复原始类型来访问就叫作标量替换,那么程序真正在执行的时候,可能不建立这个对象,直接建立它的若干个被这个方法使用到的成员变量。多线程

1·Java堆是垃圾收集器管理的主要区域
2·Java堆不须要连续的内存、能够选择固定大小或者可扩展大小
这里写图片描述并发

3·全部新生成的对象都是放在年轻代。年轻代的目的就是尽量快速的收集掉那些生命周期短的对象。
4·堆又能够分为新生代和老年代:新生代用于存放刚建立的对象已经年轻的对象,若是对象一直没有被回收,生存的足够长久就会被移入老年代。
5·新生代又进一步能够细分为eden,survivorSpace0(s0, from space)、survivorSpace1(s1, to space)。函数

方法区:

1·方法区是被全部线程都共享的一个区域
2·它用于存放虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等
3·它和堆同样不须要连续的内存、能够选择固定大小或者可扩展大小布局

运行时常量池:

1·运行时常量池是方法区的一部分
2·Class文件中除了有类的版本、字段、方法、接口等描述信息以外,还有一项信息是常量池,用于存放编译期生成的各类字面量和符号引用–>Class的常量池
3·运行时常量池相比于Class文件常量池的另一个重要特性是具有动态性,Java语言不要求常量必定只有在编译的时候才能产生,也就是说并不是预置入Class文件中的常量池的内容才能才能进入方法区运行时常量池,运行期间也能够将新的常量放入池中性能

直接内存:

·并不是JVM运行时数据区的一部分,可是这部份内存频繁被使用


虚拟机对象:

对象的建立:

对象的建立过程图解:

这里写图片描述
    在上图中的在堆区分配内存的时候通常会有两种方案:
      1. 指针碰撞:首先Java堆中的内存是绝对规整的,全部用过的内存放在一边,空闲的内存放在另一边,中间放着一个指针做为分界点的指示器,那么锁分配的内存就是把哪一个指针向空闲空间那边挪一段与对象大小等 大的距离。
      2. 空闲列表:Java堆不是规整的,已使用的内存和空闲的内存相互交错,虚拟机维护着一个列表,记录哪些内存块是可用的,在分配的时候就从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。
      选择哪一种分配方式是由Java堆是否规整决定的,而Java堆是否规整又是由所采用的垃圾收集器是否带有压缩整理功能决定的。因此,在使用 Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞,而 使用CMS这种基于Mark-Sweep算法的收集器时,一般采用空闲列表。

因为对象的建立在虚拟机中式很是频繁的行为,因此在并发的状况下也并非线程安全的,为了解决这个问题,由两种方案:
1. 对分配内存空间的动做进行同步处理—实际上虚拟机采用CAS配上失败重试的方式保证更新操做的原子性
2. 把内存分配的动做按照线程划分规划在不一样的空间之中进行

内存分配完成以后,虚拟机将分配到的内存空间都初始化为零值,这就是咱们为何在一个类中定义全局变量的时候能够不给它赋初值就直接使用。
在内存分配完成且内存初始化完成后,从虚拟机的角度看,一个新的对象已经产生,可是,从Java的角度看,对象的建立才刚刚开始,在执行完new指令以后,通常会接着执行init方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算建立完成。

对象的内存布局:

1·对象在内存中的布局能够分为3个区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)
2·对象头包括两部分信息,第一部分用于存储对象自身运行时数据,好比:哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
3·对象头的另一部分信息是类型指针,对象指向它元数据类型的指针,虚拟机经过这个来肯定这个对象是哪一个类的实例。

对象访问定位:

1·经过栈上的本地变量表中的reference数据来操做堆上的具体对象。
2·reference数据类型只是规定了一个指向对象的引用,并无定义这个引用应该经过什么方式去定位、访问堆中的对象具体位置,对象访问方式噎死取决于虚拟机实现而定的。
3·目前主流的两种访问方式有:1.句柄访问 2.指针访问

拓展
两种访问方式的区别:
      1.句柄访问时直接在堆内存中维护着两个指针,一个执行指向对象实例数据,另一个指向对象类型数据
      2.而指针访问呢?在堆内存的对象实例数据中维护着一个指针,指向对象类型数据
两种定位方式的优势和缺点:       使用句柄最大的好处就是reference中存储的时稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而不须要改变reference        使用直接指针的优势时访问的速度更快,它节省了一次指针定位的时间开销。