前段时间在面试复习的时候,看了些 jvm 相关的文章,自己做一些整理。
内存模型
JVM 的内存区域总共分为5大块,包括:方法区、堆区、程序计数器、虚拟机栈、本地方法栈;如图所示:
JVM 主要区域概要
堆 Heap
该区域主要用来存放所有的对象和数组。
方法区
存储已被虚拟机加载过的 类信息、常量、静态变量、即时编译的代码等数据,为线程共享。
程序计数器
当前线程所执行的字节码的行号指示器,每个线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。
虚拟机栈
线程私有,它是 Java 方法执行的内存模型,每一个方法从调用到执行完成的过程,就会有一个栈帧在虚拟机栈中从入栈到出栈的过程。栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,
本地方法栈
虚拟机执行 Native 方法时使用,HotSpot 虚拟机的本地方法栈和虚拟机栈合二为一。
内存溢出
内存泄漏
发生内存泄漏的根本原因是:长生命周期的对象持有短生命周期对象的引用就有可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。下面是几类常见的场景:
静态集合类引起的内存泄漏
像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
当集合里面的对象属性被修改后,再调用 remove() 方法时不起作用
各种连接
必须及时的调用相应的 close()方法,否则不会触发 GC 回收。
内部类和外部模块的引用
单例模式
单例对象的生命周期和应用的生命周期一致,如何单例持有外部的引用,那么这个对象就无法被 JVM 正常回收,导致内存泄漏。
内存回收机制
1.1 回收算法
标记回收算法(Mark and Sweep GC)
从”Gc Roots” 集合开始,将内存整个遍历一遍,保留所有可以被 Gc Roots 直接或者间接引用到的对象,而剩下的对象都当作垃圾对待并回收。这个算法需要中断进程内其他组件的执行并且可能产生内存碎片
复制算法 (Copying)
将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
标记-压缩算法(Mark-Compact)
先需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。
分代算法
所有的新建对象都放入称为年轻代的内存区域,年轻代的特点是对象会很快回收,因此,在年轻代就选择效率较高的复制算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老生代的内存空间。对于新生代适用于复制算法,而对于老年代则采取标记-压缩算法。
简单总结一下:
简单梳理一下文中讲到的一些知识点
- 为了更好的管理堆内存,该区域分为新生代和老年代。新生代发生垃圾回收要比老年代频繁。
- 新生代发生的垃圾回收成为 Minor GC;老年代发生的 GC 成为 Full GC。
- 新生代使用[复制算法]进行垃圾回收;老年代使用[标记-压缩算法]
- 为了更高效管理新生代的内存,按照复制算法,结合 IBM 的研究论证,新生代分为三块,一块比较大的 Eden 区和两块比较小的 Survivor 区,比例为 8:1:1
- 尽可能的避免或者减少垃圾回收。