Skip to content

Latest commit

 

History

History
58 lines (37 loc) · 5.6 KB

Java内存模型以及GC原理.md

File metadata and controls

58 lines (37 loc) · 5.6 KB

Java GC原理

背景

JAVA GC是可以帮助我们回收系统中无用的内存,这件事情Java内部已经替我们做了,但是这个不是我们说我们可以完全不了解GC内部原理的,因为如果我们随意使用JAVA内存,或者使用不当的话,可能会导致内存泄漏,或者系统进行频繁的GC这样会导致我们的应用卡顿,或者出现OOM的问题。本文的主要目的就是让我们更加了解内存GC的原理,编写出更加高效的代码,使我们的应用更加的稳定。

JVM内存管理

JVM内存区域

  1. 方法区 所有线程共享,用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据。方法区还有另一个名字叫做非堆,用于和Java堆进行区分,方法区一般被成为永久代,一般来说这个区域很少进行垃圾回收,其上的GC主要针对常量池的回收和已加载类的卸载。在方法区上进行GC,条件相当苛刻而且困难。 运行时常量池:用来存储编译器产生的常量和引用。
  2. 堆区 被所有线程共享,在虚拟机启动的时候创建,用来存放对象实例,记录所有对象的实例都在这里分配,对于大多数应用来说,堆是虚拟机所属管理的内存中最大的一块。 Java堆是垃圾收集器管理的主要区域,因此很多时候也称为GC堆,如果从内存回收的角度来看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为新生代和老年代,新生代由三部分组成:Eden空间,From Survivor控件,To Survivor控件三部分组成。Java堆不需要连续的内存,并且可以通过动态增加内存,增加失败会抛出OutOfMemoryError异常。
  3. 虚拟机栈 线程私有的,它的生命周期与线程相同 虚拟机栈描述的是Java方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表,操作栈,动态链接,方法出口等信息,每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 局部变量表存放了从编译期可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double),
  4. 本地方法栈 与虚拟机栈非常相似,区别是虚拟机栈为虚拟机执行的Java方法,也就是字节码服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。
  5. 程序计数器 线程私有,它的生命周期与线程的生命周期相同 可以看作是当前线程所执行的字节码的信号指示器 如果正在执行的是一个Java方法,这个计数器记录的是正在执行虚拟机字节码指令的地址,如果正在执行的是Native方法,这个计数器的值则为空 程序计数器所占用的空间大小不会随着程序执行而发生改变,所以这个区域是不会发生OutOfMemoryError

GC的查找算法

引用计数发 为每个对象,添加引用计数器,每被引用一次,计数器便加一,失去引用计数器就减一,在计数器在一段时间都为0的时候,便可以将它回收了。不过可能产生相互引用的情况,就会导致永远也不会回收。

可达性分析法 从一个GC Root的根结点出发,向下搜索,如果一个对象不能从GC Root结点出发到达之后,就说明该对象已经不再被引用了,便可以回收了。

a.该类的所有实例都已经被回收; b.加载该类的ClassLoad已经被回收; c.该类对应的反射类java.lang.Class对象没有被任何地方引用。

内存分区

内存区域主要分为三块,新生代,旧生代,持久代。新生代比较适合生命周期较短,快速创建和销毁的对象,旧生代比较适合生命周期较长的对象,持久代在虚拟机中就是指方法区(有一些JVM中根本没有持久代的概念)

新生代 内部分为Eden区和Survivor区,Survivor区又分为大小相同的两部分:FromSpace和ToSpace。新建的对象是从新生代分配内存,当Eden区不足的时候,会把存活的对象转移到Survivor区。当新生代进行垃圾回收时会发出Minor GC,(也称为Youn GC)。

旧生代 旧生代用于存放新生代多次回收依然存活的对象,如缓存对象,当旧生代满了的时候,会对旧生代进行回收,旧生代的垃圾回收称为Major GC(也称Full GC)

GC回收算法

  1. 复制-清除 从根结点进行扫描,将活的对象移动到一块空闲的区域,这样复制完之后,就可以完全释放另一块区域的所有内容,当对象存活的对象较少时,复制算法会比较高效,带来的成本就是需要一块额外的空闲空间和对象的移动。

  2. 标记-清除 该方法采用的方式是从根集合开始扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间,将未被标记的对象进行清除。

  3. 标记-压缩 该算法与标记-清除算法类似,都是先对存活的对象进行标记,但是在清除后会把活的对象向左端空闲空间移动,然后在更新引用对象的指针

由于进行了移动规整动作,该算法避免了标记-清除的碎片问题,但由于需要进行移动,因此成本也增加了。(该算法适用于旧生代)