JVM之JVM内存区域与内存分配(转载)

先来看看JVM运行时候的内存区域html

 大多数 JVM 将内存区域划分为 Method Area(Non-Heap)(方法区),Heap(堆),Program Counter Register(程序计数器), VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的),Native Method Stack (本地方法栈),其中Method Area和Heap是线程共享的,VM Stack,Native Method Stack 和Program Counter Register是非线程共享的。为何分为线程共享和非线程共享的呢?请继续往下看。
 首先咱们熟悉一下一个通常性的 Java 程序的工做过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每一个java程序都须要运行在本身的JVM上,而后告知 JVM 程序的运行入口,再被 JVM 经过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?
 归纳地说来,JVM初始运行的时候都会分配好Method Area(方法区)和Heap(堆),而JVM 每遇到一个线程,就为其分配一个Program Counter Register(程序计数器), VM Stack(虚拟机栈)和Native Method Stack (本地方法栈),当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为何我把内存区域分为线程共享和非线程共享的缘由,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,因此这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来讲知发生在Heap上)的缘由。
1.  程序计数器
  程序计数器是一块较小的内存区域,做用能够看作是当前线程执行的字节码的位置指示器。分支、循环、跳转、异常处理和线程恢复等基础功能都须要依赖这个计算器来完成,很少说。
2.VM Strack
  先来了解下JAVA指令的构成:
  JAVA指令由 操做码 (方法自己)和 操做数 (方法内部变量) 组成。   
    1)方法自己是指令的操做码部分,保存在Stack中;
    2)方法内部变量(局部变量)做为指令的操做数部分,跟在指令的操做码以后,保存在Stack中(其实是简单类型(int,byte,short 等)保存在Stack中,对象类型在Stack中保存地址,在Heap 中保存值);
  虚拟机栈也叫栈内存,是在线程建立时建立,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来讲不存在垃圾回收问题,只要线程一结束,该栈就 Over,因此不存在垃圾回收。也有一些资料翻译成JAVA方法栈,大概是由于它所描述的是java方法执行的内存模型,每一个方法执行的同时建立帧栈(Strack Frame)用于存储局部变量表(包含了对应的方法参数和局部变量),操做栈(Operand Stack,记录出栈、入栈的操做),动态连接、方法出口等信息,每一个方法被调用直到执行完毕的过程,对应这帧栈在虚拟机栈的入栈和出栈的过程。
  局部变量表存放了编译期可知的各类基本数据类型(boolean、byte、char、short、int、float、long、double)、对象的引用(reference类型,不等同于对象自己,根据不一样的虚拟机实现,多是一个指向对象起始地址的引用指针,也多是一个表明对象的句柄或者其余与对象相关的位置)和 returnAdress类型(指向下一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配,在方法在运行以前,该局部变量表所须要的内存空间是固定的,运行期间也不会改变。
  栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法 A 被调用时就产生了一个栈帧 F1,并被压入到栈中,A 方法又调用了 B 方法,因而产生栈帧 F2 也被压入栈,执行完毕后,先弹出 F2栈帧,再弹出 F1 栈帧,遵循“先进后出”原则。光说比较枯燥,咱们看一个图来理解一下 Java栈,以下图所示java

3.Heap
  Heap(堆)是JVM的内存数据区。Heap 的管理很复杂,是被全部线程共享的内存区域,在JVM启动时候建立,专门用来保存对象的实例。在Heap 中分配必定的内存来保存对象实例,实际上也只是保存对象实例的属性值,属性的类型和对象自己的类型标记等,并不保存对象的方法(以帧栈的形式保存在Stack中),在Heap 中分配必定的内存保存对象实例。而对象实例在Heap 中分配好之后,须要在Stack中保存一个4字节的Heap 内存地址,用来定位该对象实例在Heap 中的位置,便于找到该对象实例,是垃圾回收的主要场所。java堆处于物理不连续的内存空间中,只要逻辑上连续便可。
4.Method Area
  Object Class Data(加载类的类定义数据) 是存储在方法区的。除此以外,常量、静态变量、JIT(即时编译器)编译后的代码也都在方法区。正由于方法区所存储的数据与堆有一种类比关系,因此它还被称为 Non-Heap。方法区也能够是内存不连续的区域组成的,而且可设置为固定大小,也能够设置为可扩展的,这点与堆同样。
  垃圾回收在这个区域会比较少出现,这个区域内存回收的目的主要针对常量池的回收和类的卸载。
5.运行时常量池(Runtime Constant Pool)
  方法区内部有一个很是重要的区域,叫作运行时常量池(Runtime Constant Pool,简称 RCP)。在字节码文件(Class文件)中,除了有类的版本、字段、方法、接口等先关信息描述外,还有常量池(Constant Pool Table)信息,用于存储编译器产生的字面量和符号引用。这部份内容在类被加载后,都会存储到方法区中的RCP。值得注意的是,运行时产生的新常量也能够被放入常量池中,好比 String 类中的 intern() 方法产生的常量。
  常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其余类型、方法、字段的符号引用.例如:
◆类和接口的全限定名;
◆字段的名称和描述符;
◆方法和名称和描述符。
  池中的数据和数组同样经过索引访问。因为常量池包含了一个类型全部的对其余类型、方法、字段的符号引用,因此常量池在Java的动态连接中起了核心做用.
  颇有用且重要关于常量池的扩展:Java常量池详解 Integer与new Integer()的区别
6.Native Method Stack
  与VM Strack类似,VM Strack为JVM提供执行JAVA方法的服务,Native Method Stack则为JVM提供使用native 方法的服务。
7.直接内存区
  直接内存区并非 JVM 管理的内存区域的一部分,而是其以外的。该区域也会在 Java 开发中使用到,而且存在致使内存溢出的隐患。若是你对 NIO 有所了解,可能会知道 NIO 是可使用 Native Methods 来使用直接内存区的。
小结:
  在此,你对JVM的内存区域有了必定的理解,JVM内存区域能够分为线程共享和非线程共享两部分,线程共享的有堆和方法区,非线程共享的有虚拟机栈,本地方法栈和程序计数器。
8.JVM运行原理 例子
以上都是纯理论,咱们举个例子来讲明 JVM 的运行原理,咱们来写一个简单的类,代码以下:bootstrap

public class JVMShowcase {  
 //静态类常量,  
 public final static String ClASS_CONST = "I'm a Const";  
 //私有实例变量  
 private int instanceVar=15;  
 public static void main(String[] args) {  
  //调用静态方法  
  runStaticMethod();  
  //调用非静态方法  
  JVMShowcase showcase=new JVMShowcase();  
  showcase.runNonStaticMethod(100);  
 }  
 //常规静态方法  
 public static String runStaticMethod(){  
  return ClASS_CONST;  
 }  
 //非静态方法  
 public int runNonStaticMethod(int parameter){  
  int methodVar=this.instanceVar * parameter;  
  return methodVar;  
 }  
}

 这个类没有任何意义,不用猜想这个类是作什么用,只是写一个比较典型的类,而后咱们来看
看 JVM 是如何运行的,也就是输入 java JVMShow 后,咱们来看 JVM 是如何处理的:
     第 1 步 、向操做系统申请空闲内存。JVM 对操做系统说“给我 64M(随便模拟数据,并非真实数据) 空闲内存”,因而,JVM 向操做系统申请空闲内存做系统就查找本身的内存分配表,找了段 64M 的内存写上“Java 占用”标签,而后把内存段的起始地址和终止地址给 JVM,JVM 准备加载类文件。
     第 2 步,分配内存内存。JVM 分配内存。JVM 得到到 64M 内存,就开始得瑟了,首先给 heap 分个内存,而后给栈内存也分配好。
     第 3 步,文件检查和分析class 文件。若发现有错误即返回错误。
     第 4 步,加载类。加载类。因为没有指定加载器,JVM 默认使用 bootstrap 加载器,就把 rt.jar 下的全部类都加载到了堆类存的Method Area,JVMShow 也被加载到内存中。咱们来看看Method Area区域,以下图:(这时候包含了 main 方法和 runStaticMethod方法的符号引用,由于它们都是静态方法,在类加载的时候就会加载)数组

第 5 步、执行方法。执行 main 方法。执行启动一个线程,开始执行 main 方法,在 main 执行完毕前,方法区以下图所示:
(public final static String ClASS_CONST = "I'm a Const";  )jvm

在 Method Area 加入了 CLASS_CONST 常量,它是在第一次被访问时产生的(runStaticMethod方法内部)。
     堆内存中有两个对象 object 和 showcase 对象,以下图所示:(执行了JVMShowcase showcase=new JVMShowcase();  )this

为何会有 Object 对象呢?是由于它是 JVMShowcase 的父类,JVM 是先初始化父类,而后再初始化子类,甭管有多少个父类都初始化。
在栈内存中有三个栈帧,以下图所示:操作系统

于此同时,还建立了一个程序计数器指向下一条要执行的语句。
第 6 步,释放内存。释放内存。运行结束,JVM 向操做系统发送消息,说“内存用完了,我还给你”,运行结束。 .net

 此外还有这个适合看看 Java 内存分配全面浅析线程

深刻Java核心 Java内存分配原理精讲   翻译

详细介绍Java的内存管理与内存泄露

 java内存分配研究

 Java虚拟机(JVM)中的内存设置详解