点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
导语:公众号后续将推出酒后系列技术文,全是干货,希望不要喝醉哦!
为什么要写这么一篇文章呢,因为很基础,很基础,很基础,重要的事情说三遍!!!哼,还不是因为面试的时候经常会被问到,哈哈哈哈哈????,为此我静下心来,狠心写下了这一篇文章,好去对付一般的面试官,也可以帮助你更好的去理解,这是一场恶战,让我们磨好枪准备开干吧。
先让我们看一看这张图,相信大家也看过很多次了,这一次让我们不醉不归????,干杯!
(红框为线程共享的数据区、篮框为线程隔离的数据区)
Java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域各有各的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。
看上图我们就清楚运行时数据区分为以下几个部分:
一、????让我们干一杯吧!第一杯敬 程序计数器
程序计数器时一块儿较小的内存空间,它可以看作时当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。它是线程私有的,每个线程都需要一个独立的程序计数器。
如果线程执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;
如果正在执行的是一个Native方法,计数器值为空(Undefined)。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError异常的区域
二、????再喝完这杯还有三杯!!!【Java虚拟机栈】
线程私有,生命周期与线程相同。
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
如果虚拟机栈可以动态扩展,扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常;
三、????第三杯【本地方法栈】
本地方法栈与虚拟机栈的作用时相似的,它们之间的区别是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务;本地方法栈则为虚拟机使用到的Native方法服务;本地方法栈也会抛出StackOverflowError异常和OutOfMemoryError异常。
四、????喝完这一杯,真的只有最后一杯【Java堆】
所有线程共享的一块内存区域,在虚拟机启动时创建。此内存对象的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的主要区域,因此也可以称之为“GC”堆(Garbage Collected Heap),或者是“垃圾堆”,这句话是开玩笑的。现在的垃圾收集器都采用的是分代收集算法,所以Java堆细分为了:新生代和老年代(这部分放在后面在详解);无论如何划分区域,都与存放内容无关,无论哪个区域,存储的都是对象实例,是为了更好的回收内存,或者说是更快的分配内存。
Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间,只要逻辑上是连续的即可。在实现时,既可以实现成固定大小的,也可以实现成可扩展的,当前主流的虚拟机都是按照可扩展的来实现的(通过-Xmx和Xms控制)。如果在堆中没有足够内存完成实例分配,并且堆也无法再扩展时,将会抛OutOfMemoryError异常。
光说没用,直接上才艺!
代码演示堆内存的使用大小,运行原理(在Run as ->Run Configurations中输入"-XX:+PrintGCDetails"可以查看堆内存运行原理图)
public class JVMTest {
public static void main(String[] args) {
long maxMemory = Runtime.getRuntime().maxMemory(); //Java虚拟机试图使用的最大内存
long totalMemory = Runtime.getRuntime().totalMemory(); //Java虚拟机中的内存总量
System.out.println("MAX_MEMORY = " + maxMemory + "(字节)" + (maxMemory / 1024 / 1024) + "MB");
System.out.println("TOTOL_MEMORY = " + totalMemory + "(字节)" + (totalMemory / 1024 / 1024) + "MB");
}
}
运行后的结果如下图所示:
当然也可以通过设置参数触发垃圾回收(在Run as ->Run Configurations中输入设置“-XX:+PrintGCDetails”可以参看垃圾回收机制原理)
public class JVMTest {
public static void main(String[] args) {
long maxMemory = Runtime.getRuntime().maxMemory(); //Java虚拟机试图使用的最大内存
long totalMemory = Runtime.getRuntime().totalMemory(); //Java虚拟机中的内存总量
System.out.println("MAX_MEMORY = " + maxMemory + "(字节)" + (maxMemory / 1024 / 1024) + "MB");
System.out.println("TOTOL_MEMORY = " + totalMemory + "(字节)" + (totalMemory / 1024 / 1024) + "MB");
String str = "hello world, JVM!";
while (true) {
str += str + new Random().nextInt(88888888) + new Random().nextInt(99999999);
}
}
}
运行如下图所示:
五、????最后一杯,干了我们是好朋友【方法区】
线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
方法区也不要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。大多在HotSpot虚拟机上开发的程序员也会把方法区称之为“永久代”,是因为垃圾收集扩展至了方法区,但并不是说数据进入了方法区(永久代)中,就如“永久代”的名字 一样永久存在了,只是相对来说垃圾收集在这个区域时比较少出现的。这区域的内存回收目标主要是针对常量池的回收和堆类型的卸载,一般来说,这个区域的回收结果比较难以令人满意,可能会导致内存泄漏,但是这个区域的回收还是必要的。
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
运行时常量
池属于方法区中的一部分。class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法去的运行时常量池中存放。
运行时常量池相对于class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译器才能产生,运行期间也可以将新的常量放入池中,这种特性被开发人员利用的比较多的是String类的intern()方法。
运行时常量池时方法区的一部分,自然会受到方法区的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。
干了这五杯酒之后,上图就变成了以下这个样子
至此,我们5杯酒就喝完了,想必很多同学已经上头了吧,微醺刚刚好,拿去干面试官去!如果同学们还有点儿晕,不要紧,让我们以后继续喝,必须把你的量给练上去,以后碰到哪个面试官都不怕。
有热门推荐????
分享几种优雅停止Spring Boot方法,不止kill -9
干货分享:公众号后台回复“99”领取99套实战项目+资料
想充电就关注程序员闪充宝
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)