Java面试必问知识点之三--垃圾收集器与内存分配策略
垃圾收集器与内存分配策略
* 哪些内存需要回收
程序计数器,虚拟机栈,本地方法栈都是线程私有,随线程而生,线程而灭; 这几个区域的内存分配和回收都是确定。而java堆和方法区是线程共享的区域,这部分内存的分配和回收都是动态的,垃圾收集器主要关注这部份的内存区域。
* 什么时候回收
当对象“死亡”时会被垃圾收集器回收
判断对象是否存活的方法:
- 引用计数法
给对象添加一个引用计数器,每当对象被引用时,计数器数值加1;每当引用失效时,计数器数值-1。当数值为0时对象则不能被引用,判断为死亡需要被垃圾收集器回收。该方法有个弊端是很难解决对象之间相互循环引用,java虚拟机则没有使用该方法。
- 可达性分析法
可达性分析法是指,通过一些列称之为“GC Roots” 的对象作为起点;从此起点向下搜索,所走过的路径称之为引用链,当一个对象到 GC Roots 没有任何引用链相连接,代表此对象不可达。
如下图所示:
Java 可以作为GC Roots 的对象包括:
- 虚拟机栈(帧栈中的本地变量表)中的引用对象。
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI (即一般说的 Native 方法) 的引用对象
在谈引用:
java中把内存中对象的引用分成了四个等级,在不同程度下进行垃圾的回收工作。按照回收优先级顺序分为:虚引用 -> 弱引用 -> 软引用 -> 强引用
* 如何回收
- 标记-清除算法
最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。
- 复制算法
为了解决Mark-Sweep算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉。
- 标记-整理算法
结合了以上两个算法,为了避免缺陷而提出。标记阶段和Mark-Sweep算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
- 分代收集算法
分代收集法是目前大部分JVM所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将GC堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
目前大部分JVM的GC对于新生代都采取复制算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照1:1来划分新生代。一般将新生代划分为一块较大的Eden空间和两个较小的Survivor空间(From Space, To Space),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将该两块空间中还存活的对象复制到另一块Survivor空间中。
而老生代因为每次只回收少量对象,因而采用Mark-Compact算法。另外,不要忘记在Java虚拟机(JVM)中提到过的处于方法区的永生代(Permanet Generation)。它用来存储class类,常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。
对象的内存分配主要在新生代的Eden Space和Survivor Space的From Space(Survivor目前存放对象的那一块),少数情况会直接分配到老生代。当新生代的Eden Space和From Space空间不足时就会发生一次GC,进行GC后,Eden Space和From Space区的存活对象会被挪到To Space,然后将Eden Space和From Space进行清理。如果To Space无法足够存储某个对象,则将这个对象存储到老生代。在进行GC后,使用的便是Eden Space和To Space了,如此反复循环。当对象在Survivor区躲过一次GC后,其年龄就会+1。默认情况下年龄到达15(虚拟机默认15)的对象会被移到老生代中。
* G1收集器的特点
- 并行并发,利用多CPU多核的环境下,来缩短Stop-The-World的停顿时间
- 分代收集
- 空间整合
- 可预测的停顿
* 大对象直接分配到老年代
所谓大对象是指,需要大量连续内存空间的java对象,最典的大对象就是很长的字符串以及数组,大对象对虚拟机的分配来说就是个坏消息,所以在开发程序的时候应该尽量避免。
文章有帮助你,请关注微信公众号:肆意游离 有更多精彩等着你