①类加载检查→②分配内存→③初始化零值→④设置对象头→⑤执行init()方法
内存分配的两种方式:指针碰撞、空闲列表。
堆空间的基本结构:
上图所示的eden区、s0区、s1区都属于新生代,tentired区属于老年代。大部分情况,对象都首先在eden区分配,在一次新生代垃圾回收之后,如果对象依然存活,则会进入s0或者s1,并且对象年龄还会加1(Eden区->Survivor区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代,这个阈值可以通过参数-XX:MaxTenuringThreshold
来设置。
另外大对象和长期存活的对象会直接进入老年代。
大多数情况下,对象在新生代中eden区分配,当eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
JDK1.2之前,Java中引用的定义很传统:如果reference类型的数值存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)。
强引用(Strong Reference) 必不可少的生活用品
JDK1.2之前的引用,使用最普遍的引用。如果一个对象具有强引用,垃圾回收器绝不会回收它。
软引用(Soft Reference) 可有可无的生活用品
如果内存空间不够,垃圾回收器不会回收具有软引用的对象。只要垃圾回收器没有回收它,该对象就可以被程序引用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用(WeakReference) 更可有可无的生活用品
如果一个对象只具有弱引用,那么它相较于软引用拥有更短的生命周期。在垃圾回收器扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间存在与否,都会回收它的内存。不管垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列。
虚引用(Phantom Reference) 故名思义,虚引用形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
虚引用主要用来跟踪对象被垃圾回收的活动。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(Reference Queue)必须联合使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否需要垃圾回收。
特别注意:在程序设计中一般很少使用弱引用和虚引用,使用软引用的情况较多,这是因为软引用可以加速JVM对垃圾的回收速度,可以维护系统的运行安全,防止内存溢出等问题的产生。
运行时常量池主要回收的是废弃的常量。
以字符常量为例,如果该字符串没有被任何String对象引用,则说明该常量是废弃常量,这是如果发生内存回收而且有必要的话,该字符常量会被系统清理出常量池。
判定一个“无用的类”,需要满足以下三个条件:
虚拟机可以对满足上述3个条件的无用类进行回收,这里仅仅是说可以,而不是和对象一样不使用了就必然会被回收。
标记-清除算法:
算法分为标记和清除两个阶段:首先标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法。后续算法都是对其不足进行的改进。
存在的问题:
复制算法 为了解决效率问题,复制收集算法被提出。它将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完之后,就将还存活的对象复制到另一块,然后再把使用的空间一次性清理掉。
标记-整理算法 根据老年代的特点出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活对象向一端移动,然后直接清除掉边界以外的内存。
分代收集算法 当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同内存分为几块。一般将Java堆分为新生代和老年代这样就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以靠要选择复制算法,只需付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保。所以我们必须选择标记-清除或标记-整理算法。
即先分代,新生代采用复制算法,老年代选择标记-清除算法或者标记-整理算法。
收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。
至今为止没有最好的收集器出现,也没有万能的收集器,我们需要根据具体的应用场景选择适合自己的垃圾收集器。
Serial收集器 Serial(串行)收集器是最基本、历史最悠久的垃圾收集器。它是一个单线程收集器,这不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在垃圾收集工作的时候必须暂停其他所有的工作线程(“Stop The World”),直到它收集结束。
后续的垃圾收集器在设计中停顿的时间在不停缩短。
Serial收集器也有一些优于其他收集器的地方。它简单而高效(与其他收集器的单线程)相比。Serial收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial收集器对于运行在Client模式下的虚拟机来说是个不错的选择。
ParNew收集器 ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器完全一样。
它是运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器(真正意义上的并发收集器)配合工作。
这里的并行和并发概念:
Parallel Scavenge收集器 Parallel Scavenge收集器类似于ParNew收集器。
-XX:+UseParallelGC
使用Parallel收集器+老年代串行
-XX:+UseParallelOldGC
使用Parallel收集器+老年代并行
Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对收集器运作不太了解的话,手工优化存在的话可以选择吧内存管理优化交给虚拟机去完成也是一个不错的选择。
Serial Old收集器 Serial收集器的老年版本,它同样是一个单线程收集器。它主要有两大用途:一种是在JDK1.5以及之前版本中与Parallel Scavenge收集器搭配使用;另一种用途是作为CMS收集器的后备方案。
Parallel Old收集器 Parallel Scavenge收集器的老年代版本。使用多线程和标记-整理算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑Parallel Scavenge收集器和Parallel Old收集器。
CMS收集器 CMS(Concurren Mark Sweep)收集器是一种以获得最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
CMS收集器是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了垃圾收集线程与用户线程(基本上)同时工作。
从名字可以看出,CMS收集器是一种标记-清除算法实现的。它的运作福偶成相比于前面几种垃圾收集器来说更复杂一些。整个过程分为四步:
CMS收集器主要优点:
它有下面三个明显的缺点:
G1收集器 G1(Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量的特征。
被视为JDK1.7中HotSpot虚拟机的一个重要进化特征。它具备以下特点:
G1收集器的运作大致分为以下几个步骤:
G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,有限回收价值最大的Region(这就是名字Garbage-First的由来)。这种使用Region划分空间以及有优先级的区域回收方式,保证了G1收集器在有限时间内可以尽可能高的收集效率。
Java Guide面试突击版,百度可得最新版本,这里有删减、扩充和修正。