您的位置:首页 > 教育 > 培训 > 上海设计公司排名榜_镇江门户网_阿亮seo技术_计算机培训

上海设计公司排名榜_镇江门户网_阿亮seo技术_计算机培训

2025/5/24 17:50:26 来源:https://blog.csdn.net/2301_78320637/article/details/142900288  浏览:    关键词:上海设计公司排名榜_镇江门户网_阿亮seo技术_计算机培训
上海设计公司排名榜_镇江门户网_阿亮seo技术_计算机培训

文章目录

  • 内存模型的基本概念
    • 案例
  • 程序计数器
    • `Java`虚拟机栈
      • 局部变量表
        • 栈帧中局部变量表的实际状态
        • 栈帧中存放的数据有哪些
      • 操作数栈
      • 帧数据
    • 本地方法栈
    • 堆空间是如何进行管理的?
  • 方法区
    • 静态变量存储
  • 直接内存
    • 直接内存的作用

内存模型的基本概念

在前面的学习中,我们知道了字节码文件(.class)会通过类加载器加载JVM虚拟机中,接下来JVM虚拟机就会执行其中的字节码指令.我们把JVM虚拟机被分配的内存叫做运行时数据区域
内存模型就是指运行时数据区域中被划分的不同区域.
JDK6版本:
在这里插入图片描述字符串常量池存放在方法区中,方法区存放在堆中;
JDK1.7版本:
在这里插入图片描述

  • 方法区脱离堆,单独占用一部分内存

  • 字符串常量池依旧存储在堆中
    JDK1.8版本:
    在这里插入图片描述

  • 方法区发生移动,从JVM虚拟机内存中,移动到本地内存中

Java虚拟机(JVM)的内存模型是Java程序运行时内存管理的基础。它定义了Java程序如何在内存中分配、使用和回收资源

案例

class Person{int id;String name;public Person(int id,String name){this.id=id;this.name=name;}
}
public class JvmTest {public void func1(int a){int b=10;Person p=new Person(1,"张三");a=11;}public static void main(String[] args) {int a=10;new JvmTest().func1(a);System.out.println(a);}
}

在这里插入图片描述

程序计数器

  1. 控制程序解释执行指令的顺序.在代码执行过程中,程序计数器会记录下一行字节码指令的地址.执行完当前指令之后,虚拟机的执行引擎根据程序计数器执行下一行指令.程序计数器可以控制程序指令的进行,实现分治,跳转或者异常等等逻辑
  2. 保证在多线程的情况下线程之间的切换.JVM虚拟机需要通过程序计数器记录CPU切换前解释执行到哪一行指令并继续解释运行

程序计数器会不会发生内存溢出?
内存溢出:程序在使用某一块内存区域时,存放的数据需要占用的内存大小超过了虚拟机能够提供的内存上限
程序计数器不会发生内存溢出的情况.原因:每个程序计数器只会存储一个固定长度的内存地址,也就是字节码指令的内存地址.

Java虚拟机栈

保存在java中实现的方法
采用的数据结构来管理方法调用中的基本数据,先进后出,每一个方法的调用使用一个栈帧来保存.
Java虚拟机栈随着线程的创建而创建,随着线程的结束而销毁
在每一个栈帧中,存放的内容有:局部变量表,操作数栈,帧数据
在这里插入图片描述

局部变量表

在方法执行过程中存放所有的局部变量,编译成字节码文件时,就可以确定局部变量表的内容
在这里插入图片描述

栈帧中局部变量表的实际状态

栈帧中的局部变量表是一个数组,数组中的每一个位置称之为,longdouble类型占用两个槽,其他类型占用一个槽.
在这里插入图片描述

栈帧中存放的数据有哪些
  • 实例方法中的序号为0的位置存放的是this,指的是当前调用方法的实例对象,运行时会在内存中存放实例对象的地址
  • 方法参数也会保存在局部变量表中,其顺序与方法中参数定义的顺序一致

局部变量表保存的内容有:

  • 实例方法的this对象
  • 方法的参数
  • 方法体声明的局部变量

为了节省空间,局部变量表中的槽是可以复用的,一旦某个局部变量不再生效,当前槽就可以再次被使用

操作数栈

操作数栈是栈帧中虚拟机执行指令过程中用来存放临时数据的一块区域

  • 操作数栈是栈帧中虚拟机在执行指令的过程中用来存放中间数据的一块区域.他是一种栈式的数据结构,如果一条指令将一个值压入操作数栈,则后面的指令可以弹出并使用该值.

比如:在字节码指令执行的过程中,会产生一些临时数据,这些临时数据会先存放到操作数栈中,然后再通过下一条指令放入到局部变量表中

  • 在编译期就可以确定操作数栈的最大深度,从而执行时正确的分配内存大小

帧数据

(这个并不是由虚拟机设定标准)
帧数据主要包含动态链接,方法出口,异常表引用

  • 动态链接:当前类的字节码指令引用了其他类的属性或者方法时,需要将符号引用(编号)转换成对应的运行时常量池中的内存地址.动态链接就保存了编号到运行时常量池的内存地址的映射关系.
    在这里插入图片描述

  • 方法出口:方法在正确或者异常结束时,当前栈帧会被弹出,同时程序计数器应该指向上一个栈帧中的下一条指令的地址.所以在当前栈帧中,需要存储此方法出口的地址.

  • 异常表:存放的是代码中异常的处理信息,包含了异常捕获的生效范围以及异常发生后跳转到的字节码指令位置.

Java虚拟机栈是否会出现栈内存溢出?

  • Java虚拟机栈如果栈帧过多,占用内存超过栈内存可以分配的最大容量,就会出现栈溢出(虚拟机为每一个线程分配的栈的大小是有限的)
  • Java虚拟机栈内存溢出时会出现StackOverflowError的错误

本地方法栈

保存的是在java中实现的使用native修饰的,实际是由C++编写的本地方法

  • Java虚拟机栈存储了Java方法调用时的栈帧,而本地方法栈存储的是native本地方法的栈帧.
  • HotSpot虚拟机中,Java虚拟机栈和本地方法栈实现上使用了同一个栈空间.本地方法栈在栈内存上生成一个栈帧,临时保存方法的参数同时方便出现异常时也把本地方法的栈信息打印出来.
    在这里插入图片描述

一般Java程序中堆内存是空间最大的一块内存区域.创建出来的对象都存在于堆上
Java 堆是所有线程共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都存放在这里,是垃圾收集器管理的主要区域
栈上的局部变量表中,可以存放堆上对象的引用.静态变量也可以存放堆对象的引用,通过静态变量就可以实现对象在线程之间的共享.
在这里插入图片描述

堆内存是否会出现溢出?
堆内存大小是有上限的,当对象一直向堆中放入对象达到上限之后,就会抛出OutOfMemory错误

堆空间是如何进行管理的?

堆空间有三个需要关注的值:uesd,total,max

  • used指的是当前已经使用的堆内存;
  • totaljava虚拟机已经分配可用堆内存;
  • maxjava虚拟机可以分配的最大堆的内存
    在这里插入图片描述

随着堆中对象增多,当total可以使用的内存即将不足的时候,虚拟机会继续分配内存给堆,扩展total的大小

  • 如果堆内存不足,java虚拟机就会不断地分配内存,total值会变大.
  • 是不是当used=total=max,就会导致内存溢出?
    答案:不一定

在实际应用中一般都需要设置totalmax的值

  • 要修改堆的大小,可以使用虚拟机参数-Xmx(max最大值)和-Xms(初始的total)
    • 语法:-Xmx值,-Xms
    • 单位:字节(默认,必须是1024倍数),K或者k(KB),m或者M(MB),g或者G(GB)
    • 限制:Xmx必须大于2MB,Xms必须大于1MB

Java服务端程序开发的时候,建议将-Xmx-Xms设置为相同的值(total=max),这样在程序启动之后可使用的总内存就是最大内存,而无需向Java虚拟机再次申请,减少了申请并分配内存时间上的开销,同时也不会出现内存过剩之后堆收缩的情况

方法区

方法区是存放每个类基础信息的位置,线程共享,主要包含三部分内容:

  • 类的元信息:每个类的基本信息
    一般称之为InstanceKlass对象.在类加载阶段完成
    在这里插入图片描述
  • 运行时常量池
    • 常量池中存放的是字节码中的常量池内容
    • 字节码文件中通过编号查表的方式找到常量,这种常量池称之为静态常量池
    • 常量池加载到内存中之后,可以通过内存地址快速的定位到常量池中的内容,这种常量池称之为运行时常量池.
      在这里插入图片描述
    • 字符串常量池:保存了字符串常量
      字符串常量池存储在代码中定义的常量字符串内容.比如:"123",这个123就会被放入字符串常量池
      在这里插入图片描述
      这里我们再来举个例子加深印象:
      举例1:
public class StringTest {public static void main(String[] args) {String a="1";String b="2";String c="12";String d=a+b;System.out.println(c==d);}
}

这里的运行结果就代表了我们的d是存放在字符创常量池中还是堆内存中.
运行结果false,原因如下:
在这里插入图片描述
举例2:

public class StringTest {public static void main(String[] args) {String a="1";String b="2";String c="12";String d="1"+"2";System.out.println(c==d);}
}

运行结果是:true,为什么?
我们来观察一下此时的字节码指令状态:
在这里插入图片描述
所以,此处d存放在常量池中的原因是:这里并没有使用StringBuilder对象来进行字符串的相加,而是直接使用的ldc字节码指令进行的,没有使用对象,所以就不需要存放在

静态变量存储

  • JDK7之前的版本中,静态变量是存放在方法区中的,也就是永久代
  • JDK7及其之后的版本中,静态变量时存放在堆中的Class对象中,脱离了永久代

直接内存

首先我们要确定的是,直接内存并不属于Java运行时的内存区域.
JDK1.4中引入了NIO机制,使用了直接内存,主要为了解决以下两个问题:

  • Java堆中的对象如果不再使用要回收,回收时会影响对象的创建和使用(可能会出现卡顿的现象)
  • IO操作比如读文件,需要先把文件读入直接内存中,再把数据复制到Java堆中.
    现在放入直接放入到直接内存中即可,同时在Java堆上维护直接内存的引用,减少了数据复制的开销.写文件也是这种思路.
    在这里插入图片描述

直接内存的作用

  • 提高读写文件的性能
  • 避免垃圾回收机制影响对象的创建和使用
  • 在`JDK1.78即之后存放方法区

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com