200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > 深入理解Java虚拟机读书笔记之垃圾收集器与内存分配策略

深入理解Java虚拟机读书笔记之垃圾收集器与内存分配策略

时间:2019-04-07 12:31:14

相关推荐

深入理解Java虚拟机读书笔记之垃圾收集器与内存分配策略

可达性分析算法Java虚拟机通过可达性分析算法来判断哪些对象已死可以回收,哪些对象还存活不可回收。可达性分析算法的实现原理:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连接时,那么这个对象就死了,可以被回收。换句话说,当对象到GC Roots不可达时,该对象就可以被垃圾收集器回收。可作为GC Roots的对象有哪些?虚拟机栈(栈帧中的本地变量表)中引用的对象。方法区中类静态属性引用的对象。方法区中常量引用的对象。本地方法栈中JNI引用的对象。再谈引用Java的引用分为强引用,软引用,弱引用,虚引用四种,这四种引用强度依次递减。接下来分别介绍这几种引用。强引用:强引用在程序中普遍存在,类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。软引用:软引用是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这部分对象列进回收范围之中进行第二次回收,如果这次回收后还没有足够内存的话,将会抛出内存溢出异常。软引用通过类SoftReference类来实现,实例如下:import java.lang.ref.SoftReference;public class Main {public static void main(String[] args) {SoftReference<String> sr = new SoftReference<String>(new String("hello"));System.out.println(sr.get());}}

弱引用:用来描述非必须对象的,它的强度比软引用还要弱,被弱引用关联的对象只能生产到下一次垃圾收集发生之前,当垃圾收集器开始工作时,无论内存空间是否足够,都会把弱引用关联的对象回收掉。弱引用通过WeakReference来实现。实例如下:public class Main {public static void main(String[] args) {WeakReference<String> sr = new WeakReference<String>(new String("hello"));System.out.println(sr.get());System.gc(); //通知JVM的gc进行垃圾回收System.out.println(sr.get());}}虚引用:虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。生存还是死亡判断一个对象是活着的还是已经死了要经过两次标记,如果对象经过可达性分析后发现该对象没有雨GC Roots之间有任何引用链的话,此时对该对象进行第一次标记。然后进行第二次筛选。进行第二次筛选的条件是,如果该对象有finalize方法,而且finalize方法还没有执行的话那么进行第二次帅选,否则直接判定该对象已死。如果想要挽救该对象的话,则需要在finalize方法中把对象连接到GC Roots引用链上的任何一个对象上即可。垃圾收集算法目前主流的垃圾收集算法有,标记-清除算法(Mark-Sweep),复制算法(Copying),标记-整理算法(Mark-Compact),分代收集算法。下面简单介绍下各算法的特点。标记-清除算法:该算法就是先通过可达性分析算法判断出那些对象已死,然后把已死的对象进行标记,然后再统一执行清除。缺点是容易产生内存不连续的碎片,当要分配较大对象而没有整块内存时,不得不再触发一次GC。复制算法:复制收集算法是把堆内存分成两部分,给对象分配内存时只在其中的一部分分配,然后发送GC时,经过可达性分析后把存活的对象移到另外的一部分中,然后把本端全部清空。复制算法适用于每次GC后仅有少量对象存活的场景,这样复制对象时不会消耗太长时间。所以复制收集算法仅适用于年轻代,年轻代对象朝生夕灭。该算法的另外一个缺点是,由于把内存分为两部分导致其中一部分内存无法再给新创建的对象分配内存。导致内存使用率可能会比较低。但是生产环境中使用的复制收集算法的垃圾收集器并不是将内存一分为二的,比如ParNew收集器,它是将年轻代内存区域分为edn区,survivor from,survivor to三部分,可通过XX:SurvivorRatio参数来设置edn和Survivor的比例,默认为8:1。新创建的对象都分配在edn区,然后发送GC后edn区和survivor from中还存活的对象都复制到survivor to区。(from 和 to是相对的,真实的survivor分为survivor 0 和survivor 1 可通过jstat -gcutil pid来查看)然后再下一次GC时,再把edn区和survivor to区中存活的对象都移到survivor from区中。如果这时surivior from区中空间不足以装下存活的对象,那么就需要JAVA堆老年代来进行内存担保,之间把那些存活的对象都分配给老年代中。所以从这也可以看出为什么老年代不适用复制收集算法的原因之二,就是没有内存给老年代再做内存担保。如果此时老年代也没有足够内存存放这些存活的对象的话,则会触发一次老年代的GC(Full GC)。标记-整理算法:和标记-清除算法差不多,它先根据可达性分析后判断出那些对象存活,然后把所有存活的对象移到内存的一端,然后把存活边界以外的所有内存清空。优点是GC后内存规整连续,缺点是移动对象带来的性能消耗。分代收集算法:生产环境中大部分都是采用分代收集的思想,将内存分为年轻代和老年代,可通过-Xmn参数来分配年轻代,-Xmx和-Xms来分配整个堆内存的最大值和最小值,如果这两个参数设置的值是一样的,则表示不允许JVM进行堆内存的动态扩展。那么新创建的对象分配在堆内存的那个区域呢,何时才会进入老年代呢?下面来解答这两个疑问:新创建的对象优先在年轻代的edn(伊甸)区分配,但这也不是绝对,如果发现edn区中没有足够的空间的话,则会触发一次年轻代的GC(Monitor GC)。如果这个对象很大,需要大量的连续内存去分配的话,比如说很长的字符串,数组等,JVM提供了一个参数-XX:PretenureSizeThreshold,让大于这个阈值的对象直接分配到老年代中。这样做的目的是如果这个大对象是个长时间存活的对象的话,避免在年轻代的edn区和survivor区中来回复制。那么年轻代的对象什么时候会进入老年代呢,虚拟机给每个对象定义了一个对象年龄,只要熬过一次GC后,对象年龄便会加一,当它的年龄超过一定阈值时(默认为15)则直接进入老年代中。阈值可以通过参数-XX:MaxTenuringThreshold来设置。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。