前言:jvm是java的底层,是所有java框架的底层,凡事需深入现象看本质,知晓其原理才能更好的做上层开发。

一、jvm的体系结构

类装载器classloader:用来装载.class文件

执行引擎:执行字节码,或者执行本地方法

运行时数据区(这个就是本章讨论的重点):方法区、虚拟机栈、本地方法栈、堆、程序计数器  

二、运行时数据区

1、程序计数器:可以看成当前线程所执行的字节码的行号指示器( 线程私有)

2、 java虚拟机栈:存放基本数据类型、对象引用类型(线程私有)

3、本地方法栈:存放本地(native)方法相关信息

4、方法区:存放已被虚拟机加载的类型信息、常量、静态变量、及时编译器编译后额代码缓存等数据

5、堆:对象实例及数组

三、对象的内存布局

对象在堆内存的存储布局可以分为三个部分:对象头、实例数据、填充数据(保证对象大小都是8字节的整数倍)

Hotspot虚拟机对象的对象头中包括两类信息:第一类是用于存储对象自身的运行时的数据,如hashcode、gc分代年龄、锁状态标志、线程持有的锁、偏向线程id、偏向时间戳,这些被称为Mark Word,另一类是类型指针(通过其判断是哪个类的实例)

四、垃圾收集(GC)

显然,不能所有对象一直在内存中存在,这样不停有新对象,内存就会爆掉,所有需要进行GC,而在进行GC的时候,我们首先就需要判断哪些对象存活,哪些对象已死,下面就介绍常见的2种算法判断对象已死

1、引用计数算法(python)

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加一;当引用失效时,计数器就减一;任何时刻计数器为零的对象就是不可能再被使用的(这个算法看似很好,实则不然,当出现对象循环引用时就很难解决)

2、可达性分析(java、c#)

通过一系列称为"GC Roots"的根对象作为起始节点,根据其引用关系向下搜索,搜索过程所走过的路径被称为“引用链”,当一个对象到GC Roots之间没有任何引用链,可判定为可回收对象,不过这个时候不会直接回收掉,需进行第一次标记,随后再通过一次筛选,筛选的条件就是此对象是否有必要执行finalize(),加入对象没有覆盖finalize(),或者已经执行过一次,这认为此对象可以被回收了。

常见的GC Roots:当前或者的线程所引用的对象、方法区的静态变量、常量

3、引用的分类

引用分为以下四类:强引用、软引用、弱引用、虚引用

其中强引用就是我们普通的new对象,只要强引用关系还存在,就不会被回收

软引用,被软引用的对象在系统进行下下次GC则回收

弱引用,被弱引用的对象在系统进行下次GC则回收

虚引用,完全无影响对象生存时间,GC时会收到系统通知,就是一个对象存活的监控

五、垃圾收集算法

1、标记-清除算法

首先标记出需要回收的对象,在标记完成后,同一回收(显然会效率低、内存碎片化)

2、标记-复制算法

将可用内存划分为等大小2块,当一块快用完了,将其或者的对象按顺序移到另一块上(显然浪费空间)

3、标记-整理算法

首先标记所有可回收对象,然后将其存活的对象都放一起,其他位置清掉

4、分代收集算法

分代是基于把堆分成以下3个部分的

新生代:一般情况下,所有新生成的对象首先放在新生代,新生代又分为Eden区域和两个Survivor(survivor0、suirvivor1)区,大部分对象在Eden区生成

老年代:存放的都是些生命周期较长的对象,在新生代中经历了多次垃圾回收仍然存活的对象就放入老年代

元空间:存放静态文件,如java类、方法

新生代采用标记复制算法,老年代采用标记整理算法

六、经典垃圾收集器

1、Serial收集器

看其名字便知道其为单线程收集器,单线程收集器比较大的一个问题就是,会Stop the world,就是停掉正常工作的所有线程。

serial收集器中,对新生代采用标记复制算法,对老年代采用标记整理算法

2、ParNew收集器

多线程Serial收集器

3、CMS收集器

分四个步骤初始标记(STW)、并发标记、重新标记、并发清除(STW)

缺点:并发处理、导致应用程序变慢 且CMS收集器是基于标记清除的,会造成内存碎片化

4、G1收集器

大致分四个步骤初始标记、并发标记、最终标记、筛选回收

除并发标记外,其他步骤均STW

筛选回收:任意选择多个Region区进行GC,将一个Region区活着的对象放入空的Region区

G1通过把堆分成多个相对独立的区域,并行的选择性回收,兼顾STW与吞吐量,基于标记-复制算法

03-28 04:34