Java内存分配和垃圾回收

最近拜读了周志明老师的深入理解Java虚拟机,也写一点皮毛的Java垃圾回收机制,可能存在一些纰漏,本人是菜鸟,哈哈。
废话不多说,直接进入正题。
Java提供了垃圾回收机制(GC)让我们可以免去很多的像C++的内存释放的问题,但是问题接着又来了,由于GC是随机的,不可避免有一些内存会浪费,那么就需要我们对Java的GC机制有着清晰的认识。
第一、怎么判断对象已经死了呢?
在Java中提供了一下两种普遍的方法,在之前的Java虚拟机使用的是第一种,引用计数算法,顾名思义,就是一个对象每增加一个引用,便在自己的计数器上面增加1,当减少一个引用的时候,就在对象的计数器减1,这种算法是可行的,并且也具有很高的执行效率,但是存在缺陷的是引用计数算法无法处理当对象相互循环引用的时候带来的问题,会造成对象其实已经可以回收但是却还存在引用计数器不为0的现象。为了解决这个问题,提出了第二种算法,可达性分析算法,可达性分析算法中,是通过GCRoots作为对象的起始点,每个对象会有自己的引用链跟GC Roots相连接,当不可达的时候,也就是说当对象的引用链并没有刻意达到GCRoots的时候,就被判定为刻意回收的对象。
在这里引出Java之前给引用的四个类型定义:
1、强引用
不会被内存回收的引用,具有最高的权限,就算是抛出OOM也不舍得回收强应用类型的对象。
2、软引用
只有在内存快溢出的时候才会去收集这一部分对象,如果经过这次回收之后,依然是内存不足,那么就会OOM。
3、弱引用
在检查到弱引用需要回收,会在接下来的GC进行回收
4、虚引用
任意时刻都可以进行回收的,所以该优先级也是最低的。
但是,我们判定对象是没用的, 不可达的时候,是否就会真的回收了呢?答案是不一定。
当对象被认为是不可达的时候,其实只是进行了一次标记,真正需要判断是否可以进行回收需要经历如下的操作。
当对象在可达性分析的时候,发现并没有跟GCRoots连接,那么就会进行第一次标记,并进行一次筛选,筛选的原则是对象是否需要执行finalize()方法或者是对象是否覆盖了该方法,如果对象不需要执行该方法的时候,就会被放过。不会进行回收,但是如果对象被判定需要执行finalize()的方法的时候,那么虚拟机会将对象放在F-Queue队列中,并且会开启一个线程去执行。但是在这个时候可以进行自救,也就是说在finalize()方法中完成自救,拥有跟其他任意对象相关联。
如果没有完成自救,或者已经自救过一次被再次回收的,虚拟机就会进行回收,并释放对应的内存区域。
此时,当虚拟机已经完成垃圾对象的判定的时候,接下来就是垃圾回收,垃圾回收的关键在于垃圾收集的算法。
这里有以下几种垃圾收集的算法:
1、标记——清除算法
标记——清除算法值得是在不可用的对象做一个标记,然后回收的时候原地回收,这个算法适合回收大规模的垃圾,不然该算法有一个缺陷,就是当进行标记——清除的时候会产生很多的内存碎片,当下次需要分配大对象内存的时候,需要寻找一块大的内存空间,就会触发下一次的GC。
2、复制算法
复制算法是一种把内存容量划分为相等大小的两块内存空间A和B,当A空间需要进行GC的时候,将A中的存活的对象放在B中,然后对A内存整块回收,这样的算法可以解决大对象开辟空间的问题,但是内存减少了一半,所以我们可以考虑是否将内存空间划分成其他比例,例如811呢。
3、标记——整理算法
标记整理算法其实跟标记清除算法是异曲同工的,但是唯一的区别在于,标记整理算法是将存活的对象往一边挪动,然后在这个边界之外的对象进行GC。
知道了如何判断对象死活,知道了垃圾收集算法,现在执行的垃圾收集器我们也需要了解。
垃圾收集器有很多的种类,由于认知原因,只拿出以下几种了解了解。
第一种、Serial 单线程垃圾回收器
Serial是一个执行在新生代的单线程垃圾回收器,当Serial进行GC的时候,会触发STW(Stop the World),也就意味着会暂停用户的所有进程,当GC完之后再恢复用户的操作,这样带给用户端的体验是不好的,但是反过来一想,其实也可以理解,就像你在打扫课室的时候,你打扫,然后你的同学继续扔,这样的回收可是很累的。但是无可否认的是,Serial在单CPU,单核机器上面的处理性能是很棒的。
第二种、ParNew 多线程垃圾回收器
ParNew实际上就是Serial的多线程版本,实现了可以让用户的进程和垃圾回收的进程可以同时运行的效果。
第三种、Parallel Scavenge
其实这个跟ParNew是很类似的,唯一的不同是他们考虑的侧重点不同,ParNew是为了减少用户线程的停顿时间,但是Parallel Scavenge是为了提高吞吐量,吞吐量也就是意味着有效代码的执行时间,有效代码的执行时间占总代码执行时间(等于有效代码+GC)的比例就是吞吐量,提高吞吐量也会造成GC次数过多,因为你需要保证垃圾少,那么就需要保证多次触发进行垃圾回收。
还有各垃圾回收器对应的老年代的垃圾回收期,还有G1就先不讲了。
到这里就已经将垃圾回收的过程和核心讲完了。
接下来既然有垃圾回收,那么也有对象在内存中的存放
内存分配也是一个大学问。
但是会有一下几条原则:
1、对象优先分配到新生代的Eden区,当Eden区没有足够的空间分配的时候,虚拟机进行一次Minior GC,经历一次Minor GC之后,新生代Eden区的对象会放在Survivor区,如果Survivor区不够容量去存放这些对象的时候,会将对象放在老年代区,此时涉及到担保
担保指的是当对象移到Survivor区的时候,如果没有足够的区域存放从Eden区的对象的时候,便会送到老年代区,此时老年代区的存放能力就是一种担保。
2、大对象直接进入老年代
大对象,举例而言就是比如数组,数组就是一个典型的大对象,需要的内存也大,同时还有字符串等等。大对象对于内存分配是一个坏消息,因为在新生代我们采用的是复制算法,为了避免复制的频繁发生就放在老年代,老年代除存放大对象之外,更重要的是将长期存活的对象放入老年代中。
这里也涉及到一个标准,就是如何衡量对象是长期存活的呢,这个就要看我们的设置,但是我们在这里只讲算法,称之为年龄计算算法。
年龄计数算法是依靠Minor GC来实现的。当第一次Minor GC的时候,新生代Eden区会将其移动到Survivor,然后年龄增加1,再次经历Minor GC的时候会增加1,然后增加到虚拟机指定的次数,一般是15的时候,那么就移动到老年代。
然后,洗澡去了,哈哈。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值