1.3、Java内存模型
内存模型设计:由两个部分的数据非常关键,堆内存和方法区,因为这两个区域的存放的是所有线程共享的数据,因此数据会比较多。而线程私有的数据区如程序计数器,本地方法栈、虚拟机栈里面的数据跟线程的生命周期有关,一旦线程被销毁,这些数据也就被回收了,因此不用过多关注。
2、垃圾回收算法
========
2.1、如何判断某个对象是否是垃圾?
我们先来了解一个最基本的问题:如果确定某个对象是“垃圾”?既然垃圾收集器的任务是回收垃圾对象所占的空间供新的对象使用,那么垃圾收集器如何确定某个对象是“垃圾”?———即通过什么方法判断一个对象可以被回收了。
public class Main {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.object = object2;
object2.object = object1;
object1 = null;
object2 = null;
}
}
class MyObject{
public Object object = null;
}
最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数都不为0,那么垃圾收集器就永远不会回收它们。
为了解决这个问题,在Java中采取了 可达性分析法。该方法的基本思想是通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。
Java垃圾回收-可达性分析算法
也可以看:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) 周志明 3.2章节
回收算法有3种:
2.2、标记-清除
通过可达性分析的方式来标记需要回收的对象。
清除后:
缺点:
- 堆中所有的对象都会被扫描一遍,从而才能确定需要回收的对象,比较耗时
- 有空间碎片,空间不连续,导致一些打的对象可能分配不到合适的空间,这样就还会触发GC回收,而GC回收是比较耗时的
2.3、标记-复制
清除后:
优点:不会有空间碎片;
缺点:浪费了一半的空间;
2.4、标记-整理
整理后:
堆:Old Young(Eden、S0和S1)
哪个算法用在哪个区域呢?
不同的代用不同的垃圾回收算法
Young区,复制 —> 前提条件:每次垃圾回收 存活的对象都比较少 —> 复制算法
绝大多数的对象都被回收掉了 —> 一般的对象都是朝生夕死的
垃圾回收的过程,GC日志 文章名:Understanding G1 GC Logs https://blogs.oracle.com/poonam/understanding-g1-gc-logs
结论:Young区使用复制算法是没有问题的。
Old区:一般是存活时间比较长的,意味着很难被回收 —>
当需要要回收的对象很少时,使用标记整理算法(只标记,不清除);
当需要回收的对象很多时,使用标记清除算法;
总结:
- 1、新生代: 新生代中的对象存活率低,只要付出少量的复制成本就能完成回收过程,因此选用复制算法;
- 2、老生代: 老生代中的对象存活率高,并且没有额外空间进行分配担保,因此选用 “标记 - 清理” 或 “标记 - 整理” 算法。
3、面试题
=====
1、简单讲一下Java的内存模型?
2、Java 的 GC机制
- GC有多少种垃圾回收的算法?
- 你听说过 年轻代、老年代吗?
- 复制算法、标记清除 有了解过吗?
3、Java内存管理:常见的变量,在内存的不同区域,GC的回收;
- 我在new一个对象的时候,它是被放在内存的那一块区域;
- 成员变量和局部变量:在内存区域位置的区别和回收方面的区别;
3.1、简单讲一下Java内存模型【垃圾回收相关】。
JVM的运行时数据区包括线程私有(程序计数器、本地方法栈、虚拟机栈)和线程共享(堆、方法区)。
而内存模型的设计主要考虑线程共享区域,因为这两个区域存放的是所有线程共享的数据,因此数据会比较多,而线程私有的数据跟线程的生命周期有关,一旦线程被销毁,这些数据也就被回收了,因此不用过多关注。
而线程共享区域包含有堆和方法区,方法区也不用关注,因为方法区里面的内存分配和垃圾回收我们无法控制,那么我们只有一个区域需要关注了,那就是堆。
堆可分为老年代和新生代,新生代又分为Eden空间、From Survivor空间、To Survivor空间。(空间比例为8:1:1)。
3.2、简单讲一下新生代和老年代。
堆可分为老年代和新生代,新生代又分为Eden空间、From Survivor空间、To Survivor空间。(空间比例为8:1:1)。
**1)年轻代(Young Gen):**年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden Space和2个Suvivor Space(命名为A和B)。当对象在堆创建时,将进入年轻代的Eden Space。垃圾回收器进行垃圾回收时,扫描Eden Space和A Suvivor Space,如果对象仍然存活,则复制到B Suvivor Space,如果B Suvivor Space已经满,则复制到Old Gen。同时,在扫描Suvivor Space时,如果对象已经经过了几次的扫描仍然存活,JVM认为其为一个持久化对象,则将其移到Old Gen。扫描完毕后,JVM将Eden Space和A Suvivor Space清空,然后交换A和B的角色(即下次垃圾回收时会扫描Eden Space和B Suvivor Space。这么做主要是为了减少内存碎片的产生。
我们可以看到:Young Gen垃圾回收时,采用将存活对象复制到到空的Suvivor Space的方式来确保尽量不存在内存碎片,采用空间换时间的方式来加速内存中不再被持有的对象尽快能够得到回收。
Old Gen。同时,在扫描Suvivor Space时,如果对象已经经过了几次的扫描仍然存活,JVM认为其为一个持久化对象,则将其移到Old Gen。扫描完毕后,JVM将Eden Space和A Suvivor Space清空,然后交换A和B的角色(即下次垃圾回收时会扫描Eden Space和B Suvivor Space。这么做主要是为了减少内存碎片的产生。
我们可以看到:Young Gen垃圾回收时,采用将存活对象复制到到空的Suvivor Space的方式来确保尽量不存在内存碎片,采用空间换时间的方式来加速内存中不再被持有的对象尽快能够得到回收。