JVM之堆内存分配,方法区的回收 -- 02


  JVM的内存模型JMM图形如下:
在这里插入图片描述
  这里主内存里面的数据就是共享的数据(堆,方法区的数据)。为了保证内存的不断变大撑爆内存,需要对主内存的数据进行回收。

1.内存分配及回收策略

 对象的内存分配从大体上讲:

  • 在堆上分配(JIT编译优化后可能在栈上分配),主要在新生代的Eden区中分配;
  • 如果启用了本地线程分配缓冲,将线程优先在TLAB上分配;
  • 少数情况下,可能直接分配在老年代中。
    分配的细节取决于当前使用哪种垃圾收集器组合,以及JVM中内存相关参数设置。

在这里插入图片描述

1.1 对象优先在Eden分配

步骤如下:

  • 1)将新生代内存分为一块较大的Eden空间和两块较小的Survivor空间;

  • 2)每次使用Eden和其中一块Survivor;

  • 3)当回收时将Eden和使用中的Survivor中还存活的对象一次性复制到另外一块Survivor;

  • 4)而后清理掉Eden和使用过的Survivor空间;

  • 5)后面就使用Eden和复制到的那一块Survivor空间,重复步骤3;

默认Eden:Survivor=8:1,即每次可以使用90%的空间,只有一块Survivor的空间被浪费;

大多数情况下,对象在新生代Eden区中分配;

当Eden区没有足够空间进行分配时,JVM将发起一次Minor GC(新生代GC);

Minor GC时,如果发现存活的对象无法全部放入Survivor空间,只好通过分配担保机制提前转移到老年代。

1.2 大对象直接进入老年代

  大对象指需要大量连续内存空间的Java对象,如,很长的字符串、数组;经常出现大对象容易导致内存还有不少空间就提前触发GC,以获取足够的连续空间来存放它们,所以应该尽量避免使用创建大对象;

  “-XX:PretenureSizeThreshold”:可以设置这个阈值,大于这个参数值的对象直接在老年代分配; 默认为0(无效),且只对Serail和ParNew两款收集器有效;如果需要使用该参数,可考虑ParNew+CMS组合。

1.3 长期存活的对象将进入老年代

  JVM给每个对象定义一个对象年龄计数器,其计算流程如下:

  在Eden中分配的对象,经Minor GC后还存活,就复制移动到Survivor区,年龄为1;而后每经一次Minor GC后还存活,在Survivor区复制移动一次,年龄就增加1岁; 如果年龄达到一定程度,就晋升到老年代中;

  “-XX:MaxTenuringThreshold”:设置新生代对象晋升老年代的年龄阈值,默认为15;

1.4 动态对象年龄判定

  JVM为更好适应不同程序,不是永远要求等到MaxTenuringThreshold中设置的年龄;如果在Survivor空间中相同年龄的所有对象大小总和大于Survivor空间的一半,大于或等于该年龄的对象就可以直接进入老年代。
  -XX:TargetSurvivorRatio

1.5 空间分配担保

  当Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保(Handle Promotion);

分配担保的流程如下:

  • 在发生Minor GC前,JVM先检查老年代最大可用的连续空间是否大于新生所有对象空间;
  • 如果大于,那可以确保Minor GC是安全的;
  • 如果不大于,则JVM查看HandlePromotionFailure值是否允许担保失败;
  • 如果允许,就继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小;
  • 如果大于,将尝试进行一次Minor GC,但这是有风险的;
  • 如果小于或HandlePromotionFailure值不允许冒险,那这些也要改为进行一次Full GC;

尝试Minor GC的风险–担保失败:
  因为尝试Minor GC前,无法知道存活的对象大小,所以使用历次晋升到老年代对象的平均大小作为经验值;假如尝试的Minor GC最终存活的对象远远高于经验值的话,会导致担保失败(Handle Promotion Failure);失败后只有重新发起一次Full GC,这绕了一个大圈,代价较高;

2.回收方法区

  Java方法区在Sun HotSpot虚拟机中被称为永久代,很多人认为该部分的内存是不用回收的,java虚拟机规范也没有对该部分内存的垃圾收集做规定,但是方法区中的废弃常量和无用的类还是需要回收以保证永久代不会发生内存溢出。

2.1主要回收对象
  • 1、废弃常量
    与回收Java堆中对象非常类似;
  • 2、无用的类
    同时满足下面3个条件才能算”无用的类”:
    • 1)该类所有实例都已经被回收(即Java椎中不存在该类的任何实例);
    • 2)加载该类的ClassLoader已经被回收,也即通过引导程序加载器加载的类不能被回收;
    • 3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法;
2.2 需要注意方法区回收的应用

   在大量使用反射、动态代理、经常动态生成大量类的应用,要注意类的回收;

如运行时动态生成类的应用:

  • 1)CGLib在Spring、Hibernate等框架中对类进行增强时会使用;
  • 2)VM的动态语言也会动态创建类来实现语言的动态性;
  • 3)另外JSP(第一次使用编译为Java类)、基于OSGi频繁自定义ClassLoader的应用(同一个类文件,不同加载器加载视为不同类)等
2.3 HotSpot虚拟机的相关调整

1)在JDK7中

   使用永久代(Permanent Generation)实现方法区,这样就可以不用专门实现方法区的内存管理,但这容易引起内存溢出问题;
   
   有规划放弃永久代而改用Native Memory来实现方法区;
   
   不再在Java堆的永久代中生成中分配字符串常量池,而是在Java堆其他的主要部分(年轻代和老年代)中分配;

   更多请参考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/enhancements-7.html

2、在JDK8中

  永久代已被删除,类元数据(Class Metadata)存储空间在本地内存中分配,并用显式管理元数据的空间:

   从OS请求空间,然后分成块;

   类加载器从它的块中分配元数据的空间(一个块被绑定到一个特定的类加载器);

   当为类加载器卸载类时,它的块被回收再使用或返回到操作系统;

   元数据使用由mmap分配的空间,而不是由malloc分配的空间;

3、相关参数

  “-XX:MaxMetaspaceSize” (JDK8):指定类元数据区的最大内存大小;

  “-XX:MetaspaceSize” (JDK8):指定类元数据区的内存阈值–超过将触发垃圾回收;  

  “-Xnolassgc”:控制是否对类进行回收;

  “-verbose:class”、”-XX:TraceClassLoading”、”-XX:TraceClassUnloading”:查看类加载和卸载信息;

   更多请参考:《Java语言规范》12.7 卸载类和接口;JDK8类元数据说明: http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html#sthref62

参考:https://blog.csdn.net/tjiyu/article/details/54588494

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值