JVM内存模型

一:什么是JVM

  JVM又称为虚拟机,是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

                                                                                                          ------------来自 百度百科

 二:JVM的内存结构

内存结构是JVM中比较重要的存储结构,是硬盘和cpu之间的桥梁,JVM的内存结构规定了程序在运行过程中的内存的申请,分配,管理等一系列策略,保证了程序的高效有序地进行。因为本人的JDK为1.8版本,因此我去百度了一下Java8的内存结构图,如下:

 在Java8的内存结构图中,我们可以看出JVM内存主要由 堆,栈,程序计数器,方法区(元空间)等基本结构构成!下面,我们将详细看一下各部分的结构组成吧。

2.1Java堆(Java Heap)

Heap 堆:是JVM内存中最大的一块区域,由所有线程共享其资源,且是虚拟机中垃圾回收器主要管理的区域,由于堆中的资源是线程共享的,所以要考虑线程安全的问题。

在堆内存中主要放置以下资源:

1.实例对象:类初始化生成的对象,一般是由 new 关键字创建的对象,都放在堆内存中

2.线程分配缓冲区:线程私有但不影响堆的共性,可以提升对象分配的效率

3.字符串常量池:在Java1.7之前,字符串常量池是放在方法区(元空间)之中的,1.7之后为了提升字符串创建时的效率,将字符串常量池放在了堆内存中去了。

4.静态变量:由 static 关键字修饰这也与字符串常量池一样,在1.7之前也是放在方法区(元空间)之中的,在1.8之后也是放在了堆内存之中。

 内存溢出:new 出对象,循环添加字符数据,当堆中没有内存空间可分配给实例,也无法再扩展时,就会抛出 OutOfMemoryError 异常

补充:

在 Java7 中堆内会存在年轻代、老年代和方法区(永久代)

1.Young 区被划分为三部分,Eden 区和两个大小严格相同的 Survivor 区。Survivor 区某一时刻只有其中一个是被使用的,另外一个留做垃圾回收时复制对象。在 Eden 区变满的时候,GC 就会将存活的对象移到空闲的 Survivor 区间中,根据 JVM 的策略,在经过几次垃圾回收后,仍然存活于 Survivor 的对象将被移动到 Tenured 区间

2.Tenured 区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在 Young 复制转移一定的次数以后,对象就会被转移到 Tenured 区

3.Perm 代主要保存 Class、ClassLoader、静态变量、常量、编译后的代码,在 Java7 中堆内方法区会受到 GC 的管理

 我在刷牛客题目编程的时候,也会刷到一些有关JVM堆内存的问题,例如:

-Xms:代表什么,-Xmx:代表什么,-Xmn又代表什么。这些我们还要深入了解学习哦!

(-Xms1024m:代表最小堆;-Xmx1024m:代表最大堆;-Xmn512m:代表新生代)

2.2 Java栈(Java Stacks)

Java 虚拟机栈:Java Virtual Machine Stacks,每个线程运行时所需要的内存

虚拟机栈

1.每个方法被执行时,都会在虚拟机栈中创建一个栈帧 stack frame(一个方法一个栈帧

2.Java 虚拟机规范允许 Java 栈的大小是动态的或者是固定不变的

3.虚拟机栈是每个线程私有的,每个线程只能有一个活动栈帧,对应方法调用到执行完成的整个过程

4.每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存,每个栈帧中存储着:

5.局部变量表:存储方法里的 Java 基本数据类型以及对象的引用

6.动态链接:也叫指向运行时常量池的方法引用

7.方法返回地址:方法正常退出或者异常退出的定义

8.操作数栈或表达式栈和其他一些附加信息

虚拟机栈特点:

1.栈内存不需要进行GC,方法开始执行的时候会进栈,方法调用后自动弹栈,相当于清空了数据

2.栈内存分配越大越大,可用的线程数越少(内存越大,每个线程拥有的内存越大) 

方法内的局部变量是否线程安全

   2.1如果方法内局部变量没有逃离方法的作用访问,它是线程安全的(逃逸分析)

   2.2如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全

局部变量

局部变量表也被称之为局部变量数组或本地变量表,本质上定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量

1.表是建立在线程的栈上,是线程私有的数据,因此不存在数据安全问题

2.表的容量大小是在编译期确定的,保存在方法的 Code 属性的 maximum local variables 数据项中

3.表中的变量只在当前方法调用中有效,方法结束栈帧销毁,局部变量表也会随之销毁

4.表中的变量也是重要的垃圾回收根节点,只要被表中数据直接或间接引用的对象都不会被回收

 栈中存放的资源:

操作数栈

在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)或出栈(pop)

1.保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间,是执行引擎的一个工作区

2.Java 虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈

3.如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中

因此,在栈中主要存放方法调用和局部变量,并且栈中的资源数据都是私有的,不会被其他线程访问。

2.3程序计数器 (Program Counter Register

作用:内部保存字节码的行号,用于记录正在执行的字节码指令地址(如果正在执行的是本地方法则为空)

简单来讲就是记住下一条jvm指令的执行地址,并且是线程私有的。

原理:

1.JVM 对于多线程是通过线程轮流切换并且分配线程执行时间,一个处理器只会处理执行一个线程

2.切换线程需要从程序计数器中来回去到当前的线程上一次执行的行号

 2.4 方法区(元空间)

方法区:是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、即时编译器编译后的代码等数据,虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是也叫 Non-Heap(非堆)

方法区的大小不必是固定的,可以动态扩展,加载的类太多,可能导致永久代内存溢出 (OutOfMemoryError)

运行时常量池是方法区的一部分

1.常量池(编译器生成的字面量和符号引用)中的数据会在类加载的加载阶段放入运行时常量池

2.类在解析阶段将这些符号引用替换成直接引用

3.除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()

三:总结

是JVM中最大的一块内存区域,用于存储对象实例。一般通过new关键字创建的对象都存放在堆中,堆的大小可以通过启动参数进行调整。堆被所有线程共享,但是它的访问是线程不安全的,需要通过锁机制来保证线程安全。
用于存储方法调用和局部变量。每个线程在运行时都会有一个独立的栈,栈中的每个方法调用都会创建一个栈帧,栈帧包含了方法的参数、局部变量和返回值等信息。栈的大小是固定的,并且栈中的数据是线程私有的,不会被其他线程访问。
方法区用于存储类的信息和静态变量。它是所有线程共享的内存区域,存储了类的结构信息、常量池、静态变量和方法字节码等。方法区的大小也可以通过启动参数进行调整。
程序计数器是每个线程私有的,用于记录当前线程执行的字节码指令的地址。每个线程都有一个独立的程序计数器,用于控制线程的执行流程。
JVM内存模型的设计可以提供内存管理和线程安全的机制,同时也保证了Java程序的跨平台性。不同的内存区域有不同的作用和访问规则,合理地管理和利用这些内存区域可以提高Java程序的性能和稳定性。

最后附上Java1.7的内存模型图供大家参考学习

  • 29
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值