JVM内存模型

一、JVM运行时内存模型

  • JVM1.7版本运行时内存模型图

jvm1.7

  • JVM1.8版本运行时内存模型图

jvm1.8

1.1 程序计数器

  程序计数器(Program Counter Register)是一块较小的内存空间,是当前线程所执行的字节码的行号指示器。每个线程一块,指向当前线程正在执行字节代码的地址,如果是native方法,则为空。因为每个线程一块,因此是“线程独享”的内存,也是唯一一个没有规定任何OutOfMemoryError情况的区域。

1.2 虚拟机栈

  与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
  在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
调优参数:

  • -Xss1M 设置栈的大小,栈的大小直接决定函数调用的可达深度。
  • -XX:ThreadStackSize=512 设置线程栈大小,若为0则使用系统默认值

JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,大约在3000~5000左右

1.3 本地方法栈

  功能与Java虚拟机栈十分相同。区别在于,本地方法栈为虚拟机使用到的native方法服务。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

1.4 堆

  Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。1.7后,字符串常量池从永久代(方法区运行时常量池)中剥离出来,存放在堆中。Java堆是垃圾收集器管理的主要区域,从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。
堆内存空间默认分配如下:

  • 老年代: 2 3 \tfrac{2}{3} 32堆空间
  • 新生代: 1 3 \tfrac{1}{3} 31堆空间
    • Eden空间: 8 10 \tfrac{8}{10} 108新生代空间
    • From Survivor空间: 1 10 \tfrac{1}{10} 101新生代空间
    • To Survivor空间: 1 10 \tfrac{1}{10} 101新生代空间

  一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。
  在新生代中80%以上的对象都是朝生夕死的,因此该区域采用复制算法进行垃圾回收,复制的基本算法思想就是将内存分成两块,每一次只会使用一块,当这一块用完了,则将这一块内存全部复制到另一块,这样就不会产生内存碎片。在GC开始时,对象只会存在Eden及Form Survivor区,To Survivor区是空的;进行GC时,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向,当年龄达到一定值时(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象则移动到老年代,没有达到阈值的,则会复制到“To”区。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
调优参数:

  • -Xms128M 设置java应用程序启动时的初始堆大小
  • -Xmx1024M 设置java应用程序能获得的最大堆大小
  • -XX:NewSize=256M 设置新生代的大小
  • -XX:MaxNewSize=256M 设置新生代最大值
  • -XX:NewRatio=2 设置老年代与新生代的比例,即老年代除以新生代大小。 2表示新生代:老年代 1:2 即新生代占1/3
  • -XX:SurviorRatio=8 新生代中eden区与survivior 区的比例。 8表示survivior:eden 1:8 即两个Survivor为2,每个survivior占1/10,Eden占8/10
  • -XX:TargetSurvivorRatio=80 设置survivior 的使用率。当达到这个空间使用率时,会将对象送入老年代。
  • -XX:MaxTenuringThreshold=18 在新生代中对象存活次数(经过Minor GC的次数)后仍然存活,就会晋升到旧生代
  • -XX:MinHeapFreeRatio=40 设置堆空间的最小空间比例。当堆空间的空闲内存小于这个数值时,jvm便会扩展堆空间
  • -XX:MaxHeapFreeRatio=80 设置堆空间的最大空间比例。当堆空间的空闲内存大于这个数值时,jvm便会缩小堆空间

1.5 方法区

  方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。对于习惯在HotSpot虚拟机上开发、部署程序的开发者来说,很多人都更愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作。原则上,如何实现方法区属于虚拟机实现细节,不受虚拟机规范约束,但使用永久代来实现方法区,现在看来并不是一个好主意,因为这样更容易遇到内存溢出问题(永久代有-XX:MaxPermSize的上限)。JDK1.7将永久代移出方法区,放入堆(Heap)中。根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
调优参数:

  • -XX:PermSize=64m JVM初始分配的永久代内存, 不会被回收, 生产环境建议与maxPermSize相同, 设为256m以上
  • -XX:MaxPermSize=128M JVM最大允许分配的永久代内存, 生产环境建议设置为256m以上

1.6 运行时常量池

  运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

1.7 元数据区

  元数据区(Metaspace),1.8版本以后,元数据区代替了永久代,本质上和永久代一样,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。
调优参数:

  • -XX:MetaspaceSize=64m 初始化元数据区大小,该值越大,触发Metaspace GC的时机越晚,随着GC的到来,虚拟机会根据实际情况调控Metaspace的大小,可能增加也可能降低。
  • -XX:MaxMetaspaceSize=128M 这个参数用于限制Metaspace的上限,防止其无限使用本地内存,影响到其他程序。在本机上默认值大约为4096M
  • -XX:MinMetaspaceFreeRatio=40 当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机将增长Metaspace的大小。设置该参数可以控制Metaspace的增长速度,太小的值会导致Metaspace增长缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。
  • -XX:MaxMetaspaceFreeRatio=70 当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间占比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间
  • -XX:MaxMetaspaceExpansion=5M Metaspace增长时的最大幅度
  • -XX:MinMetaspaceExpansion=330K Metaspace增长时的最小幅度

1.8 直接内存

  直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。在JDK 1.4中引入的NIO中,引入了一种基于Channel和Buffer的I/O方式,他可以使用Native函数直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的应用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。

二、调优参数

参数名称含义默认值备注
-Xss单个线程堆栈大小默认为1M一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。相同内存的情况下,该值越小,可创建更多线程,.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成
-XX:ThreadStackSize线程栈大小默认1M如果设置为0,默认使用使用系统默认值
-Xms初始堆大小物理内存的1/64,最小为1M默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制
-Xmx最大堆大小物理内存的1/4默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
-Xmn新生代大小默认堆内存的1/3或1/4此处的大小是(eden+ 2 survivor space),增大新生代大小,将减少老年代大小
-XX:NewSize新生代的大小默认堆内存的1/3或1/4
-XX:MaxNewSize新生代最大值默认堆内存的1/3或1/4
-XX:NewRatio老年代与新生代的比例默认为2即老年代除以新生代大小。 2表示新生代:老年代 1:2 即新生代占1/3
-XX:SurviorRatio新生代中eden区与survivior区的比例8表示survivior:eden 1:8 即两个Survivor为2,每个survivior占1/10,Eden占8/10
-XX:TargetSurvivorRatiosurvivior的使用率50当达到这个空间使用率时,会将对象送入老年代
-XX:MaxTenuringThreshold年龄阈值15在新生代中对象存活次数(经过Minor GC的次数)后仍然存活,就会晋升到旧生代
-XX:MinHeapFreeRatio堆空间的最小空闲空间比例40当堆空间的空闲内存比例小于这个数值时,jvm便会扩展堆空间
-XX:MaxHeapFreeRatio堆空间的最大空闲空间比例当堆空间的空闲内存比例大于这个数值时,jvm便会缩小堆空间
-XX:PermSizeJVM初始分配的永久代大小不会被回收, 生产环境建议与maxPermSize相同, 设为256m以上,jdk1.8以后设置无效
-XX:MaxPermSizeJVM最大允许分配的永久代内存生产环境建议设置为256m以上,jdk1.8以后设置无效
-XX:MetaspaceSize初始化元数据区大小20M该值越大,触发Metaspace GC的时机越晚,随着GC的到来,虚拟机会根据实际情况调控Metaspace的大小,可能增加也可能降低。
-XX:MaxMetaspaceSize限制Metaspace的上限默认不超过系统内存防止其无限使用本地内存,影响到其他程序。在本机上默认值大约为4096M
-XX:MinMetaspaceFreeRatio元数据区最小空闲比例40当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机将增长Metaspace的大小。设置该参数可以控制Metaspace的增长速度,太小的值会导致Metaspace增长缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。
-XX:MaxMetaspaceFreeRatio元数据区最大空闲比例70当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间占比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间
-XX:MaxMetaspaceExpansionMetaspace增长时的最大幅度约5M
-XX:MinMetaspaceExpansionMetaspace增长时的最小幅度约330K
-XX:CompressedClassSpaceSizeKlass Metaspace区间大小约1G

三、 Metaspace介绍

  Metaspace是Jdk1.8特有的用来替换Perm(永久代)的。在Jdk1.8以前有Perm来存储Klass(class文件在jvm里的运行时数据结构)信息,可以通过-XX:PermSize和-XX:MaxPermSize来控制该区域的大小,但是设置多大对于开发者来说都比较困难,设大了浪费空间,设小了,容易OutOfMemoryError,但是Metaspace不受JVM内存管理,而且通过java -XX:+PrintFlagsInitial查到看MaxMetaspaceSize默认设置的特别大,并且CompressedClassSpaceSize也有1G大小,这样就不容易出现OutOfMemoryError异常。
  Metaspace主要由两大部分组成:

  • Klass Metaspace
  • NoKlass Metaspace

  Klass Metaspace主要用来存储klass,但是我们看到的Hello.class这种其实是存在Heap中的,它是java.lang.Class的一个实例对象,这块内存是紧靠Heap的,可通过-XX:CompressedClassSpaceSize设置大小,如果没有开启压缩指针或者Heap堆内存设置大于32G的话,klass都会存在NoKlass Metaspace里。
  NoKlass Metaspace主要用来存储Method,ConstantPool(常量池)等,这块内存是必须的,这块内存是由多块内存组合起来的,所以可以认为是不连续的内存块组成的。
  Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以类加载器们要分配内存,但是每个类加载器都有一个SpaceManager,来管理属于这个类加载的内存小块。如果Klass Metaspace用完了,那就会OOM了,不过一般情况下不会,NoKlass Mestaspace是由一块块内存慢慢组合起来的,在没有达到限制条件的情况下,会不断加长这条链,让它可以持续工作。
  Metaspace除了章节二中提到的优化参数外,还有UseLargePagesInMetaspace,InitialBootClassLoaderMetaspaceSize。UseLargePagesInMetaspace默认为false,意思是否允许在Metaspace使用大页面,默认情况下使用4Kb的分页大小即可,正常情况下不需要开启。InitialBootClassLoaderMetaspaceSize在64位系统下默认为4M,32位系统默认为2200K,这个参数确定了NoKlass Metaspace区域第一个内存块的大小(2*InitialBootClassLoaderMetaspaceSize),同时为bootstrapClassLoader的第一块内存chunk分配了InitialBootClassLoaderMetaspaceSize的大小

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值