这篇文章主要讲解了“java垃圾收集器与内存分配策略是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“java垃圾收集器与内存分配策略是什么”吧!
java虚拟机一个老生常谈的问题就是垃圾回收和内存分配。java虚拟机内存的自动化管理最终归结为自动的解决两个问题:给对象分配内存,以及回收分配给对象的内存。
先说回收分配给对象的内存吧,其中最重要也就输入无用对象的收集了,其实也就两步走,收集-释放。收集有涉及到垃圾收集算法,下面具体讲一下。
判定对象是否需要被回收的算法
垃圾收集算法的核心当然是判定当前对象是否已无用,然后才开始收集。《深入理解java虚拟机》中将了两种收集算法。一种为引用计数算法,一种为可达性分析算法。
一.引用计数算法:
给对象中添加一个程序计数器,对象被调用一次,计数器加1,当对象的引用失效时,计数器减1.当计数器为0 时则表示对象可能已经无用需要被回收。
拓展两点:
1.当对象被new出来的时候,如果没有使用它,是会被回收的,也就是说创建初始时,计数器为0。
2.当对象引用失效时,计数器减1。我记得当时被面试问到对象引用怎么失效的,怎么判断。现在想想当使用失效应该不是我们控制的,当对应被引用之后,过一段时间,这个引用就会自动失效吧。(这里是个人理解,有错误望指正!)
虽然引用计数算法很简单明了,但是有一个严重的问题就是循环引用的对象无法被回收。类似下面这种:
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
所以出现了另一种垃圾收集的算法。
二.可达性分析算法:
算法的基本思想是,通过一系列的“GC Roots ”的对象作为起点,通过这些起点能找到的对象则说明该对象是存活的,如果通过所有的起点都不能达到该对象,则说明该对象可能是无用的,会被回收。
主流的虚拟机都是通过可达性分析算法来判定对象是否要被回收的。
下面给大家看一个例子证明java虚拟机是通过可达性分析算法来判断的。
package chapter.three;
//Debug Configuration:
//-verbose:gc: -Xms20M -Xmx20M -Xmn10M -verbose:gc -XX:+PrintGCDetails -XX:SurvivorRatio=8
public class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[2 * _1MB];
public static void testGC()
{
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
System.out.println("第一次");
objA.instance = objB;
objB.instance = objA;
System.out.println(objA);
System.out.println(objB);
objA = null;
objB = null;
System.gc();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二次");
objA.instance = objB;
objB.instance = objA;
System.out.println(objA);
System.out.println(objB);
}
public static void main(String[] args)
{
testGC();
}
}
objA 和objB是相互引用的,如果是采用计数器的算法,两次输出应该是一样的,如果是采用可达性分析的算法,第二次会报错,因为两个对象被回收了,这样操作两个对象会报错找不到。运行结果。
第一次chapter.three.ReferenceCountingGC@15db9742chapter.three.ReferenceCountingGC@6d06d69c第二次Exception in thread "main" java.lang.NullPointerException at chapter.three.ReferenceCountingGC.testGC(ReferenceCountingGC.java:34) at chapter.three.ReferenceCountingGC.main(ReferenceCountingGC.java:43)
拓展讲一点吧,其实这个不是很重要,了解一下
另外书中还讲了一点,哪些不可达的对象,也不一定就会被回收,并没有直接判死刑,而是判的缓刑,有一次自救的机会。也就是说,所有的“GC Roots”根节点都无法到该对象时,该对象会被标记一次,并对其进行一次筛选。筛选的条件是改对象有没有重写finalize()方法,如果没有重写,那么该对象就会被判死刑,会被回收。如果该对象有重写finalize()方法,就会执行这个方法,对象可以在这个方法中自救。但是这个方法只会条用一次,也就是说第二次如果还是不可达还是会被回收掉。不知道大家有没有理解,下面看一个例子
package chapter.three;
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK=null;
public void isAlive(){
System.out.println("yes,i am still alive...");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed !");
FinalizeEscapeGC.SAVE_HOOK=this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK=new FinalizeEscapeGC();
System.out.println("first:");
SAVE_HOOK=null;
System.gc();
Thread.sleep(500);
if(SAVE_HOOK !=null){
SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead...");
}
System.out.println("second:");
SAVE_HOOK=null;
System.gc();
Thread.sleep(500);
if(SAVE_HOOK !=null){
SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead...");
}
}
}
结果
first:
finalize method executed !
yes,i am still alive...
second:
no,i am dead...
但是书中建议我们最好不要这么做,因为finalize()能做的,try-finally语句或者其他的语句就能做,,且做的更好,所以我这也是简单的提一下。
垃圾收集算法
有4种,标记-清除算法,复制算法,标记整理算法,分代收集算法。其实也就三种,以为分代收集算就是不同场景使用前面的三种方法。
标记清除算法 :就是先将需要回收的内存标记,然后清除掉。这种算法思路简单,但是会造成内存分布零散,存储大文件会导致触发一次GC.
复制算法:是将内存按照容量划分成大小相等的两块,每次都只使用其中的一块,其中一块内存使用完了,就会统计出这块内存中存活的对象复制到另一块内存中,并且清理当前快,这样循环使用。但是这样的缺点就是导师内存使用率太低,总有一半的内存在闲置状态。
标记整理的算法,标记整理算法也是先将需要回收的内存进行整理,然后并不是直接清理掉,而是将存活的对象向前移动,保存内存的前面都是存活的对象,然后清理掉回收的对象。这样做的解决了标记清除算法导致的内存可用空间分布的太散的问题,但是这样做的缺点就是效率低下。
分代收集算法:是把java堆分为老年代,新生代。新生代中对象存活时间普遍短使用复制的算法,老年代对象周期较长,采用标记整理算法或者标记清除算法。
垃圾收集器
上面的算法部分都是回收的理论,现在垃圾收集器则是内存回收的具体实现啦,虚拟机中存在很多的收集器,并且这些收集器是配合使用的,各种收集器有各自的优缺点,下图是HotSpot虚拟机中使用的垃圾收集器,以及这些收集器之间哪些是可以搭配只用的。
serial收集器:是一个单线程收集器,只有一个cpu或者一条收集线程去进行收集,而且更重要的是在收集线程在进行垃圾收集的过程中会暂停用所有的用户线程,导致用户进行等待。
ParNew收集器:ParNew收集器是serial收集器的多线程版本,但是一样的,在收集线程工作时,所有的用户线程会进行等待。
parallel Scavenge收集器 是一个新生代收集器,使用的复制算法,是并行的多线程。这个收集器关注点是吞吐量,吞吐量是指,虚拟机总共运行100s,垃圾收集占用1s,那么吞吐量就是99%。
se'rial Old 收集器 是serial收集器的老年代版本,采用的是标记整理的算法。一样是一个单线程收集器。
parallel Old 收集器是parallel Scavenge收集器的老年代版本,采用的是标记整理的算法,多线程。
GMS收集器:是一种获取最短回收停顿时间为目标的收集器。是基于标记清除 的算法实现的。有4个过程:
初始标记
并发标记
重新标记
并发清除
其中 初始标记和重新标记两部依然会“stop the world”也就是说这两部还是会造成用户线程等待,但是相对而言等待的时间要短很多。
G1收集器,是一种面向服务器的收集器,具备以下特点:
并行与并发
分代收集
空间整合
可预测的停顿
G1收集器大致分为以下几个步骤
初始标记
并发标记
最终标记
筛选回收
内存分配与回收策略
对象优先在eden(新生代)中分配。
大对象直接进入老年代
长期存活的对象进入老年代
动态对象年龄判定
感谢各位的阅读,以上就是“java垃圾收集器与内存分配策略是什么”的内容了,经过本文的学习后,相信大家对java垃圾收集器与内存分配策略是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。