深入理解JVM之自动内存管理
1.内存划分
注:上图黄色框属于线程私有的内存,绿色为共有的。
- 程序计数器: 是一块较小的内存空间,可以看做当前线程所执行的字节码的行号指示器。
- 虚拟机栈(栈内存): 虚拟机栈描述的是Java方法执行的内存模型(非native方法)。每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
- 本地方法栈 : 与虚拟机栈的作用非常相似,区别是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法。
-
堆内存(线程共享): Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此区域的唯一目的就是存放对象实例。Java堆是GC的主要区域,因此很多时候也被称为GC堆(垃圾回收的主要场所)。
从内存分配的角度来看:线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。
从内存回收的角度来看:由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代,在细致一点的有Eden空间,From Survivor空间,To Survivor空间等。 - 方法区(线程共享): 是各个线程共享内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
1.1GC 什么时候开始?对什么东西,做了什么事情?
在什么时候:
-
新生代有一个Eden区和两个survivor区,首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放,如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中,然后清空Eden和之前的那个survivor区的内存。在某次GC过程中,如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去。
-
大对象以及长期存活的对象直接进入老年区。
-
当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析,如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次Full GC以尽可能地获得老年区的空间。
对什么东西: 从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象。
做什么:
- 新生代:复制清理;
- 老年代:标记-清除和标记-压缩算法;
- 永久代:存放Java中的类和加载类的类加载器本身。
2.垃圾收集算法有哪些?
引用计数算法 : 原理是此对象有一个引用计数器,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为 0 的对象。此算法最致命的是无法处理循环引用的问题;
可达性分析算法: 通过GC Roots的对象为起点,向下搜索,搜索过程所走过的路径称为“引用链”,能到达的对象为不可回收对象,不能到达的对象为需要回收的对象。Java中就是通过可达性分析算法来判定对象是否存活的。
如下图,a~h均为对象,通过GC Roots向下搜索,可以到达的对象为a、b、d、f,所以a、b、d、f为不可回收对象,其他的均为可回收对象。
固定可以作为GC Roots的对象如下几个:
- 虚拟机栈中的引用的对象 (栈内存中引用的对象);
- 方法区中类静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中的JNI(Native 方法中 JNI 引用的对象)。
- 所以被同步锁持有的对象
标记-清除算法: 此算法执行分“标记”和“清除”两阶段。首先标记出所有需要回收的对象,然后统一回收掉被标记的对象,也可以反过来标记存活的对象。主要有两个缺点:1.执行效率不稳定,如果java堆中包含大量对象(且大部分是需要被回收的),就会进行大量标记和清除动作,导致执行效率和对象数量成反比。第二个是内存空间碎片化。
标记-复制算法: 简称为复制算法,此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中;
此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现 “碎片” 问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间;
标记-整理算法: 此算法结合了 “标记-清除” 和 “复制” 两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象 “压缩” 到堆的其中一块,按顺序排放。此算法避免了 “标记-清除” 的碎片问题,同时也避免了 “复制” 算法的空间问题。移动存活对象也是有个缺点的,就是老年代存活的对象数量较大的话,需要暂停整个应用,称之为“Stop The World”。
3.内存泄漏和内存溢出
- 内存溢出指的是内存不够用了;
- 内存泄漏是指对象可达,但是没用了。即本该被 GC 回收的对象并没有被回收;
- 内存泄露是导致内存溢出的原因之一;内存泄露积累起来将导致内存溢出。
内存泄漏的原因分析:
- 长生命周期的对象引用短生命周期的对象;
- 没有将无用对象置为 null。