JVM学习到这里,终于到学习最兴奋的地方了---垃圾回收,在学习它之前还得对JVM垃圾回收相关理论知识进行了解,然后再通过实践来加深对理论的理解,下面直接开始了解相关的理论:
JVM运行时内存数据区域:
这个在之前其实已经介绍过了,对于JVM的垃圾回收一定是回收内存里面的内容,所以如果不对内存区域的划分,区域存放的内容有所了解,那何谈垃圾回收呢?所以看一下下图对内存区域的划分描述:
其这以上区域在上一次【https://www.cnblogs.com/webor2006/p/10618362.html】都已经讲过了,下面再来整体回顾一下。
方法区域:
其中它是数据是线程共享的:
那所谓方法区域是线程共享的是指的啥意思呢?比如说一个类的class元信息就会映射到方法区域当中,那么这个元信息会被所有的线程所访问,因为只有一份,所以该区域是线程共享的。
下面再来阐述一下它:
- 存放了每个Class的结构信息,包括常量池、字段描述、方法描述。
- GC的非主要工作区域。
Java虚拟机栈(JVM Stack):
下面再对其进行一些阐述:
- Java虚拟机描述的是Java方法的执行模型:每个方法执行的时候都会创建一个帧(Frame),栈用于存放局部变量表,操作栈,动态链接,方法出口等信息。一个方法的执行过程,就是这个方法对于栈帧的入栈出栈过程。
- 它是线程隔离的,如图上所示。
本地方法栈:
堆:
它里面的数据也是线程共享的:
下面再来阐述一下它:
- 堆里存放的是对象的实例。
- 是Java虚拟机管理内存中最大的一块。
- GC主要的工作区域,为了高效的GC,会把堆细分更多的子区域。【这个在之后会细说】
- 它是线程共享的,如图所示。
程序计数器:
JVM运行时数据区域例子:
对于这样一个方法代码:
以上方法在执行之后在内存中发生的变化如下:
- 生成了2部分的内存区域:1、obj这个引用变量,因为是方法内的变量,放到JVM Stack里面;2、真正Object class的实例对象,放到Heap里面。
- 上述的new语句一共消耗12个bytes,JVM规定引用占4个bytes(在JVM Stack),而空对象是8个bytes(在Heap)。
- 方法结束后,对应Stack中的变量马上回收,但是Heap中的对象要等到GC来回收。
JVM垃圾回收(GC)模型:
垃圾判断算法:
- 引用计数算法(Reference Counting)
①、给对象添加一个引用计数器,当有一个地方引用它,计数器加1,当引用失效,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的,比较好理解。
②、引用计数算法无法解决对象循环引用的问题。啥意思,下面用图来说明一下:
JVM中存在A、B两个对象,而A、B是相互引用着的,也就是A里面持有B的引用,而B里面又持有A的引用,如下:
而开始这两对象是被其它地方所引用着的,比如方法栈中,如下:
而之后虚拟机栈的这两个引用消失了,也就是整个虚拟机中就只有这两个相互引用的对象了,而这两对象不被任何对象所引用着了:
而根据引用计数器的定义规则,A和B的引用计数器都是1,但是实际这俩都是孤立的对象,所以如果采用引用计数来进行垃圾回收,则这俩对象永远不会被回收。 - 根搜索算法(GC Roots Tracing)
既然引用计数算法存在对象循环引用的问题,所以此算法出现了,下面具体看下该算法:
①、在实际的生产语言中(Java、C#等),都是使用根搜索算法判断对象是否存活。
②、算法基本思路就是通过一系列的称为“GC Roots”的点作为超始进行向下搜索,当一个对象到GC Roots没有任何引用链(Reference Chain)相连,则证明此对象是不可用的。
③、在Java语言中,GC Roots包括【也就是什么是GC Roots?】:
a、在vm栈(帧中的本地变量)中的引用。
所以根据根搜索算法,由于目前没有栈中的引用指向A和B对象,所以这个算法规则这俩对象是可以被回收的了,如下:
b、方法区中的静态引用。
c、JNI(既一般说的Native方法)中的引用。
方法区:
- Java虚拟机规范表示可以不要求虚拟机在这区域实现GC,这区域GC的“性价比”一般比较低。
- 在堆中,尤其是在新生代,常规应用进行一次GC一般可以回收70%~80%的空间,而方法区的GC效率远小于此。
- 当前的商业JVM都有实现方法区的GC,主要是回收两部分内容:废弃常量与无用类。
- 类加收需要满足如下3个条件【条件是极其苛刻的】:
1、该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。
2、加载该类的ClassLoader已经被GC。
3、该类对应的java.lang.Class对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法。 - 在大量使用反射、动态代理、CGLib等字节码框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要JVM具备类卸载的支持以保证方法区不会溢出。
JVM常见GC算法:
- 标记-清除算法(Mark-Sweep)
- 标记-整理算法(Mark-Compact)
- 复制算法(Copying)
- 分代算法(Generational)
具体每个算法下次继续再来学习~~