一、前言
JVM是Java virtual machine 的缩写,简称:java虚拟机。它是整个java实现跨平台的最核心的部分,所有的java程序会首先被编译为.class的类文件,而解释和执行这种跨平台的.class文件的就是java虚拟机。
二、简介
而java与其他编程语言(c/c++)等,最大的不同之处也是优势之处:java不需要开发人员自己编写代码来释放和回收内存。而把这项任务交由java虚拟机自行处理,就是通常我们所说的GC回收。gc回收的工作在java里是由jvm自行负责的,编写和开发jdk的伟大工程师们把其底层所有的工作处理逻辑都安排的非常完美。GC回收机制,通常又涉及运行时内存区、GC算法、GC收集器等。
三、运行时内存区
java运行时数据区,简单概括包括了:方法区、堆、虚拟机栈、本地方法栈和程序计数器等。详细的各个区域的原理和讲解参考另一篇文章
- 方法区:是线程共享的内存区域,用来存储类加载的信息、常量、静态变量、即时编译器编译后的代码等。其中方法区中还有个经常会用到的区域叫做运行时常量池,主要用于存储一些常量,当创建一个常量时,首先会在运行时常量池查看是否有,有则直接使用,否则重新创建。
- 堆:堆是最大的一块内存区域,也是垃圾回收管理的主要区域,主要用于存放对象实例。
- 虚拟机栈:虚拟机栈主要存储基本数据类型变量和引用类型变量。其中与堆的区别就是如:Obj obj=new Object();等号左边则是在虚拟机栈上分配栈区存储引用类型变量的句柄Obj obj,等号右边则是存储对象实例,栈区的句柄是指向堆区的对象实例的,一般通过句柄访问堆区的对象实例。
- 本地方法栈:与虚拟机栈意义相似,区别在于虚拟机栈用于使Java方法,而本地方法栈则是针对于Native方法服务。
- 程序计时器:线程私有的,每个线程都会分配一个线程计时器,用来表示当前线程执行的字节码的行号指示器。在多线程中,一个线程执行的时候释放锁,另一个线程执行完,再回来执行前面线程的时候,就是通过程序计时器来获取继续执行的位置。
四、内存分配和回收策略
下图所示是堆中内存分配示意图,创建一个对象,首先会在eden区域分配区域,如果内存不够,就会将年龄大的转移到Survivor区,当survivor区域存储不下,则会转移年老代的。对于一些静态变量不需要使用对象,直接调用的,则会被放入永生代。一般来说长期存活的对象最终会被存放到年老代,还有一种特殊情况也会被存放到年老代,就是创建大对象时,比如数据这种需要申请连续空间的,如果空间比较大的,则会直接进入年老代。
在回收过程中,有一个参数比较重要,就是对象的年龄,如果在一次垃圾回收过程中有使用该对象的,则将对象年龄加1,否则减1,当计数为0,则进行回收,如果年龄达到一定数字则进入老生代。总的来说内存分配机制主要体现在对象创建之后是否仍在使用,已经不使用的则回收,继续使用的则对其年龄进行更新,达到一定程度,转移到年老代。
五、GC回收算法
1.标记-清除算法
该算法先标记,后清除,将所有需要回收的算法进行标记,然后清除;这种算法的缺点是:效率比较低;标记清除后会出现大量不连续的内存碎片,这些碎片太多可能会使存储大对象会触发GC回收,造成内存浪费以及时间的消耗。
2.复制算法
复制算法将可用的内存分成两份,每次使用其中一块,当这块回收之后把未回收的复制到另一块内存中,然后把使用的清除。这种算法运行简单,解决了标记-清除算法的碎片问题,但是这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工作,效率比较低。
3.标记整理算法
标记整理算法是针对复制算法在对象存活率较高时持续复制导致效率较低的缺点进行改进的,该算法是在标记-清除算法基础上,不直接清理,而是使存活对象往一端游走,然后清除一端边界以外的内存,这样既可以避免不连续空间出现,还可以避免对象存活率较高时的持续复制。这种算法可以避免100%对象存活的极端状况,因此老年代不能直接使用该算法。
4.分代收集算法
分代收集算法就是目前虚拟机使用的回收算法,它解决了标记整理不适用于老年代的问题,将内存分为各个年代,在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率搞,没有额外空间对它进行分配担保,所以只能使用标记清除或者标记整理算法。
六、垃圾收集器
- Serial收集器
jdk1.3之前唯一新生代收集唯一选择,最基本、发展历史最悠久的收集器,单线程的收集器;当它进行垃圾回收的时候,必须暂停其他所有工作的线程直至它的垃圾收集工作结束Stop The World“”。
- parNew收集器
parNew是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为均与Serial相同。Server模式下的虚拟机中首选的新生代收集器,目前唯一能与CMS收集器配合工作的。
- parallel scavenge收集器
parallel scavenge是一个新生代收集器,使用了复制的算法;并行多线程的收集器。它的目标是达到一个可控制的吞吐量(吞吐量 = 运行代码时间 / 垃圾收集时间)。一般来说,吞吐量越高,标明程序性能越好;
- serial old收集器
Serial Old是Serial收集器的老年代版本,也是单线程收集器。使用“标记—整理”算法,作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。
- parallel old收集器
parallel old是parallel scavenge收集器的老年代版本,使用多线程和“标记—整理”算法。jdk1.6才开始提供。
- cms收集器
cms收集器是一种以获取最短回收停顿时间为目标的收集器,多集中用于互联网网站或B/S架构的服务端上,重视服务的响应速度,希望系统停顿时间短,用户体验好。使用“标记—清除”算法:
初始标记
并发标记
重新标记
并发清除
- g1收集器
g1收集器是当今收集器技术最前沿的成果之一,jdk1.7的重要进化特征。面向服务端的垃圾收集器;它有如下特征:
并行与并发、分代收集、空间整合、可预测的停顿。
初始标记
并发标记
最终标记
筛选回收