赞
踩
JVM就是一个Java进程,Java进程会从操作系统这里申请一大块内存空间,给Java代码来使用。
而这一大块内存空间又可以进一步细分为以下几个最核心的内存区域:
这块的主要考点就是给一段代码,判断某个变量处于内存中的哪个位置,需要注意是的,我们是通过变量的形态(成员变量,局部变量,静态变量)来判断,而不是通过变量的类型来判断。
例如下面的例子:
void func() {
Test t = new Test();
}
接下来我们就系统的介绍一下JVM运行时数据区,也叫JVM的内存布局。按照 Java 的虚拟机规范,可以细分为程序计数器、虚拟机栈、本地方法栈、堆、方法区等,如下图:
其中堆和方法区在一个JVM进程中只有一份,而栈(虚拟机栈和本地方法栈)和程序计数器则是有多份的,每一个线程有一份。
.class 文件需要加载到虚拟机中之后才能运行和使用,类加载的过程就是将.class文件加载到内存中得到类对象的过程。其中类加载的过程分为五步,即加载→验证→准备→解析→初始化,如下图所示:
加载阶段就是查找并加载类的二进制数据(.class文件),在加载阶段,JVM做三件事情:
这一阶段的目的是确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。验证包括了:文件格式验证(二进制文件)、字节码验证,符号引用验证
在准备阶段,JVM会给类对象分配内存空间并设置初始值,初始值为数据类型的默认初始值,如0,0L,false,null等。
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。
其中符号引用就是字符串常量,它在.class文件中已经存在了,但它们之间只知道彼此的相对位置(偏移量),并不知道自己在内存中的实际位置,而当真正加载到内存中的时候,就会把字符串常量填充到特定的位置上,字符串常量之间的相对位置还是一样的,但此时它们有了实际的内存地址,此时的字符串常量就是直接引用了(Java中的普通引用)。
初始化阶段就是针对类对象进行初始化(初始化静态成员,执行静态代码块,如果该类还有父类还需加载它的父类)。
JVM 启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载。也就是说,大部分类在具体用到的时候才会去加载,这样对内存更加友好。
那么什么是需要的时候呢?
在介绍双亲委派模型之前,我们需要先知道JVM加载类时需要用到一组特殊的模块,即类加载器。
类加载器(ClassLoader)用于动态加载 Java 类到 Java 虚拟机中。主要有四种类加载器:
启动类加载器(Bootstrap ClassLoader)负责加载Java标准库中的类。
扩展类加载器(Extension ClassLoader):负责加载一些非标准的但是Sum/Oracle扩展的类
应用程序类加载器(Application ClassLoader):负责加载项目中自己写的类以及第三方库中的类。
用户自定义类加载器 (User-Defined ClassLoader),我们可以通过继承java.lang.ClassLoader类来创建自己的类加载器。
在上面我们介绍了JVM类加载的过程,而这就不得不提到一个重要的考点了——双亲委派模型。
它发生在第一步加载中找.class文件的时候,是 Java 类加载机制中的一个重要概念。这种模型指的是一个类加载器在尝试加载某个类时,首先会将加载任务委托给其父类加载器去完成。只有当父类加载器无法完成这个加载请求(即它找不到指定的类)时,子类加载器才会尝试自己去加载这个类。 如下图所示:
垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存爆掉。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
而垃圾回收之前我们需要先清楚谁是垃圾,谁不是垃圾,即GC分为了两步,先是找谁是垃圾,再对垃圾进行释放。
常用的垃圾判断算法有引用计数算法,可达性分析算法。
引用计数法:给对象增加一个引用计数器,每当有一个地方引用该对象时,计数器+1,当引用失效时,计数器-1,任何时候计数器为 0 的对象就是不可能再被使用的。
优点:实现简单,效率高
缺点:无法解决循环引用的问题
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,即从 GC Roots 到该对象节点不可达,则证明该对象是需要垃圾收集的。
可以作为GC Roots的对象:
在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是进行垃圾回收,如何高效地进行垃圾回收呢?
标记清除算法,分为 2 部分,先把内存区域中的这些对象进行标记,哪些属于可回收的标记出来,然后把这些垃圾拎出来清理掉。
优点:实现简单。
缺点:产生大量不连续的内存碎片。当我们想申请一块连续的较大的内存时,可能就申请不到。
在标记清除算法上演化而来的,用于解决标记清除算法的内存碎片问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
优点:执行效率高,没有内存碎片的问题。
缺点:空间利用率低,因为复制算法每次只能使用一半的内存。
标记整理算法,标记过程仍然与标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。
优点:解决了内存碎片问题,比复制算法空间利用率高。
缺点:因为有局部对象移动,所以效率不是很高。
当前虚拟机的垃圾收集都采用分代收集算法,是根据对象存活周期的不同将内存分为几块。一般将 Java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
由于新生代存放的大部分数据是朝生夕死的,所以新生代使用的是效率最高的复制算法;而老生代使用的是标记-清除或标记-整理算法,如果标记-清除可以满足需要那么就使用效率更好的标记-清除算法,如果标记-清除算法不能满足需要就使用标记-整理算法。
今天的分享到这里就结束了,感谢支持!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。