面试专题(四):JVM的内存布局和垃圾回收机制

JVM的内存布局和垃圾回收机制如下图

面试专题(四):JVM的内存布局和垃圾回收机制_第1张图片

当我们运行java程序的时候虚拟机并不会把程序所需要的所有数据一次性加载到内存(运行时数据区)中,它会按照数据的性质进行分类,这也就形成了运行时数据区,按照数据的性质,这些区域可以分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。

程序计数器可以理解为IDEA给我们的代码行号(如下图), 虚拟机运行我们的程序的时候,例如当它执行到第31行代码的时候,它要知道下一条要执行的语句在第几行,这就是程序计数器最大的作用,它就是一个代码行号指示器。
面试专题(四):JVM的内存布局和垃圾回收机制_第2张图片

Java虚拟机栈:每个方法在被调用时就会创建一个栈帧,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
例如:如下现在有a、b两个方法,在a中调用了b方法。虚拟机在执行a方法的时候首先会在虚拟机栈中把a方法打包成一个栈帧放到虚拟机栈中去执行,当执行到调用b方法这一句的时候它又会把b方法打包成一个栈帧投放到虚拟机栈中执行,这个时候就虚拟机开始执行b方法包含的程序代码,当b方法执行完成之后,b方法形成的栈帧就会从虚拟机栈中出栈(这也就回应了上面所说“每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程”),然后虚拟机继续执行a方法中调用b方法之后的程序代码。

public void a(){
	b();
}
public void b(){
}

Java堆:是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,对象实例在这里分配内存。是垃圾收集器(GC)管理的主要区域。
平时在代码中使用关键字new出了一个实例,这个实例就在虚拟机的堆上进行内存分配。

方法区:存储已被虚拟 机加载的类信息(类继承了哪些父类,这个类实现了哪些接口等等)、常量、静态变量(static)、即时编译器编译后的代码等数据,运行时常量池(Runtime Constant Pool,在jdk1.6及以前方法区中还包含运行时常量池)是方法区的一部分。

直接内存:直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。往往用在与io通讯相关的程序里面,并不是每一个java程序都需要使用直接内存的,但是因为直接内存可以被虚拟机所管理,而且有参数可以调节直接内存的大小,所以在学习虚拟机内存布局的时候往往也会把直接内存纳入进来。

虚拟机内存布局在jdk1.6~1.8中有一些调整

在jdk1.6中运行时常量池是放在方法区中,在jdk1.7中把运行时常量池放在堆中,在jdk1.8及以后就把方法区从运行时数据区中移除了,取而代之的是“元空间”,元空间中保存的数据就是原方法区中保存的数据,但是元空间是在运行时数据区之外从本地内存中单独分配的一块区域。
面试专题(四):JVM的内存布局和垃圾回收机制_第3张图片

虚拟机的垃圾回收机制

最开始提出的垃圾回收算法:标记-清除算法(Mark-Sweep)
面试专题(四):JVM的内存布局和垃圾回收机制_第4张图片
要回收就把要回收的部分进行标记(如上图黑色的部分),要回收的时候就把标记的部分一次性回收,这种回收带来的问题是“内存碎片”,内存不规则(可以看看清除后的图片), 假如一个绿色的矩形代表1K大小的内存,现在要存一个8k的数据,虽然绿色的矩形全部加起来是超过8K的,但是并没有一个连续的8k的内存可以供我存储数据。

为了解决内存碎片问题又提出了一个垃圾回收算法:复制算法(Copying)
面试专题(四):JVM的内存布局和垃圾回收机制_第5张图片
复制算法的基本思想是把内存区域分为两个部分,每次只用一半(如上图的内存整理前的“保留内存”和其他),先用左边,内存区域不足要进行垃圾回收,把左边中存活的对象依次挪到保留内存中按顺序摆好,摆好之后一次性将左边的内存回收(这样就形成了上图的内存中整理后那样子), 这样右边的数据也很规整。这个算法的弱点也非常明显–存在空间的浪费,在任何时候总有一半的数据区域是没有使用上的。

后来又提出了一种垃圾回收算法:标记-整理算法(Mark-Compact)
面试专题(四):JVM的内存布局和垃圾回收机制_第6张图片
在回收之前先对要会收的内存区域做一个标记(图中黄色标记), 要实际进行回收的时候把存活对象移动,按照某种顺序在内存中放好,放好之后把需要回收的内存一次性回收掉,这个算法存在一个问题就是内存区域块的移动,对性能有一定的影响。

后来虚拟机在实现垃圾回收算法的时候又提出了:分代收集
面试专题(四):JVM的内存布局和垃圾回收机制_第7张图片
因为大部分的对象在使用完之后是可以马上回收的,虚拟机就把容纳这部分对象称为新生代,新生代统一采用复制算法,新生代中活过一定次数的对象就会升级到老年代,老年代有可能采用标记清除算法,也可能采用标记整理算法,视乎垃圾回收器的不同而定。在进行垃圾回收的时候,垃圾回收的线程可以是一个也可以是多个,使用一个线程的称为串行,使用多个线程则称为并行式垃圾回收。在jdk最新的版本里面又提出了一个G1,G1里同样有新生代和老年代的区分,但是它和早期的垃圾回收算法最大的区别在于早期的垃圾回收算法中内存区域是在逻辑上分成新生代和老年代,但是在G1中内存区域区分的没那么严格,它把内存划分为一个个的小块,每个小块可能寄给新生代用也可能给老年代用(新生代上依然是使用了复制算法,老年代采用的是标记清除算法)。把内存区域划分得更详细,G1相对于早期的垃圾回收算法来讲,性能上有了不小提升。

你可能感兴趣的:(面试)