JVM内存浅述
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机内存包括程序计数器、虚拟机栈、本地方法栈、堆、方法区等几个运行时数据区域。
PC Register(PC寄存器/程序计数器)
PC寄存器是一块很小的内存区域,主要作用是记录当前线程所执行的字节码的行号。任何分支,循环,方法调用,判断,异常处理,线程等待以及恢复线程,递归等等都是通过这个计数器来完成的。
由于Java多线程是通过交替线程轮流切换并分配处理器时间的方式来实现的,在任何一个确定的时间里,在处理器的一个内核只会执行一条线程中的指令。每条线程都会有一个独立的程序计数器来记录当前指令的行号。计数器之间相互独立互不影响,我们称这块内存为“线程私有”的内存。
如果正在执行的是Native方法,PC寄存器中不存储任何信息,计数器值则为空(Undefined),此内存是唯一一个没有任何OutOfMemoryError的情况。
JVM Stack(虚拟机栈)
线程私有,生命周期与线程相同。为执行Java方法(字节码)服务;每个方法执行的时候会创建一个栈帧,用于存储局部基本类型变量、操作数栈、动态链接、方法出口,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。因此Java中基本类型的变量是值传递,而非基本类型的变量是引用传递。每个方法被调用到执行完成的过程,就对应这一个栈帧在虚拟机栈中入栈到出栈的过程。
JVM栈的空间是在物理内存上分配的,而不是从堆上分配。如果线程请求的栈深度大于虚拟机所允许,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。当线程运行完毕后,这些内存也就被自动回收。可以通过-Xss来指定栈的大小。
Native Method Stacks(本地方法栈)
作用与虚拟机栈类似,但为Native方法服务。
Heap(堆)
所有线程共享,用于存放对象实例,几乎所有的对象都会在这里分配内存,是GC管理的主要区域。可以处于物理上不连续的内存空间中,但是逻辑上要求连续。当堆中没有内存完成实例分配,并且堆也无法扩展时,将抛出OutOfMemoryError异常。其大小通过-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的值设成一样。
堆被划分为Old Generation(老年代)和Young Generation(新生代)。新生代主要存储新创建的对象和尚未进入老年代的对象。老年代存储经过多次新生代GC(Minor GC)任然存活的对象。
这里插播一下Java GC机制
一般采用引用计数法(简单高效,但无法处理循环引用)和可达性分析法(通过一系列的称为“GC Roots”的对象作为起始点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。不可达的对象要成为可回收对象至少经历两次标记过程)来判断一个对象是否可被回收。Python采用前者,Java、C#是采用后者。
GC算法常见为:
1.Mark-Sweep,标记-清除法
标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。算法很简单,但容易产生内存碎片。
2.Copying,复制法
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。该算法对内存空间的使用做出了高昂的代价,而且如果存活对象很多,那么Copying算法的效率将会大大降低。
3.Mark-Compact,标记-整理法
该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动聚拢,然后清理掉端边界以外的内存。
4.Generational Collection,分代收集法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。
新生代都采取Copying算法,每次使用Eden空间和其中的一块Survivor(From Space)空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。
由于老年代每次都只回收少量对象,一般使用的是Mark-Compact算法。
新生代
程序新创建的对象都是从新生代分配内存,新生代由Eden Space和两块相同大小的Survivor Space(通常又称S0和S1或From和To)构成,可通过-Xmn参数来指定新生代的大小,Eden Space的大小和两块Survivor Space的大小比例默认为8,这个比例可通过-XX:SurvivorRatio来指定。 针对新生代的垃圾回收即Young GC。
老年代
用于存放经过多次新生代GC任然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:1.大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就不在新生代分配,而是直接在老年代分配。2.大的数组对象,且数组中无引用外部对象。 老年代所占的内存大小为-Xmx对应的值减去-Xmn对应的值。 针对年老代的垃圾回收即Full GC。
Method Area(方法区)
在JDK中这块区域对应的为Permanent Generation,又称为持久代、永久代。所有线程共享,存储被VM加载的类信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域。当方法区无法满足内存分配需求的时候,将抛出OutOfMemoryError异常。默认大小为64M。可以通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值。
Runtime Constant Pool(运行时常量池)
是方法区的一部分,其空间从方法区域中分配,存放编译器生成的各种符号引用。
直接内存
直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。JDK1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小。
参数调优说明
-
-Xmx1024m:设置JVM最大堆内存为1024M。-Xmx一般设置为系统内存大小的一半,即充分利用系统资源,又给予系统安全运行的空间。
-
-Xms1024m:设置JVM初始堆内存为1024M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-
-Xss128k:设置每个线程的栈大小。在相同物理内存下,设置较小的线程栈以支持创建更多的线程,支持海量访问,并提升系统性能。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。需要注意的是:当这个值被设置的较大(例如>2MB)时将会在很大程度上降低系统的性能。
-
-Xmn1g:设置年轻代大小为1G。在整个堆内存大小确定的情况下,增大年轻代将会减小年老代,反之亦然。此值关系到JVM垃圾回收,对系统性能影响较大,官方推荐配置为整个堆大小的3/8。
-
-XX:NewSize=1024m:设置年轻代初始值为1024M。
-
-XX:MaxNewSize=1024m:设置年轻代最大值为1024M。
-
-XX:PermSize=256m:设置持久代初始值为256M。
-
-XX:MaxPermSize=256m:设置持久代最大值为256M。
-
-XX:NewRatio=4:设置年轻代(包括1个Eden和2个Survivor区)与年老代的比值。表示年轻代比年老代为1:4。
-
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的比值。表示2个Survivor区(JVM堆内存年轻代中默认有2个大小相等的Survivor区)与1个Eden区的比值为2:4,即1个Survivor区占整个年轻代大小的1/6。
-
-XX:MaxTenuringThreshold=7:表示一个对象如果在Survivor区(救助空间)移动了7次还没有被垃圾回收就进入年老代。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代,对于需要大量常驻内存的应用,这样做可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代存活时间,增加对象在年轻代被垃圾回收的概率,减少Full GC的频率,这样做可以在某种程度上提高服务稳定性。
-
-XX:ParallelGCThreads=4 配置并行收集器的线程数,即同时4个线程一起进行垃圾回收。此值一般配置为与CPU数目相等。
-
-XX:MaxTenuringThreshold=0 设置垃圾最大年龄(在年轻代的存活次数)。如果设置为0的话,则年轻代对象不经过Survivor区直接进入年老代。对于年老代比较多的应用,可以提高效率;如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概率。根据被海量访问的动态Web应用之特点,其内存要么被缓存起来以减少直接访问DB,要么被快速回收以支持高并发海量请求,因此其内存对象在年轻代存活多次意义不大,可以直接进入年老代,根据实际应用效果,在这里设置此值为0。
-
-XX:+UseConcMarkSweepGC 设置年老代为并发收集。CMS(ConcMarkSweepGC)收集的目标是尽量减少应用的暂停时间,减少Full GC发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代内存,适用于应用中存在比较多的长生命周期对象的情况。
-
-XX:NewSize/-XX:MaxNewSize,-Xmn,-XX:NewRatio ,都可以影响年轻代的大小,优先级从高到低。