为什么会OOM?
哪些内存需要被回收?
什么时候回收?
垃圾如何回收?
如有兴趣,请往下看。
1.为什么会OOM?
以下列代码为例:
import java.util.ArrayList;
import java.util.List;
public class LimitService {
private List List;
public static void main(String[] args) {
Listlist = new ArrayList<>();
int i = 0;
try {
while (true) {
list.add(new Byte [1024 * 1024] );
i++;
}
} catch (Throwable throwable ) {
throwable.printStackTrace ();
System.out.printIn("次数:" + i);
}
}
}
运行该代码会得到如下结果:
java.lang.OutOfMemoryError:Java heap space
Java heap space即Java堆空间不足。
造成这个问题的原因有两个:内存泄漏和内存溢出。
对于这两个概念,一个借钱的例子做了很好的解释:
假设你有900w,你的三个亲戚各向你借了250w,不还,这时你借出去的750w便是「内存泄漏」。
而这时又来了个亲戚向你借250w,全部家当150w满足不了这个需求,便导致「内存溢出」,即OutOfMemoryError。
2.哪些内存需要被回收?
需要回收的内存即要回收的垃圾,就是那些不可能再被任何途径使用的对象。
如何确定一个对象是需要回收的垃圾,有以下两种方法:
2.1引用计数法
给对象添加一个引用计数器,当有其他对象引用该对象时,计数器+1;当引用失效时,计数器-1。
该算法原理简单,容易实现。但是,无法解决对象间循环引用的问题,所以主流的JVM中均没有采用这种算法。
2.2可达性分析
为了解决循环引用问题,出现了可达性分析算法(根搜索算法)来确定垃圾。
基本思路是找一些对象作为遍历的起始点(成为GC Roots),从这些起始点开始搜索,当某个对象并没有和任何GC Root产生关联,则认为这个对象已经不被使用了,可以清除。
3.什么时候回收?
非堆不够用的Metaspace GC
old区不够用的Major GC
Eden区、survivor区不够用的Minor GC
system.gc()
4.垃圾如何回收?
找到不需要的对象,再进行回收。垃圾回收用一句话概括起来容易,但实现高效回收却不简单。
这需要程序员建立一个垃圾回收的思想:
为了让程序在运行期间能够进行垃圾回收,需要GC线程与业务线程相互配合。
而为了达到这个目的,标记/清除算法应运而生。
标记阶段从root开始递归地给堆里所有对象打上标记,在清除阶段collector会遍历整个堆,回收没有被标记的对象,将要回收的block插入free_list 链表,还在使用的对象则取消标志位。
但是,标记/清除算法存在效率低、内存不连续(内存碎片)的问题。
于是诞生了以该算法为基础,不断优化而产生的复制算法和标记/整理算法。
掌握算法后,根据对象的生命周期长短采用分代收集便能提高效率。
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、版本的虚拟机所提供的垃圾收集器都可能会有很大差别。
上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。
虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。
因为目前并无完美的收集器出现,所以在回收垃圾时只是选择对具体应用最适合的收集器。
下表列出各个收集器的适用场景:
更多JVM内容将在后续持续更新,关注我们,做一次知识框架的系统学习吧。
这是一个为 程序员量身定做的求职公众号。 我们有互联网企业创始人,技术类畅销书作者等行业大牛进行 职业生涯经验的分享 及专注互联网行业、指导过上万份简历的 资深HR面试tips 分享;累积四年以上的10余项 主流技术的面试题库 ,涵盖各大厂、互联网名企面试题;针对不同岗位的 精选面试简历 。懂你所想,知你所需! 关注我们,有机会获得1V1专属职业规划! 转了吗 赞了吗 在看