JVM知识内容很多。这里只描述JVM内存模型以及GC基本原理
参考文章:
JVM内存和垃圾回收
JVM内存模型
JVM内存模型
JVM所管理的内存也叫运行时数据区,分为下面几个子区域
- 方法区(线程共享),即我们常说的永久代(Permanent Generation), 用于存储被JVM加载的类信息、常量、静态变量
- 堆区(线程共享),几乎所有对象实例和数组都要在堆上分配,常分为新生代和老年代
- java栈区,又叫虚拟机栈(非线程共享),用于存储局部变量表、操作数栈、动态链接、方法出口
- 本地方法区(非线程共享),为Native方法服务,有些JVM把java栈和本地方法栈合二为一
- 程序计算区(非线程共享),类似于操作系统的PC,在线程切换时保存当前线程状态
JVM垃圾回收机制
对于堆中内存,在GC的角度,我们可以用图来直观表示:
堆区的内存分为新生代(young)和老年代(old),而新生区又分为 Eden、Servivor1、Servivor2, 两个Survivor 区相对地作为为From 和 To 逻辑区域, 当Servivor1作为 From 时 , Servivor2 就作为 To, 反之亦然。一般地,Eden:Servior:old,它们之间的内存配比为8:1:1
关于为什么要这样区分Young(将Young区分为Eden、Servivor1、Servivor2以及相对的From和To ),这要牵涉到JVM的垃圾回收算法的讨论。
1)因为引用计数法无法解决循环引用问题,JVM并没有采用这种算法来判断对象是否存活。
2)JVM一般采用GCRoots的方法,只要从任何一个GCRoots的对象可达,就是不被回收的对象
3)判断了对象生死,怎么进行内存的清理呢?答案是使用标记-清除算法,先标记那些要被回收的对象,然后进行清理,简单可行,但是①标记清除效率低,因为要一个一个标记和清除②造成大量不连续的内存碎片,空间碎片太多可能会导致当程序在以后的运行过程中需要分配较大对象的时候无法找到足够的连续内存而不得不触发另一次垃圾收集动作。
4)采用复制收集算法:将可用内存按照容量分为大小相等的两块,每次只是使用其中的一块。当这一块的内存用完了,就将可用内存中存活的对象复制到另一块上去,然后再将原来的内存空间清理掉。
5) 之所以要分两个Survivor,而不是直接从Survivor直接移到old区域,原因是old区域内的对象都是经过若干次yong GC之后存活下来的对象,并不是每一次yong GC存活下来的对象都需要移动到old区域内,所以需要Survivor1和Survivor2来保证Yong内存中的复制算法的实行,提高清除效率。
通过这样将内存区域分代,我们GC的效率更高,而这种GC的方法我们称之为分代收集算法
JVM类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被Java虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。类从被加载到虚拟内存中开始,到卸载内存为止,它的整个生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段。其中,验证,准备和解析三个部分统称为连接(Linking)。
双亲委派模型 :站在Java虚拟机的角度讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader。从Java开发人员的角度来看,类加载器还可以分得更细致一些,绝大部分Java程序都会使用到以下三种系统提供的类加载器:
启动类加载器
扩展类加载器
应用程序类加载器
类加载时机
- 实例化对象
- 调用类的静态方法
- 调用类的静态变量(放入常量池的常量除外)