JVM运行时内存区域

JVM 管理的内存被划分为以下运行时数据区域:程序计数器、虚拟机栈、本地方法栈、堆、方法区。

alt text

这6块区域按是否被线程共享,可以分为二大类:

一类是每个线程所独享的:

  1. PC Register:也称为程序计数器, 记录每个线程当前执行的指令信息(eg:当前执行到哪一条指令,下一条该取哪条指令)

  2. JVM Stack: 也称为虚拟机栈,记录每个栈帧(Frame)中的局部变量、方法返回地址等。它的结构如下:

alt text

线程中每次有方法调用时,会创建Frame,方法调用结束时Frame销毁。

  1. Native Method Stack: 本地(原生)方法栈,顾名思义就是调用操作系统原生本地方法时,所需要的内存区域。

上述3类区域,生命周期与Thread相同,即:线程创建时,相应的内存区创建,线程销毁时,释放相应内存。

另一类是所有线程共享的:

  1. Heap:即鼎鼎大名的堆内存区,也是GC垃圾回收的主站场,用于存放类的实例对象及Arrays实例等。

注:Heap被所有线程共享,如果严格意义上抠字眼的话,也不完正确,事实上,由于TLAB的存在,为了防止并发对象分配时,多个对象分配到同1块内存,heap中的TLAB区域,在分配时,是被线程独占写入的。

  1. Method Area:方法区,主要存放类结构、类成员定义,static静态成员等。

  2. Runtime Constant Pool:运行时常量池,比如:字符串,int -128~127范围的值等,它是Method Area中的一部分。

Heap、Method Area 都是在虚拟机启动时创建,虚拟机退出时释放。

GC垃圾回收原理

判断一个对象是否该被回收

alt text

引用计数法,思路很简单,但是如果出现循环引用,即:A引用B,B又引用A,这种情况下就不好办了,所以JVM中使用了另一种称为“可达性分析”的判断方法:

alt text

还是刚才的循环引用问题,如果A引用B,B又引用A,这2个对象是否能被GC回收? 答案:关键不是在于A,B之间是否有引用,而是A,B是否可以一直向上追溯到GC Roots。如果与GC Roots没有关联,则会被回收,否则将继续存活。

alt text

上图是一个用“可达性分析”标记垃圾对象的示例图,灰色的对象表示不可达对象,将等待回收。

哪些内存区域需要GC

alt text

在上文中,我们知道了thread独享的区域:PC Regiester、JVM Stack、Native Method Stack,其生命周期都与线程相同(即:与线程共生死),所以无需GC。线程共享的Heap区、Method Area则是GC关注的重点对象。

常用的GC算法

  • mark-sweep 标记清除法:标记出来后直接清空。该方法很简单快速,但是缺点也很明显,会产生很多内存碎片。
  • mark-copy 标记复制法:将内存对半分,总是保留一块空着,将左侧存活的对象复制到右侧,然后左侧全部清空。避免了内存碎片问题,但是内存浪费很严重,相当于只能使用50%的内存。
  • mark-compact 标记-整理(也称标记-压缩)法:避免了上述二种算法的缺点,将垃圾对象清理掉后,同时将剩下的存活对象进行整理挪动(类似于windows的磁盘碎片整理),保证它们占用的空间连续,这样就避免了内存碎片问题,但是整理过程也会降低GC的效率。
  • generation-collect 分代收集算法:将内存分成了三大块: 年青代(Young Genaration),老年代(Old Generation),永久代(Permanent Generation),其中Young Genaration更是又细为分eden,S0, S1三个区。