JVM 内存模型和GC回收机制

83 阅读4分钟

当一个Hello.java文件被JVM加载到内存中的过程

  1. Hello.java 首先通过编译器编译,生成Hello.class
  2. Hello.class 被类加载器(ClassLoader)加载到JVM内存当中
  3. JVM内存可以划分为以下几种:程序计数器、方法区、虚拟机栈、本地方法栈、堆这五个

JVM内存模型

接下来详细说一下这五个区域都是存放什么的:

程序计数器

线程私有的,主要用于记录当前线程执行某一个方法时具体的地址,以便当其他线程获取时间片后,再次回到当前线程时能够继续执行。

虚拟机栈

线程私有的,每一个方法执行时,都会在虚拟机栈中插入一个栈帧,虚拟机栈中会存在多个栈帧,当方法执行结束后,栈帧就会移除,每一个栈帧内部都是由:局部变量表、操作数栈、动态链接、返回地址四个,分别看看是什么作用

1.局部变量表

方法内部的局部变量都会存放在局部变量表中。

2.操作数栈

当执行iadd 等操作时,都是会将局部变量表中的变量添加到操作数栈中,然后执行完操作后再重新赋值到局部变量表中

3.动态链接

当方法内部执行其他方法或者其他类中的方法,动态链接就是保存其方法的引用。

4.返回地址

通常用于返回方法的返回值

本地方法栈

存放native层的方法

方法区

存放类信息(版本、字段、方法、接口)、静态变量、常量

堆(Heap)

堆是内存中最大的一块内存,主要存放的是对象实例,同时,堆内存还分为新生代和老年代,新生代还分为Eden区和S0区、S1区,比例通常是8:1:1 新出生的对象一般放在Eden区,如果对象的大小超过了Edon的剩余大小,会直接放在老年代。 当Eden区存满后,会进行垃圾回收,然后将存活的对象保存到S0区,然后等第二次Eden区存满后,会将Eden和S0区的垃圾对象清除,然后将Eden和S0中存活的对象保存到S1区,就这样S0和S1反复交换15次之后,会移动到老年代。通常就是存活时间较长的对象保存到老年代

异常情况

StackOverFlowError 栈溢出,通常是方法循环调用导致,当每执行一个方法时,都会生成一个栈帧,而方法也不会退出,所以栈帧会一直创建,导致栈溢出。

OutOfMemorryError 一般都是堆内存溢出,当不断的创建对象时,堆内存会随之增加,而不及时清除时,会导致堆内存溢出。

GC回收机制

JVM内存中,程序计数器、虚拟机栈、本地方法栈随线程创建而生,随线程销毁而销毁。栈帧随方法进入而创建,方法退出而销毁。所以GC回收机制通常是堆内存中的实例对象。

什么是垃圾

可达性分析

选择一个GC Root作为起点,然后沿着根节点往下一直查找,所经过的路径作为链路,没有在链路上的对象就是认定为没有对象在引用他,可以清除。

GC Root

哪些可以被当作是GC Root 呢:

  1. 局部变量表中的变量
  2. 静态常量
  3. 存活的线程对象

什么时候回收

两种情况会触发,来清除堆内存中的实例对象,分别是

  1. Allocation Failure:在堆内存分配对象时,发现内存中没有剩余空间而分配失败时,系统会自动执行一次GC
  2. System.gc() 主动调用方法来寻求系统执行GC

如何回收垃圾

标记清除算法
  1. Mack标记阶段:将GC Root仍在引用的对象进行标记,其他的标记为黑色
  2. Sweap清除阶段:将黑色的对象直接清除。 这个算法缺点是:会产生内存碎片
标记压缩算法
  1. Mack标记阶段:将GC Root仍在引用的对象进行标记,其他的标记为黑色
  2. Compact压缩阶段:将存活的对象压缩到内存的某一端
复制算法

复制前先将内存一分为二,先使用内存A,然后,发生GC时,将还存活的对象复制到内存B中,然后清空内存A。 此算法通常实现在新生代,存活率不高的对象

再谈引用

  1. 强引用 new出来的对象都是强引用
  2. 弱引用 WeakReference wr = new WeakReference<>(); 第一次GC时就会移除
  3. 软引用 SoftReference sr = new SoftReference<>(); 当内存满的时候会对软引用进行移除
  4. 虚引用 不会使用