【JVM知识汇总-1】JVM内存模型【JVM知识汇总-2】HotSpot虚拟机对象【JVM知识汇总-3】垃圾回收策略与算法【JVM知识汇总-4]HotSpot垃圾收集器[JVM知识汇总-5]内存分配与回收策略[JVM知识汇总-6]JVM性能调优[JVM知识汇总-7]Class文件结构[JVM知识汇总-8]时序类加载【JVM知识总结-9】类加载的过程【JVM知识总结-10】类加载器程序计数器、虚拟机栈、本地方法栈随线程一起诞生和销毁;它在方法开始时被压入堆栈,并在方法结束时弹出堆栈。这些区域的内存分配和回收是确定性的,不需要过多考虑这些区域的回收,因为内存自然会在方法结束或者线程结束的时候回收。对于Java堆和方法区,我们只能知道在程序运行过程中会创建哪些对象。这部分内存的分配和回收是动态的,垃圾收集器关注的正是这部分内存。判断对象是否存活如果一个对象没有被任何对象或变量引用,那么它就是一个无效对象,需要被回收。引用计数方法在对象头维护一个计数器计数器,对象被引用一次时计数器+1;如果引用对时间敏感,则计数器为-1。当计数器达到0时,该对象被视为无效。引用计数算法实现简单,判断效率也很高。在大多数情况下,这是一个很好的算法。但是主流的Java虚拟机并没有使用引用计数算法来管理内存,主要是因为很难解决对象之间的循环引用问题。(虽然循环引用的问题可以通过Recycler算法解决,但是在多线程环境下,需要昂贵的同步操作来改变引用计数,性能低下。早期的编程语言都会使用这个算法.)例如:objectobjAobjB和objB都有一个字段instance,这样objA.instance=objB和objB.instance=objA,因为它们相互引用,它们的引用计数器不为0,所以引用计数器算法不能通知GC收集器回收它们。在可达性分析方法中,所有与GCRoots直接或间接关联的对象都是有效对象,没有与GCRoots关联的对象都是无效对象。GCRoots指的是Java虚拟机栈中引用的对象(战神中的局部变量表)。本地方法引用的对象的方法区常量引用的对象对象方法中静态属性引用的对象的GCRoots不包括堆中对象引用的对象,所以有不会有循环引用问题。引用的类型决定了对象是否存在或与“引用”相关。在JDK1.2之前,Java中的引用定义非常传统。一个对象只有被引用和不被引用两种状态。我们希望这样描述这个现象:当内存空间足够时,它会一直保存在内存中;如果垃圾回收后内存空间仍然很紧张,可以丢弃这些对象。许多系统的缓存功能复合了这样的使用场景。在JDK1.2之后,Java扩展了引用的概念,将其分为四种。不同的引用类型主要体现了对象不同可达状态和垃圾回收的影响。强引用(StrongReference)像Objectobj=newObject()这样的引用就是强引用。只要强引用存在,垃圾回收器就永远不会回收被引用的对象。但是,如果我们错误地保留了强引用,例如:给一个静态变量赋值,那么该对象将长时间得不到回收,就会发生内存泄漏。软引用(SoftReference)软引用是一种比强引用弱的引用,可以让对象免于某些垃圾收集器的攻击。只有当JVM认为内存不足时,才会尝试回收软引用指向的对象。JVM将确保在抛出OutOfMemeryError之前清除软引用指向的对象。软引用通常用于实现对内存敏感的缓存。如果还有空闲内存,可以暂时保留缓存,等内存不够用再清理。这样可以确保在使用缓存时不会耗尽内存。弱引用比软引用更弱。JVM在进行垃圾回收时,无论内存是否充足,只与弱引用关联的对象都会被回收。幻影引用(PhantomReference)幻影引用又叫幽灵引用或幻影引用,是最弱的一种引用关系。一个对象是否有虚引用根本不会影响它的生命周期。它只是提供了一种机制来确保对象在被终结之后做某些事情,例如,它通常被用来做所谓的事后清理机制。对于可达性分析中的不可达对象,回收堆中的无效对象并不是不可能的。判断是否需要finalize()执行JVM会判断是否需要对这个对象执行finalize()方法。如果对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,则视为“不需要执行”。那么这个对象基本上就真的被回收了。如果确定该对象需要执行finalize()方法,那么该对象会被放入一个F-Queue队列中,虚拟机会执行这些优先级比较低的finalize()方法,但不会确保将执行所有finalize()方法。如果finalize()方法中有耗时操作,虚拟机会直接停止指向这个方法。清除对象。对象重生或死亡如果在执行finalize()方法时将this赋值给一个引用,则该对象将重生。如果不是,那么它将被垃圾收集器清除。任何对象的finalize()方法只会被系统自动调用一次。如果这个对象面临下一次回收,它的finalize()方法就不会再执行了,如果你想继续在finalize()中保存自己,那也是无效的。回收方法区内存方法区存放类信息、常量、静态变量,生命周期长,每次垃圾回收时只清除少量垃圾。方法区需要清理的垃圾主要有两种:废弃常量和无用类决定废弃常量只要常量池中的常量没有被任何变量或对象引用,那么这些常量就会被清除。比如一个字符串“bingo”已经进入了常量池,但是当前系统在常量池中并没有引用这个“bingo”常量的String对象,也没有其他引用这个字面值。如有必要,“bingo”常量将从常量池中清除。判断无用类判断一个类是否为“无用类”,条件极其苛刻。该类的所有对象均已清除。加载这个类的ClassLoader已经被回收了。爪哇。一个类被虚拟机加载到方法区,那么堆中就会有一个代表这个类的对象:java.lang.Class。该对象在类加载到方法区时创建,在类从方法区删除时清除。在垃圾回收算法学会了如何判断无效对象、无用类和过时常量之后,剩下的工作就是回收这些垃圾。常见的垃圾回收算法有几种:mark-clear算法mark:遍历所有GCRoots,然后将GCRoots可达的所有对象标记为存活对象。【注意】被标记的对象为存活对象,不需要回收!!!清除:清除的过程会遍历堆中的所有对象,清除所有未标记的对象。同时清除被标记对象的标记,以便下次垃圾回收。这种方法有两个不足:效率问题:标记和清除两个过程的效率不高。空间问题:标记清除后会产生大量不连续的内存碎片。太多的碎片可能会导致以后需要分配大对象时无法找到足够的连续内存,不得不提前触发另一个垃圾回收动作。复制算法为了解决效率问题,出现了“复制”收集器算法。它根据容量将可用内存分成大小相等的两块,一次只使用其中一块。当这块内存用完需要进行垃圾回收时,将survivor的对象复制到另一块,然后将第一块内存全部清空。这个算法有优缺点:优点:不会有内存碎片的问题。缺点:内存减少到原来的一半,浪费空间。为了解决空间利用问题,可以将内存分为三个块:Eden/FromSurvivor/ToSurvivor,比例为8:1:1,每次使用Eden和Survivor之一。回收时,一次性将Eden和Survivor中的存活对象复制到另一个Survivor空间,最后清理掉刚刚使用过的Eden和Survivor空间。这样只会浪费10%的内存。但是,我们不能保证每次收集后存活的对象不超过10%。当Survivor空间不足时,我们需要依赖其他内存(指老年代)进行分配保证。AllocationGuarantee在为一个对象分配内存空间时,如果Eden+Survivor中的空闲区域不能容纳这个对象,就会触发MinorGC进行垃圾回收。但是如果MinorGC之后还有超过10%的对象还活着,存活的对象会通过分配保证机制直接进入老年代,然后在Eden区存放新的对象。Mark-organizationmethod(oldage)mark:它的第一阶段和mark-clear算法完全一样,都是遍历GCRoots,然后标记存活的对象。整理:移动所有存活的对象,并按照内存地址的顺序排列,然后回收结束内存地址后的所有内存。因此,第二阶段称为收尾阶段。这是一个老年代的垃圾回收算法。老年代的对象一般都有很长的生命周期,所以每次垃圾回收都会有大量的对象存活下来。如果使用复制算法,每次都需要复制大量的存活对象,效率很低。分代收集算法根据对象的生命周期将内存分成若干块。一般Java堆分为新生代和老年代,根据各个年代的特点采用最合适的收集算法。新生代:复制算法老年代:Mark-Sweep算法,Mark-Sort算法
