JVM内存模型

 JVM内存模型总体架构图

      

1)程序计数器
多线程时,当线程数超过CPU数量或CPU内核数量,线程之间就要根据时间片轮询抢夺CPU时间资源。因此每个线程有要有一个独立的程序计数器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成,记录下一条要运行的指令。线程私有的内存区域。如果执行的是JAVA方法,计数器记录正在执行的java字节码地址,如果执行的是native方法,则计数器为空。

此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError情况的区域。


2)虚拟机栈
线程私有的,它的生命周期与线程相同,与线程在同一时间创建和销毁。虚拟机栈描述的是JAVA方法执行的内存模型。每个方法执行时都会创建一个桢栈来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。对于执行引擎来说,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法,-Xss参数可以设置虚拟机栈大小)。栈的大小可以是固定的,或者是动态扩展的。如果请求的栈深度大于最大可用深度,则抛出stackOverflowError;如果栈是可动态扩展的,但没有内存空间支持扩展,则抛出OutofMemoryError。下图为栈帧结构图:

 

3)本地方法区
本地方法栈(Native MethodStacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法(本地方法)服务。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

4)JAVA堆

堆是Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例和数组,几乎所有的对象实例都在这里分配内存。堆是垃圾收集器管理的主要区域。

堆可以分为新生代和老年代(tenured)。

新生代(young)程序新创建的对象都是从新生代分配内存,新生代由Eden Space和两块相同大小的Survivor Space(通常又称S0和S1或From和To)构成,可通过-Xmn参数来指定新生代的大小,也可以通过-XX:SurvivorRation来调整Eden Space及SurvivorSpace的大小。

老年代(tenured):用于存放经过多次新生代GC仍然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:

1、大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就直接在老年代分配。

2、大的数组对象,且数组中无引用外部对象。老年代所占的内存大小为-Xmx对应的值减去-Xmn对应的值。

 

堆的大小可以通过-Xms(最小值)和-Xmx(最大值)参数设置,-Xms为JVM启动时申请的最小内存,默认为操作系统物理内存的1/64但小于1G,-Xmx为JVM可申请的最大内存,默认为物理内存的1/4但小于1G,默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小heap的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,对于运行系统,为避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。

如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。

 

5)方法区

 

线程共享的,用于存放被虚拟机加载的类的元数据信息:如常量、静态变量、即时编译器编译后的代码。也成为永久代。如果hotspot虚拟机确定一个类的定义信息不会被使用,也会将其回收。回收的基本条件至少有:所有该类的实例被回收,而且装载该类的ClassLoader被回收

 

方法区在一个jvm实例的内部,类型信息被存储在一个称为方法区的内存逻辑区中。类型信息是由类加载器在类加载时从类文件中提取出来的。类(静态)变量也存储在方法区中。

简单说方法区用来存储类型的元数据信息,一个.class文件是类被java虚拟机使用之前的表现形式,一旦这个类要被使用,java虚拟机就会对其进行装载、连接(验证、准备、解析)和初始化。而装载(后的结果就是由.class文件转变为方法区中的一段特定的数据结构。这个数据结构会存储如下信息:

 

类型信息

      这个类型的全限定名

      这个类型的直接超类的全限定名

      这个类型是类类型还是接口类型

      这个类型的访问修饰符

      任何直接超接口的全限定名的有序列表

 

字段信息

      字段名

      字段类型

      字段的修饰符

 

方法信息

      方法名

      方法返回类型

      方法参数的数量和类型(按照顺序)

      方法的修饰符

 

其他信息

      除了常量以外的所有类(静态)变量

      一个指向ClassLoader的指针

      一个指向Class对象的指针

      常量池(常量数据以及对其他类型的符号引用)

 

JVM为每个已加载的类型都维护一个常量池。常量池就是这个类型用到的常量的一个有序集合,包括实际的常量(string,integer,和floating point常量)和对类型,域和方法的符号引用。池中的数据项象数组项一样,是通过索引访问的

 

每个类的这些元数据,无论是在构建这个类的实例还是调用这个类某个对象的方法,都会访问方法区的这些元数据。

构建一个对象时,JVM会在堆中给对象分配空间,这些空间用来存储当前对象实例属性以及其父类的实例属性(而这些属性信息都是从方法区获得),注意,这里并不是仅仅为当前对象的实例属性分配空间,还需要给父类的实例属性分配,到此其实我们就可以回答第一个问题了,即实例化父类的某个子类时,JVM也会同时构建父类的一个对象。从另外一个角度也可以印证这个问题:调用当前类的构造方法时,首先会调用其父类的构造方法直到Object,而构造方法的调用意味着实例的创建,所以子类实例化时,父类肯定也会被实例化。

类变量被类的所有实例共享,即使没有类实例时你也可以访问它。这些变量只与类相关,所以在方法区中,它们成为类数据在逻辑上的一部分。在JVM使用一个类之前,它必须在方法区中为每个non-final类变量分配空间。

 

方法区主要有以下几个特点:

1、方法区是线程安全的。由于所有的线程都共享方法区,所以,方法区里的数据访问必须被设计成线程安全的。例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个类还没有被装入JVM,那么只允许一个线程去装载它,而其它线程必须等待

2、方法区的大小不必是固定的,JVM可根据应用需要动态调整。同时,方法区也不一定是连续的,方法区可以在一个堆(甚至是JVM自己的堆)中自由分配。

3、方法区也可被垃圾收集,当某个类不在被使用(不可触及)时,JVM将卸载这个类,进行垃圾收集

 

可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。

对于习惯在HotSpot 虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久代”(PermanentGeneration),本质上两者并不等价,仅仅是因为HotSpot 虚拟机的设计团队选择把GC 分代收集扩展至方法区,或者说使用永久代来实现方法区而已。对于其他虚拟机(如BEA JRockit、IBM J9 等)来说是不存在永久代的概念的。

相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。

当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

 


JVM参数:

 

-XX:+PrintGCDetails  打印垃圾回收信息

-Xms 为Heap区域的初始值,线上环境需要与-Xmx设置为一致,否则capacity的值会来回飘动
-Xmx 为Heap区域的最大值
-Xss(或-ss) 线程栈大小(指一个线程的native空间)1.5以后是1M的默认大小
-XX:PermSize与-XX:MaxPermSize  方法区(永久代)的初始大小和最大值(但不是本地方法区)
-XX:NewRatio  老年代与新生代比率
-XX:SurvivorRatio  Eden与Survivor的占用比例。例如8表示,一个survivor区占用 1/8 的Eden内存,即1/10的新生代内存,为什么不是1/9?因为我们的新生代有2个survivor,即S1和S22。所以survivor总共是占用新生代内存的 2/10,Eden与新生代的占比则为 8/10。
-XX:MaxHeapFreeRatio  GC后,如果发现空闲堆内存占到整个预估的比例小于这个值,则减小堆空间。
-XX:MinHeapFreeRatio  GC后,如果发现空闲堆内存占到整个预估的比例大于这个值,则增大堆空间。
-XX:NewSize    新生代大小

参考文章:

http://www.cubrid.org/blog/dev-platform/understanding-java-garbage-collection/

http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

 http://blog.csdn.net/kingofworld/article/details/17718587

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值