java内存区域
一些基本概念
http://www.importnew.com/18694.html
https://www.cnblogs.com/wangyayun/p/6557851.html
1.方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈和程序员计数器是运行时线程私有的内存区域。
2.Java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
3.方法区(Method Area),方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
4.程序计数器(Program Counter Register),程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
5.JVM栈(JVM Stacks),与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
6.本地方法栈(Native Method Stacks),本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用
类的生命周期
1.加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
2.连接,连接又包含三块内容:验证、准备、解析。1)验证,文件格式、元数据、字节码、符号引用验证;2)准备,为类的静态变量分配内存,并将其初始化为默认值;3)解析,把类中的符号引用转换为直接引用
3.初始化,为类的静态变量赋予正确的初始值
4.使用,new出对象程序中使用
5.卸载,执行垃圾回收
引用类型
对象引用类型分为强引用、软引用、弱引用和虚引用
强引用:就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收
软引用:软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。
弱引用:弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。
强引用不用说,我们系统一般在使用时都是用的强引用。而“软引用”和“弱引用”比较少见。他们一般被作为缓存使用,而且一般是在内存大小比较受限的情况下做为缓存。因为如果内存足够大的话,可以直接使用强引用作为缓存即可,同时可控性更高。因而,他们常见的是被使用在桌面应用系统的缓存。
JVM参数配置
JVM启动模式
Client模式:启动速度较快,但运行时性能和内存管理效率不高
Server模式:启动比Client模式慢10%,但运行时性能和内存管理效率较高
在JVM启动的时候指定:-Client或者-Server来判断启动模式
分代收集算法 把对象分为新生代、老年代、持久代,对不同生命周期的对象使用不同的算法 http://www.importnew.com/19255.html
试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,因为可能进行了很多次遍历,但是他们依旧存在。因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。
新生代
所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个Survivor区(默认,可改),大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能
老年代
在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
持久代
用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响
垃圾回收算法 http://www.importnew.com/18740.html
引用计数(Reference Counting):
比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。
标记-清除(Mark-Sweep):
此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。 碎片太多可能引发另一次GC
复制(Copying):
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次此法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。
标记-整理(Mark-Compact):
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
串行GC
Serial收集器
1)特点:
–仅仅使用单线程进行内存回收;
–它是独占式的内存回收 ;
–进行内存回收时, 暂停所有的工作线程(“Stop-The-World”) ;
–使用复制算法;
–适合CPU等硬件一般的场合;
–到JDK1.7为止,是JVM Client模式下默认的新生代收集器;
2)设置参数:
-XX:+UseSerialGC 指定使用新生代Serial和老年代SerialOld
SerialOld收集器
1)特点:
–同新生代Serial收集器一样,单线程、独占式的垃圾收集器;
–使用“标记-整理”算法;
–通常老年代内存回收比新生代内存回收要更长时间,所以可能会使应用程序停顿较长时间;
2)设置参数:
-XX:+UseSerialGC 新生代、老年代都使用串行GC;
-XX:+UseParNewGC 新生代使用ParNew,老年代使用SerialOld;
-XX:+UseParallelGC 新生代使用Parallel,老年代使用SerialOld;
并行GC(吞吐量优先)
ParNew收集器
1)特点:
–Serial的多线程版本;
–使用复制算法 ;
–垃圾回收时,应用程序仍会暂停,只不过由于是多线程回收,在多核CPU上,回收效率会高于串行GC。反之在单核CPU,效率会不如串行GC;
2)设置参数:
-XX:+UseParNewGC 新生代使用ParNew,老年代使用SerialOld;
-XX:+UseConcMarkSweepGC 新生代使用ParNew,老年代使用CMS;
-XX:ParallelGCThreads=n 指定ParNew收集器工作时的收集线程数,当CPU核数小于8时,默认开启的线程数等于CPU数量,当高于8时,可使用公式:3+((5*CPU_count)/8) 。
ParNew收集器其实就是Serial收集器的多线程版本,除了多条线程收集之外,其余行为包括Serial收集器可用的设置参数、收集算法、Safepoint、对象分配规则、回收策略等都与Serial收集器完全一样,并没有太多特别之处。但它却是运行在JVM Service模式下首选的新生代收集器,其中一个很重要的原因是:除了Serial收集器外,目前只有它能于CMS收集器(并发GC)配合工作。
ParNew收集器在单CPU环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程切换的开销,ParNew收集器在两个CPU环境中都不能100%保证优于Serial收集器。随着可以使用的CPU数量的增加,它对于GC时系统资源的有效利用还是有利的。
Parallel收集器
1)特点:
–同ParNew回收器一样, 不同的地方在于,它非常关注系统的吞吐量(通过参数控制) ;
–使用复制算法;
–支持自适应的GC调节策略;
2)设置参数:
-XX:+UseParallelGC 新生代使用Parallel,老年代使用SerialOld;
-XX:+UseParallelOldGC 新生代使用Parallel,老年代使用ParallelOld;
-XX:MaxGCPauseMillis=n 设置内存回收的最大停顿时间,单位ms ;
-XX:GCTimeRatio=n 设置吞吐量的大小,假设值为n(在0-100之间),那么系统将花费不超过1/(n+1)的时间用于内存回收。默认值为99,就是允许最大1%的垃圾收集时间;
-XX:+UseAdaptiveSizePolicy 自适应GC策略的开关参数。
吞吐量(Throughput) = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。
Parallel收集器支持“GC自适应的调节策略(GC-Ergonomics)”,这也是Parallel收集器与ParNew收集器的一个重要区别。
设置参数-XX:+UseAdaptiveSizePolicy,这是一个开关参数,当这个参数打开后,不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最适合的停顿时间或者最大的吞吐量,这种调节方式称为“GC自适应的调节策略”。
你也可以在使用Parallel收集器自适应调节策略时,把基本的内存数据设置好(如-Xmx 最大堆),然后使用MaxGCPauseMillis参数(更关注最大停顿时间)或GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标,具体细节参数的调节任务就交由虚拟机去完成。
ParallelOld收集器
1)特点:
–关注吞吐量的老年代并发收集器;
–使用“标记-整理”算法;
2)设置参数:
-XX:+UseParallelOldGC 新生代使用Parallel,老年代使用ParallelOld。
这个收集器是在JDK1.6中才开始提供,在此之前,如果新生代选择了Parallel收集器,老年代除了SerialOld收集器外别无选择。在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel加ParallelOld收集器。
并发GC
CMS收集器
1)特点:
–非独占式的老年代并发收集器,大部分时候应用程序不会停止运行;
–使用“标记-清除”算法,因此回收后会有内存碎片,可设置参数进行内存碎片的压缩整理 ;
–与Parallel和ParallelOld不同,CMS主要关注系统停顿时间;
2) 缺点:
对CPU资源敏感;
无法处理浮动垃圾(Floating Garbage);
内存碎片问题
其中初始标记、重新标记这两个步骤仍需“Stop-The-World”,这两个阶段是独占的,不能与用户线程一起执行,而其它阶段则可以与用户线程一起执行。
初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快;
并发标记阶段就是进行GC Roots Tracing的过程;
重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
由于整个过程中耗时最长的并发标记和并发清除过程,收集线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
浮动垃圾: 并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生
2)设置参数:
-XX:-CMSPrecleaningEnabled 关闭预清理,默认在并发标记后会有一个预清理的操作;
-XX:+UseConcMarkSweepGC 老年代使用CMS,新生代使用ParNew;
-XX:ConcGCThreads=n 设置并发线程数;
-XX:ParallelCMSThreads=n 同上,设置并发线程数;
-XX:CMSInitiatingOccupancyFraction=n 指定老年代回收阀值,默认值为68;
-XX:+UseCMSCompactAtFullCollection 开启内存碎片整理;
-XX:CMSFullGCsBeforeCompation=n 指定进行多少次CMS垃圾回收后再进行一次内存压缩;
-XX:+CMSParallelRemarkEnabled 在使用UseParNewGC参数的情况下,尽量减少 mark(标记)的时间;
-XX:+UseCMSInitiatingOccupancyOnly 表示只有达到阀值时才进行CMS垃圾回收
对象何时进入老年代
长期存活的对象将进入老年代
-XX:MaxTenuringThreshold=n 假设值为n,则新生代的对象最多经历n次GC,就能晋升老年代,但这个必不是晋升的必要条件
-XX:TargetSurvivorRatio=n 用于设置Survivor区的目标使用率,即当Survivor区GC后使用率超过这个值,就可能会使用较小的年龄作为晋升年龄。默认为50
虚拟机采用分代收集的思想管理内存,那内存回收时就必须能识别那些对象该放到新生代,那些该到老年代中。为了做到这点,虚拟机为每个对象定义了一个对象年龄(Age,由GC的次数决定)。每经过一次新生代GC后仍然存活,将对象的Age增加1岁,当年龄到一定程度(默认为15)时,将会被晋升到老年代中。
如果将 -XX:MaxTenuringThreshold 参数设置为0的话,则新生代对象不经过Survivor区,直接进入老年代,对于需要大量常驻内存的应用,这样做可以提高效率;如果将此值设置为一个较大值,则新生代对象会在Survivor区进行多次复制,这样做可以增加对象在新生代的存活时间,增加对象在新生代被垃圾回收的概率,减少Full GC的频率,可以在某种程度上提高服务稳定性。
适龄对象也可能进入老年代
为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄。
大对象直接进入老年代
-XX:PretenureSizeThreshold 即对象的大小大于此值,就会绕过新生代,直接在老年代分配(即所谓“大对象直接进入老年代”)。
除年龄外,对象体积也会影响对象的晋升的。若对象体积太大,新生代无法容纳这个对象,则这个对象可能就会直接晋升至老年代。特别是,如果程序中经常出现“短命的大对象”,容易发生内存还有不少空间却不得不提前触发Full GC以获取足够的连续空间,导致GC效率低下。可通过以下参数使用对象直接晋升至老年代的阈值,单位是byte
PretenureSizeThreshold 参数的意义在于,若遇上述情况时,能避免在 Eden 及两个 Survivor 之间发生大量的内存复制。此参数只对串行GC以及ParNew有效,而Parallel并不认识这个参数。Parallel收集器一般并不需要特别设置。如果遇到必须使用此参数的场合,也可以考虑 ParNew加CMS的收集器组合。
******************************************************************************************************************************************************************************************************************************************************************************************************
常见配置汇总 http://www.importnew.com/19264.html
堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
收集器设置
-XX:+UseSerialGC:设置串行收集器 新生代Serial和老年代SerialOld
-XX:+UseParallelGC:设置并行收集器 新生代使用Parallel,老年代使用SerialOld
-XX:+UseParNewGC:设置并行收集器 新生代使用ParNew,老年代使用SerialOld;
-XX:+UseParalledlOldGC:设置并行年老代收集器 新生代使用Parallel,老年代使用ParallelOld;
-XX:+UseConcMarkSweepGC:设置并发收集器 新生代使用ParNew,老年代使用CMS;
垃圾回收统计信息
-XX:+PrintGC 简单GC日志模式
-XX:+PrintGCDetails 就开启了详细GC日志模式。在这种模式下,日志格式和所使用的GC算法有关
-XX:+PrintGCTimeStamps 可以将时间和日期也加到GC日志中。表示自JVM启动至今的时间戳会被添加到每一行中
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
******************************************************************************************************************************************************************************************************************************************************************************************************
java -Xmx3550m -Xms3550m -Xmn2g –Xss128k
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
******************************************************************************************************************************************************************************************************************************************************************************************************
调优总结
年轻代大小选择
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
年老代大小选择
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
1. 并发垃圾收集信息
2. 持久代并发收集次数
3. 传统GC信息
4. 花在年轻代和年老代回收上的时间比例
减少年轻代和年老代花费的时间,一般会提高应用的效率
吞吐量优先的应用
一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
tomcat设置JVM参数
在Windows下,在$TOMCAT_HOME/bin/catalina.bat,Linux下,在$TOMCAT_HOME/bin/catalina.sh的前面,增加如下设置:
JAVA_OPTS=”-Xms [min heap size]-Xmx[max heap size]”
例如:
JAVA_OPTS="-Xms1024m -Xmx1024m -XX:PermSize=128m -XX:MaxPermSize=256m"
对象何时进入老年代
长期存活的对象将进入老年代
-XX:MaxTenuringThreshold=n 假设值为n,则新生代的对象最多经历n次GC,就能晋升老年代,但这个必不是晋升的必要条件
-XX:TargetSurvivorRatio=n 用于设置Survivor区的目标使用率,即当Survivor区GC后使用率超过这个值,就可能会使用较小的年龄作为晋升年龄。默认为50
虚拟机采用分代收集的思想管理内存,那内存回收时就必须能识别那些对象该放到新生代,那些该到老年代中。为了做到这点,虚拟机为每个对象定义了一个对象年龄(Age,由GC的次数决定)。每经过一次新生代GC后仍然存活,将对象的Age增加1岁,当年龄到一定程度(默认为15)时,将会被晋升到老年代中。
如果将 -XX:MaxTenuringThreshold 参数设置为0的话,则新生代对象不经过Survivor区,直接进入老年代,对于需要大量常驻内存的应用,这样做可以提高效率;如果将此值设置为一个较大值,则新生代对象会在Survivor区进行多次复制,这样做可以增加对象在新生代的存活时间,增加对象在新生代被垃圾回收的概率,减少Full GC的频率,可以在某种程度上提高服务稳定性。
适龄对象也可能进入老年代
为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄。
大对象直接进入老年代
-XX:PretenureSizeThreshold 即对象的大小大于此值,就会绕过新生代,直接在老年代分配(即所谓“大对象直接进入老年代”)。
除年龄外,对象体积也会影响对象的晋升的。若对象体积太大,新生代无法容纳这个对象,则这个对象可能就会直接晋升至老年代。特别是,如果程序中经常出现“短命的大对象”,容易发生内存还有不少空间却不得不提前触发Full GC以获取足够的连续空间,导致GC效率低下。可通过以下参数使用对象直接晋升至老年代的阈值,单位是byte
PretenureSizeThreshold 参数的意义在于,若遇上述情况时,能避免在 Eden 及两个 Survivor 之间发生大量的内存复制。此参数只对串行GC以及ParNew有效,而Parallel并不认识这个参数。Parallel收集器一般并不需要特别设置。如果遇到必须使用此参数的场合,也可以考虑 ParNew加CMS的收集器组合。