JVM内存模型

1 篇文章 0 订阅

JVM内存模型

1.JVM(Java Virtual Machine)

JVM(Java虚拟机)本质上就是一个程序,当它在命令行上启动的时候,就开始执行保存在某字节码文件中的指令。Java语言的可移植性正是建立在Java虚拟机的基础上。任何平台只要装有针对于该平台的Java虚拟机,字节码文件(.class)就可以在该平台上运行。这就是一次编译,多次运行

引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

其中对于不同的的操作系统的JVM都是一样的,但是底层和操作系统交互的部分是不一样,JRE中包含这些屏蔽底层操作系统差异的模块。

2.JMM(Java Memroy Model)

Java内存模型根据不同的角度可以分为如下两种:

  • 用于定义(所有线程的共享变量, 不能是局部变量)变量的访问规则 (线程的共享的角度)

  • Java运行时的内存模型(JVM运行时内存区域)

2.1线程共享角度下的JMM

按照线程共享的角度将JMM分为两块区域

  • 主内存:真实存放数据(变量),线程共享。
  • 工作内存:存放主内存中数据的副本,线程私有。

如下图所示:
在这里插入图片描述

  • 每个线程都只能访问自己工作内存中的数据,不能访问主内存和其它线程的中的数据

  • 不同线程之间的数据交互可以通过主内存间接完成。

线程和主内存之间的数据交互

单个线程的和主内存中的数据交互,如下图所示:
在这里插入图片描述

1.Lock:将主内存中的变量,表示为一条线程的独占状态

2.Read:将主内存中的变量,读取到工作内存中

3.Load:将2中读取的变量拷贝到变量副本中

4.Use:把工作内存中的变量副本,传递给线程去使用

5.Assign:把线程正在使用的变量,传递给工作内存中的变量副本中

6.Store:将工作内存中变量副本的值,传递到主内存中

7.Write:将变量副本作为一个主内存中的变量进行存储

8.Unlock:解决线程的独占状态

JVM要求以上八个步骤必须是原子性的,但是JVM对于double、long等64位(8个字节)数据类型有一些非原子性协议。也就意味着在写入double、long等64位数据的时候可能会出现只写入一半的情况,可以通过如下两个方案解决:

  • 使用商用JVM
  • 使用Volatile关键字修饰变量

2.2JVM运行时内存模型

JVM在运行时,将内存分为如下图所示的五个区域
在这里插入图片描述

程序计数器

程序计数器又称行号指示器,指向当前线程执行的字节码文件的指令地址。

1.一般情况下,程序计数器指向的值是当前线程执行字节码文件的行号,但是如果程序执行了native方法,那么指向的值为undefine。

2.程序计数器是JVM内存中唯一一个不会出现内存溢出的区域

PS:在Java有有一个保留的关键字goto,其本质就是改变程序计数器的行号。

虚拟机栈

定义: 描述方法执行的内存模型

  • 在方法执行的同时,会在虚拟机栈中为该方法创建一个栈帧
  • 栈帧:包含方法的局部变量表(方法中用到的局部变量)、操作数栈(变量的值)、动态链接(多态,引用与指向地址关系)、方法出口等信息。

当方法执行太多或者编写递归方法没有正确结束,虚拟机栈中存放的栈帧数量超过了内存大小,虚拟机栈就会内存溢出。

public static void main(String[] args) {
        main(new String[]{"a", "b", "c"});
    }

在这里插入图片描述

本地方法栈

结构基本上和虚拟机栈类似,不同的是,虚拟机栈存放的是JDK自带和自己编写的Java方法,本地方法栈存放的是底层的操作系统提供的方法

  • 用于存放对象实例(对象、数组)
  • 堆是JVM中内存最大的一块区域,在JVM启动的时候就已经创建完毕
  • GC(垃圾回收器)主要管理的区域
  • 堆本身是线程共享的,但是线程可以在堆中划分出多个线程私有的缓冲区(IO流)
  • 允许内存空间物理上的不连续,逻辑上连续(链表),这样会直接导致内存外碎片的产生
  • 堆中空间可以分为:新生代 和 老生代。大小比例 新生代 : 老生代 = 1:2
  • 新生代中分为: Eden、s0、s1,比例为8:1:1
  • 新生代的使用率一般在90%,在使用的时候s0和s1只能使用一块(内存复制算法,避免内存碎片)
  • 新生代:存放 1.生命周期比较短的对象 2.小的对象;反之,存放在老生代中。对象的大小,可以通过参数设置 -XX:PretenureSizeThredshold 。一般而言,大对象一般是 集合、数组、字符串。生命周期: -XX:MaxTenuringThredshold
  • 新生代、老生代中年龄:MinorGC回收新生代中的对象。如果Eden区中的对象在一次回收后仍然存活,就会被转移到 s区中;之后,如果MinorGC再次回收,已经在s区中的对象仍然存活,则年龄+1。如果年龄增长一定的数字,则对象会被转移到 老生代中。简言之:在新生代中的对象,每经过一次MinorGC,有三种可能:1从eden ->s区 2.(已经在s区中)年龄+1 3.转移到老生代中
    在这里插入图片描述

新生代在使用时,只能同时使用一个s区:底层采用的是复制算法,为了避免碎片产生

老生代: 1.生命周期比较长的对象 2.大的对象; 使用的回收器 MajorGC\FullGC

新生代特点:

  • 大部分对象都存在于新生代
  • 新生代的回收频率高、效率高

老生代特点:

  • 空间大、
  • 增长速度慢
  • 频率低

意义:可以根据项目中 对象大小的数量,设置新生代或老生代的空间容量,从提高GC的性能。

虚拟机参数

-Xms128m :JVM启动时的大小

-Xmn32m:新生代大小

-Xmx128:总大小

jvm总大小= 新生代 + 老生代

堆内存溢出示例

package cn.yu;

import java.util.ArrayList;

// 测试 堆内存溢出
//-Xms128m 虚拟机启动内存大小 -Xmn64m 新生代内存大小 -Xmx128m 总大小
public class TestHeap {

    public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<>();
        while (true){
            list.add(new int[1024 * 1024]);
        }
    }
}

在这里插入图片描述

方法区
  • 存放类的元数据信息(JDK1.7以前是存放在堆中的永久代)、常量池、方法信息(方法代码、数据)
  • GC主要回收方法区中类的元数据信息(类卸载的时候回收)、常量池中的数据(String类)

方法区和其它区域的内存联动
在这里插入图片描述
常量池: 存放编译时产生的字面量(String s = “abcd”)信息、符号引用(java.lang.String)

关于内存溢出

JVM中内存溢出会导致JVM退出,但是产生内存溢出不只是JVM虚拟机会产生,操作系统产生内存溢出,导致操作系统崩溃,也会间接的导致JVM退出,在NIO技术中会操作直接内存,也会导致内存溢出,操作系统就会终止程序。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小宇0926

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值