JVM内存模型简述
首先来看一个简单程序代码:
public class JVMModel { public void math(){ int a = 1; int b =2; int c = a+b; } public static void main(String[] args){ JVMModel jvmModel = new JVMModel(); jvmModel.math(); } }
其执行内存模型为下图所示:
下面结合上边程序和模型图进行JVM内存模型分析
1.程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
java虚拟机的多线程是通过线程流切换并分配CPU的时间片的方式实现的,因此在任何时刻一个处理器(如果是多核处理器,则只有一个核)都只会处理一个线程,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各线程之间计数器互相不影响,独立存储,因此这类内存区域为“线程私有”的内存。
由此可总结出程序计数器的作用;
(1)字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
(2)在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次执行位置。
*由于程序计数器是用来记录执行链地址的,所有生命周期随着线程是一致的,也就不存在OOM(OutOfMemoryError内存溢出)的区域了。
2.栈(线程栈)
java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是java方法执行的内存模型,java虚拟机栈是由一个个栈帧组成,线程在执行一个方法时,便会向栈中放入一个栈帧,每个栈帧都拥有局部变量表、操作数栈、动态链接、方法出口信息。局部变量表主要存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)和对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其它与此对象相关的位置)。
Java虚拟机会出现以下两种异常:
(1)StackOverFlowError:若Java虚拟机的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度,就会抛出StackOverFlowError;
(2)OutOfMemoryError:若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈的内存用完了,无法再动态扩展了,此时就会抛出OutOfMemoryError;
*Java虚拟机栈也是线程私有的,其生命周期是随着线程的创建而创建,随着线程死亡而死亡。
3.本地方法栈
本地方法栈的起源是由于java出现早期,java很多方法都是使用C编写的(Java是基于C发展的,过渡期),所以在JVM内存中针对这部分方法开辟了本地方法栈。
和虚拟机所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。在HotSpot虚拟机中和Java虚拟机栈合二为一。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该方法的局部变量表、操作数栈、动态链接、出口信息。
*本地方法栈同样也会出现StackOverFlowError和OutOfMemoryError异常
4.堆
堆是java虚拟机中所管理的内存中最大的一块,java堆是所有线程共享的一块区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。几乎所有的对象实例以及数组都在这里分配内存(目前由于编译器的优化,对象在堆上分配已经没有那么绝对了。
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:其中新生代又分为:Eden空间、From Survivor、To Survivor空间。进一步划分的目的是更好地回收内存,或者更快地分配内存。“分代回收”是基于这样一个事实:对象的生命周期不同,所以针对不同生命周期的对象可以采取不同的回收方式,以便提高回收效率。从内存分配的角度来看,线程共享的java堆中可能会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
如图所示,JVM内存主要由新生代、老年代、永久代构成。
① 新生代(Young Generation):大多数对象在新生代中被创建,其中很多对象的生命周期很短。每次新生代的垃圾回收(又称Minor GC)后只有少量对象存活,所以选用复制算法,只需要少量的复制成本就可以完成回收。
新生代内又分三个区:一个Eden区,两个Survivor区(一般而言),大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区的存活且不满足“晋升”条件的对象将被复制到另外一个Survivor区。对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代,这个过程也称为“晋升”。显然,“晋升年龄阈值”的大小直接影响着对象在新生代中的停留时间,在Serial和ParNew GC两种回收器中,“晋升年龄阈值”通过参数MaxTenuringThreshold设定,默认值为15。
② 老年代(Old Generation):在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代,该区域中对象存活率高。老年代的垃圾回收(又称Major GC)通常使用“标记-清理”或“标记-整理”算法。整堆包括新生代和老年代的垃圾回收称为Full GC(HotSpot VM里,除了CMS之外,其它能收集老年代的GC都会同时收集整个GC堆,包括新生代)。
③ 永久代(Perm Generation):主要存放元数据,例如Class、Method的元信息,与垃圾回收要回收的Java对象关系不大。相对于新生代和年老代来说,该区域的划分对垃圾回收影响比较小。
*在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。
5 方法区
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。
*相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。
6 运行时常量池
运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)
既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。
JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
7 直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致OutOfMemoryError异常出现。
JDK1.4中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它可以直接使用Native函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。
本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
原文地址:https://www.cnblogs.com/smallVampire/p/12572925.html
- C语言程序设计50例(三)(经典收藏)
- 实战:详解猫眼电影小程序开发过程
- C语言程序设计50例(二)(经典收藏)
- C语言程序设计50例(一)(经典收藏)
- 程序员面试之软件测试面试问答
- 微信小程序,开发大起底
- Netty粘包拆包解决方案
- 为什么要用 Node.js
- JavaScript定时器:setTimeout与setInterval 定时器与异步循环数组
- 深入理解javascript原型和闭包(1)——一切都是对象
- 程序员面试50题(2)—二元查找树的后序遍历结果[数据结构]
- 总结了一些指针易出错的常见问题(一)
- Eureka 服务上下线监控
- 程序员面试50题(1)—查找最小的k个元素[算法]
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Web 指纹识别之路
- RedisTemplate常用集合使用说明-opsForHash(四)
- Mybatis高级查询(二):多表联合查询
- Maven配置多仓库无效?来看看这篇文章
- Spring Boot启动slf4j提示找不到weblogic.xml日志异常
- Angular 应用的DevDependencies
- 轻量级服务网格 - osm
- 漫画:设计模式之 “外观模式”
- 使用 Zotero 在 Markdown 中优雅处理参考文献
- Mybatis plus自动生成业务代码
- 功能强大的CD工具 - flagger
- Swift defer
- MySQL 最佳实践:CPU 100%,MySQL 到底在干什么
- 【译】Flutter 1.20 发布
- 算法面试题:一个List,要求删除里面的男生,不用Linq和Lamda,求各种解,并说明优缺点!