浅谈JVM(二)——内存分配和垃圾回收

6 篇文章 0 订阅

对于C/C++开发人员来说,他们需要在代码层面上控制一个对象生命周期,自由宣布对象的产生和灭亡。我们称之为手动的内存管理。虽然灵活,但是增加了内存溢出和内存泄漏的风险。而对于Java来说,我们并不需要手动的内存管理,因为JVM自带了内存管理机制,实现了自动的内存管理,并且会大大降低出现内存溢出和内存泄漏的风险。但是,过度的依赖于JVM自带的内存管理机制,会弱化我们在程序出现内存溢出时问题的定位和解决能力。


New一个对象

在代码中创建一个对象我们只需要用new关键字即可,但是在JVM中,这个过程就不这么简单了。首先,JVM会见长这个new指令的参数是否可以在常量池中定位到一个类的符号引用,然后再检查与这个符号引用对应的类是否已经成功的加载、连接、和初始化等步骤,当类完成装载以后就可以完全知道创建一个对象实例所需要的内存空间打消了,然后JVM对其内存分配,用来存储新产生的对象。

内存分配

为新的对象分配内存是非常严谨和复杂的,JVM不仅要考虑如何分配和在哪里分配,还要考虑用GC执行完内存回收以后是否会产生内存碎片。因此,分配的规则不是固定的,这需要根据当前使用的是哪种垃圾收集器组合还有虚拟机与内存相关的参数设置。但是总的来说,内存分配还是遵循如下规则的:

优先在Eden区分配

由于JVM垃圾回收机制,会把堆内存分为老年代和新生代。由于新生代存放的对象的生命周期比较短,所以,一般使用标记清楚算法来回收垃圾。新生代又可以划分为Eden,Survior和To Survior区(我的JVM(一)中有介绍),每次创建新的对象时,JVM都会优先把对象分配到Eden区中。(ps:从堆区划分空间的操作是非线程安全的,因此需要保证操作的原子性)如果Eden区的内存不够时,JVM就会执行一次MinorGC(后面会说),直到可以在Eden区可以分配内存空间为止。


大对象进入老年代

大对象的大是一相对的大,是比“-XX:PretrnureSizeThreshold”设置的对象还大的对象称为大对象。常见的如长字符串,长数组,他们占据了大量连续的内存。对于大对象,如果放入Eden区域会带来问题,这会使得大量原来存在于Eden区的对象复制到老年代,这将增加系统的开销。因此,如果直接把大对象放入Eden区,就会避免大量的对象复制。


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

当对象刚刚创建的时候我们都知道是年轻的,当对象经历一次MinorGC以后还能存货,那么对象年龄就加一。而这个长期也是相对的长期,当对象的年龄大于“-XX:MaxTenurinigThreshold”设置的值时,就会进入老年代,如果你没有设置,则默认的一般是15。

注意:JVM并不一定是等到对象年龄达到阀值才将对象进入到老年代。如果在Survior内存区中,某一个相同的年龄的所有对象占用的空间如果超过了Survior区域内存的一般,那么,这些相同年龄对象会直接进入老年代。


空间分配担保

当发生MinorGC的时候,JVM会先查看老年代的空间是否大于新生代所有对象总的空间,如果条件成立,那么这次GC可以确保安全,不会存在新生代对象进入老年代失败的情况。但如果不成立,JVM会查看HandlePromotionFailure设置值是否允许担保失败。也就是告诉JVM这次MinorGC是否可以失败,如果允许失败,那么JVM会继续检查老年代中的空间是否大于新生代进入老年代平均大小,如果大于,则大胆的进行一次MinorGC;如果小于或者不允许出现GC失败,那么JVM就会进行一次FullGC,使得老年代腾出更多的空间。

通俗来讲:MinorGC一次,新生代一部分对象会进入老年代,但是具体有多少对象是不明确的,如果老年代空闲的空间足够大,就算新生代中所有对象全部进入老年代都是安全的;如果空闲的空间不足够,就需要老年代进行担保,取平均MinorGC新生代的值和空闲空间作比较,确定是否FullGC让老年代腾出足够的空间。

垃圾回收

Java对不同类型的引用有着不同的垃圾回收策略。

  • 强引用:Person person = new Person(“sunny”); 不管系统资源有么的紧张,强引用的对象都绝对不会被回收,即使他以后不会再用到。

  • 软引用:SoftReference p = new SoftReference(new Person(“Rain”)),内存紧张时会收回。

  • 弱引用:通过WeakReference类实现,WeakReference p = new WeakReference(new Person(“Rain”));不管内存是否足够,系统垃圾回收时必定会回收。

垃圾标记

在垃圾回收之前,首先需要知道那些是垃圾,只有被标记为垃圾的对象JVM才可以将其回收,而此过程我们称之为垃圾标记阶段。目前比较常见的垃圾标记算法有引用计数法和根搜索算法,但是大多数JVM常用后者。

  • 引用计数算法:每一个对象都有一个私有的引用计数器,当对象被引用时,计数器加一,不在引用时就减一,当计数器为0时,说明对象没有被其他对象所引用,此时这对象可以标价为垃圾。但是当存在两个对象相互引用时,计数器永远不为零,这就会导致对象不发回收,极有可能发生内存泄漏。
  • 根搜索算法:以根对象集合作为起点,按照从上往下搜索被根对象集合所连接的目标对象是否可达,如果目标不可达,就意味着对象已经死亡。

垃圾回收算法

对垃圾进行标记以后,就要对垃圾进行回收。JVM中常见的三种垃圾回收算法有:

  • 标记—清除
  • 复制算法
  • 标记压缩

标记—清除:它将垃圾回收分为两个阶段,分别是垃圾标记和内存释放,相对于其他两种算法,标记—清除算法不仅效率低下,而且会产生内存碎片,导致后面没有足够的内存分配给比较大的对象。

复制算法:复制算法被广泛用于新生代中。当使用复制算法执行一次MinorGC时,过程如下:

  1. Eden区存活下来的对象进入To Survivor区。
  2. Survivor区存活下来的年轻对象进入To Survivor,老年对象进入老年代,当To Survivor容量达到阈值时候对象也会进入老年代。
  3. Eden区和Survivor区都为空,存活下来的对象都在To区或进入老年代
  4. Survivor和ToSurvivor互换位置。

总结:复制算法就是将两个Survivor区作为临时的空间角色,且保证了两个Survivor区有一块是空的。

标记压缩:标记压缩常用于老年代中。当成功标记出内存中的垃圾对象时,该算法会把所有存货的对象都放在一块连续的内存空间中,然后执行FullGC回收垃圾释放空间。此时,内存中已用和未用的内存都在一边,当为新对象创建内存空间时,只要将对象分配在第一块空闲内存上即可。

垃圾回收算法总结

  • 标记—清除:效率低,产生内存碎片,勉强适用于老年代。
  • 复制算法:速度快,但是会有内存空间浪费,适用于新生代。
  • 标记压缩:节省内存,适用于老年代。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值