Java初学者如何在Java虚拟机中回收机制实现步骤!

发布于 2017-12-12 | 作者: 听我说教育 | 来源: 网易号 | 转载于: 网易号

在Java虚拟机中,对象和数组的内存都是在堆中分配的,垃圾收集器主要回收的内存就是再堆内存中。如果在Java程序运行过程中,动态创建的对象或者数组没有及时得到回收,持续积累,最终堆内存就会被堆满,导致OOM。

JVM提供了一种垃圾回收机制,简称GC机制。通过GC机制,能够在运行过程中将堆中的垃圾对象不断回收,来保证程序的正常运行的哟。

垃圾对象的判定

我们都知道,所谓“垃圾”对象,就是指我们在程序的运行过程中不再有用的对象,即不再存活的对象。那么怎么来判断堆中的对象是“垃圾”、不再存活的对象呢?

引用计数法

每个对象都有一个引用计数的属性,用来保存该对象被引用的次数。当引用次数为0时,就意味着该对象没有被引用了,也就不会在使用这个对象了,可以判定为垃圾对象。但是,这种方式有一个很大的Bug,就是无法解决对象间相互引用或者循环引用的问题:当两个对象相互引用,他们两个和其他任何对象也没有引用关系,它俩的引用次数都不为0,因此不会被回收,但实际上这两个对象已经不再有用了。

可达性分析(根搜索法)

为了避免使用引用计数法带来的问题,Java采用了可达性分析法来判断垃圾对象。

虽然这种方式可以将所有对象的引用关系想象成一棵树,从树的根节点GC Root遍历所有引用的对象,树的

那么什么样的对象可以作为GC的根节点呢?

虚拟机栈(帧栈中的本地变量表)中引用的对象

方法区中静态属性引用的对象

方法区中常量引用的对象

本地方法栈中JNI引用的对象

引用状态

垃圾回收机制,不管采用是引用计数法,还是可达性分析法,都与对象的引用有关,Java中存在四种引用状态:

强引用 - 我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,就表示它处于可达状态,垃圾回收器绝不会回收它,即便系统内存非常紧张,Java虚拟机宁愿抛出

OutOfMemoryError

错误,使程序异常终止,也不会回收被强引用所引用的对象。因此,强引用是造成Java内存泄露的主要原因之一。

软引用 - 一个对象只具有软引用,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

弱引用 - 一个对象只具有弱引用,那就类似于是可有可无的。弱引用和软引用很像,但弱引用的引用级别更低。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

虚引用 - 一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收的活动,我们平常一般不会使用。

垃圾回收算法

通过可达性分析算法能够判定哪些对象是需要回收的了,那么回收具体需要怎样去执行呢?

标记-清除算法

首先需要标记可以回收的对象内存,然后在对回收的内存进行清除。

标记-清除算法(回收前)

标记-清除算法(回收后)

但是这样的话,随着程序的运行,会不断分配释放内存,在堆中会产生很多的不连续的空闲内存区,即内存碎片。这样即使有足够多的空闲内存,也不一定能分配出足够大的内存,并且可能会造成频繁的GC,影响效率,甚至OOM。

标记-整理算法

和标记-清除算法不同的是,标记-整理算法在标记后不直接清理可回收内存,而是将存活对象都移动到一端,然后清除掉可回收内存。

标记-整理算法(回收前)

标记-整理算法(回收后)

这样做的好处就是不会产生内存碎片。

分享之后我还是要推荐下我自己的Java学习群:217799417,不管你是小白还是大牛,小编我都挺欢迎,不定期分享干货,包括我自己整理的一份2017最新Java开发和零基础入门教程,送给大家,欢迎初学和进阶中的小伙伴。

复制算法

复制算法需要先将内存分为两块,先在其中一块内存上分配内存,当这块内存被分配完后,则执行垃圾回收,然后把存活对象全部复制到另一块内存上,第一块内存则全部清空。

复制算法(回收前)

复制算法(回收后)

这种算法不会产生内存碎片,但是相当于只能使用一半的内存空间。同时,复制算法和存活对象的数量有关,如果存活对象的数量多,那么复制算法的效率会大大降低。

分代收集算法

而后在Java虚构机中,工具的性命周期有长有短,大部分工具的性命周期很短,只要少部分的工具才会在内存中存留较长期,是以能够根据工具性命周期的是非将它们放在分歧的地区。在采纳分代网络算法的Java虚构机堆中,一样平常分为三个地区,用来分离贮存这三类工具:

重生代 - 刚创立的工具,在代码运转时一样平常都邑持续不断地创立新的工具,这些新创立的工具有许多是部分变量,很快就会酿成渣滓工具。这些工具被放在一块称为重生代的内存地区。重生代的特色是渣滓工具多,存活工具少。

老年月 - 一些工具很早被创立了,阅历了屡次GC也没有被收受接管,而是不停存活下来。这些工具被放在一块称为老年月的地区。老年月的特色是存活工具多,渣滓工具少。

永远代 - 一些随同虚构机性命周期永远存在的工具,好比一些动态工具,常量等。这些工具被放在一块称为永远代的地区。永远代的特色是这些工具一样平常不需要渣滓收受接管,会在虚构机运转进程中不停存活。(在Java1.7以前,办法区中存储的是永远代工具,Java1.7办法区的永远代工具移到了堆中,而在Java1.8永远代曾经从堆中移除,这块内存给了元空间。)

分代网络算法也就根据重生代和老年月来停止渣滓收受接管的。

对付重生代地区,每次GC都邑有许多渣滓工具被收受接管,只要大批存活。是以采纳复制收受接管算法,GC时把残剩很少的存活工具复制曩昔便可。

在重生代地区中,并非依照1:1的比例来停止复制收受接管,而是依照8:1:1的比例分为了Eden、SurvivorA、SurvivorB三个地区。此中Eden意为伊甸园,描述有许多重生工具在里面创立;Survivor区则为幸存者,即阅历GC后仍旧存活下来的工具。

Eden区对外供给堆内存。当Eden区将近满了,则停止Minor GC(重生代GC),把存活工具放入SurvivorA区,清空Eden区;

Eden区被清空后,继承对外供给堆内存;

当Eden区再次被填满,此时对Eden区和SurvivorA区同时停止Minor GC(重生代GC),把存活工具放入SurvivorB区,此时同时清空Eden区和SurvivorA区;

Eden区继承对外供给堆内存,并反复上述进程,即在 Eden 区填满后,把Eden区和某个Survivor区的存活工具放到另一个Survivor区;

当某个Survivor区被填满,且仍有工具未被复制终了时,或许某些工具在反复Survive 15次阁下时,则把这部分残剩工具放到老年月地区;当老年区也被填满时,停止Major GC(老年月GC),对老年月地区停止渣滓收受接管。

老年月地区工具一样平常存活周期较长,每次GC时,存活的工具比较多,是以采纳标志-收拾算法,GC时挪动大批存活工具,不会发生内存碎片。

触发GC的范例

Java虚构机会把每次触发GC的信息打印进去,能够根据日记来阐发触发GC的缘故原由。

GC_FOR_MALLOC:表现是在堆上分派工具时内存不敷触发的GC。

GC_CONCURRENT:当咱们应用程序的堆内存到达一定量,或许能够懂得为将近满的时刻,体系会主动触发GC操纵来开释内存。

GC_EXPLICIT:表现是应用程序挪用System.gc、VMRuntime.gc接口或许收到SIGUSR1旌旗灯号时触发的GC。

GC_BEFORE_OOM:表现是在筹备抛OOM非常以前停止的末了尽力而触发的GC。