美文网首页
01 | JVM-存储机制

01 | JVM-存储机制

作者: 小亮__ | 来源:发表于2019-06-12 22:05 被阅读0次

    JVM内存因为对象的生命周期,分配和回收的频率不一样,所以对内存区域进行了区域划分,主要被分为三块,新生代老年代持久代

    • 新生代:用于存储新new出来的对象,一般垃圾回收回收这里,当垃圾回收后没有被回收的对象会放到Survivor Spaces中(为复制算法提供了专门的from-to区域)
    • 老年代:用于存储,经过几次垃圾回收没被回收的对象(如缓存对象)
    • 永久代:一般存储方法区中的类信息,Class在被 Load的时候被放入PermGen space区域. 它和和存放Instance的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误
    • 元空间:(Meta Space)在jdk1.8版本后,废弃了永久代,使用元空间
    设计各个分区数据大小的经验
    • 总大小:3-4倍的活跃数据大小
    • 新生代:1-1.5倍活跃数据的大小
    • 老年代:2-3倍活跃数据的大小(新生代的2倍)
    • 永久代:1.2-1.5倍FullGC后永久代空间占用

    活跃数据的大小,是指项目平稳运行时期,长期存活的对象在堆中占用的大小,也就是在Full GC之后,老年代的大小,比较准确的计算方法时,多次获得GC数据,通过取平均值的方式计算活跃数据的大小

    年龄判断
    • 默认对象的年龄是15, -XX:MaxTenuringThreadshold=15 ,经过15次GC后进入老年代
    • 动态年龄判断:相同您领所有对象的大小总和 > Survivor空间的一半
    常用参数
    • -Xmx3550m:设置JVM最大堆内存为3550M。
    • -Xms3550m:设置JVM初始堆内存为3550M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
    • -Xss128k:设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M,之前每个线程栈大小为256K。应当根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。需要注意的是:当这个值被设置的较大(例如>2MB)时将会在很大程度上降低系统的性能。
    • -Xmn2g:设置年轻代大小为2G。在整个堆内存大小确定的情况下,增大年轻代将会减小年老代,反之亦然。此值关系到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的频率,这样做可以在某种程度上提高服务稳定性。默认是15
    • -XX:PretenureSizeThreshold=3145728:动态年龄判断,表示对象大于3M直接进入老年代)
    OOM异常一般主要有如下2种原因:
    1. 年老代溢出,表现为:java.lang.OutOfMemoryError:Javaheapspace
      这是最常见的情况,产生的原因可能是:设置的内存参数Xmx过小或程序的内存泄露及使用不当问题。
      例如循环上万次的字符串处理、创建上千万个对象、在一段代码内申请上百M甚至上G的内存。还有的时候虽然不会报内存溢出,却会使系统不间断的垃圾回收,也无法处理其它请求。这种情况下除了检查程序、打印堆内存等方法排查,还可以借助一些内存分析工具,比如MAT就很不错。

    2. 持久代溢出,表现为:java.lang.OutOfMemoryError:PermGenspace
      通常由于持久代设置过小,动态加载了大量Java类而导致溢出,解决办法唯有将参数 -XX:MaxPermSize 调大(一般256m能满足绝大多数应用程序需求)。将部分Java类放到容器共享区(例如Tomcat share lib)去加载的办法也是一个思路,但前提是容器里部署了多个应用,且这些应用有大量的共享类库

    参数方案(试用于web并发量比较大的服务器):
    java -jar server -Xmx3550m -Xms3550m -Xmn1256m -Xss128k 
     -XX:SurvivorRatio=6 -XX:MaxPermSize=256m -XX:ParallelGCThreads=8
     -XX:MaxTenuringThreshold=0 -XX:+UseConcMarkSweepGC
    
    调优说明:
    • -Xmx 与 -Xms 相同以避免JVM反复重新申请内存。-Xmx 的大小约等于系统内存大小的一半,即充分利用系统资源,又给予系统安全运行的空间。
    • -Xmn1256m 设置年轻代大小为1256MB。此值对系统性能影响较大,Sun官方推荐配置年轻代大小为整个堆的3/8。
    • -Xss128k 设置较小的线程栈以支持创建更多的线程,支持海量访问,并提升系统性能。
    • -XX:SurvivorRatio=6 设置年轻代中Eden区与Survivor区的比值。系统默认是8,根据经验设置为6,则2个Survivor区与1个Eden区的比值为2:6,一个Survivor区占整个年轻代的1/8。
    • -XX:ParallelGCThreads=8 配置并行收集器的线程数,即同时8个线程一起进行垃圾回收。此值一般配置为与CPU数目相等。
    • -XX:MaxTenuringThreshold=0 设置垃圾最大年龄(在年轻代的存活次数)。如果设置为0的话,则年轻代对象不经过Survivor区直接进入年老代。对于年老代比较多的应用,可以提高效率;如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。根据被海量访问的动态Web应用之特点,其内存要么被缓存起来以减少直接访问DB,要么被快速回收以支持高并发海量请求,因此其内存对象在年轻代存活多次意义不大,可以直接进入年老代,根据实际应用效果,在这里设置此值为0。
    • -XX:+UseConcMarkSweepGC 设置年老代为并发收集。CMS(ConcMarkSweepGC)收集的目标是尽量减少应用的暂停时间,减少Full GC发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代内存,适用于应用中存在比较多的长生命周期对象的情况。

    设置完成后,使用怎么的方法,查看,可是使用 jmap -heap PID 实时查看进程使用情况


    引用类型

    • 强引用(Strong Reference).new Object创建出来的对象,GCRoot可达的时候是不会被回收的
    • 软引用(Soft Reference).SoftReference<?>这类对象,在JVM内存紧张的时候,就算引用也会被回收,可用于缓存场景
    • 弱引用(Weak Reference).弱引用的对象就是一定需要进行垃圾回收的,不管内存是否紧张
    • 虚引用(Phantom Reference).JVM完全不会在乎虚引用,其唯一作用就是做一些跟踪记录,辅助finalize函数的使用

    分配担保

    在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC

    永久代到元空间的变更

    首先需要了解的是为什么要去除永久代,成立元空间,因为以前的永久代有一些问题

    • 永久代的大小是在启动时固定好的——很难进行调优
    • 字符串常量池存在永久代中,容易出现性能问题和内存溢出
    • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出
    • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低
    • Oracle 可能会将HotSpot 与 JRockit 合二为一

    总结一下:就是将以前永久代中,内存大小浮动比较大的信息移到了JAVA堆中,而比较确定的留在了元数据中

    相关文章

      网友评论

          本文标题:01 | JVM-存储机制

          本文链接:https://www.haomeiwen.com/subject/pezrfctx.html