首页 > 系统相关 >Java内存区域

Java内存区域

时间:2023-10-14 19:32:29浏览次数:32  
标签:Java 虚拟机 计数器 区域 线程 内存 方法

图示

Java1.8以前

Java内存区域_JVM内存区域

JDK1.8:

Java内存区域_Java_02

说明

线程私有的:

  • 程序计数器
  • 机栈
  • 本地方法栈

线程共享的:

  • 方法区
  • 直接内存(非运行时数据区的一部分)
  • 程序计数器

程序计数器

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。

另外,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有"的内存。

从上面的介绍中我们知道程序计数器主要有两个作用:

1.字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如: 顺序执行、选择、循环、异常处理。

2.在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

注意: 程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。

Java 虚拟机栈

与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。

Java 内存可以粗糙的区分为堆内存(Heap) 和 内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。 (实际上,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)

局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、longdouble)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。

Java 虚拟机栈会出现两种异常: StackOverFlowError 和 OutfMemoryError。

StackOverFlowError: 若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。OutfMemoryError: 若Java 拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了无法再动态扩展了,此时抛出OutOfMemoryError异常。

Java 虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。

扩展: 那么方法/函数如何调用?

Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈,每一次函数调用都会有一个对应的栈帧被压入Java栈,每一个函数调用结束后,都会有一个栈顿被弹出。

Java方法有两种返回方式:

1. return 语句。

2.抛出异常。

不管哪种返回方式都会导致栈帧被弹出。


本地方法栈

和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机为虚拟机执行 Jaa 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。在 HotSpot 虚拟机中和Java 虚拟机栈合二为一。

本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。

方法执行完毕后相应的栈顿也会出栈并释放内存空间,也会出现 StackOverFlowError 和OutOfMMemoryError 两和异常。

Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap) .从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为: 新生代和老年代:再细致一点有: Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。

Java内存区域_Java_03

上图所示的 eden区、s0区、s1区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 S0 或者 S1,并且对象的年龄还会加 1(Eden区->Survivor 区后,对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

方法区

方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap (非堆),目的应该是与Java 堆区分开来。

方法区也被称为永久代。很多人都会分不清方法区和永久代的关系,为此我也查阅了文献。

方法区和永久代的关系

《Java虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的JVM 上方法区的实现肯定是不同的了。 方法区和永久代的关系很像Java中接口和类的关系,类实现了接口,而永久代就是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式。也就是说,永久代是HotSpot的概念,方法区是Java虚拟机规范中的定义,是一种规范,而永久代是种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久带这一说法。

常用参数

JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小

-XX:Permsize=N //方法区(永久代)初始大小-XX:MaxPermsize=N //方法区(永久代)最大大小,超过这个值将会抛出0ut0fMemoryError异常:java. lang.OutOfMemoryError: PermGen

相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在"了。"*

JDK 1.8 的时候,方法区(HotSpot的永久代)被彻底移除了 (JDK1.7就已经开始了),取而代之是元空间,元空间使用的是直接内存。

下面是一些常用参数 :

-XX:MetaspaceSize=N //设置Metaspace的初始 (和最小大小)
-XX:MaxMetaspaceSize=N //设置Metaspace的最大大小

与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。

为什么要将永久代(PermGen)替换为元空间(MetaSpace)呢?

整个永久代有一个JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会得到java.lang.OutOfMemoryError 。你可以使用 -XX :MaxMetaspaceSize 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。-XX: MetaspaceSize 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。

当然这只是其中一个原因,还有很多底层的原因,这里就不提了

运行时常量池

运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息 (用于存放编译期生成的各种字面量和符号引用)

既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。

IDK1.7及之后版本的JVM 已经将运行时常量池从方法区中移了出来,在Java 堆(Heap) 中开了一块区域存放运行时常量池。

Java内存区域_Java_04

直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。

JDK1.4 中新加入的 (NIO New Input/Output) 类,引入了一种基于通道 (Channel) 与缓存区(Buffer)的I/ 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。

本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

标签:Java,虚拟机,计数器,区域,线程,内存,方法
From: https://blog.51cto.com/u_15590807/7862935

相关文章

  • Java基础-初识JDBC
    目录1.JDBC简介2.JDBC项目3.JDBC的导入4.JDBC的使用内容JDBC简介什么是JDBCJDBC的全称是Java数据库连接(JavaDatabaseconnect),它是一套用于执行SQL语句的JavaAPI。应用程序可通过这套API连接到关系数据库,并使用SQL语句来完成对数据库中数据的查询、更新和删除等......
  • 手撕Vue-提取元素到内存
    接着上一篇文章,我们已经实现了构建Vue实例的过程,接下来我们要实现的是提取元素到内存。主要是通过文档碎片来实现,文档碎片是一个轻量级的文档,可以包含和控制节点,但是不会像真实的DOM那样占用内存,所以我们可以通过文档碎片来提高性能。大致的思路是这样的:创建一个空的文档碎片......
  • 设置不同区域编辑权限
    问题:为不同区域设置不同权限解决:审阅》允许编辑区域》新建》设置区域与密码》重复新建多个区域》保护工作表......
  • java基础——枚举类
     枚举枚举对应英文(enumeration,简写enum)。枚举是一组常量的集合。可以这样理解:枚举是一种特殊的类,里面只包含一组有限的特定的对象。枚举的两种实现方式自定义类实现枚举使用enum关键字实现枚举自定义实现枚举不需要提供setXxx方法,因为枚举对象值通常为制度。对枚举对象/属性使用f......
  • Mac上使用jenv管理多个java版本
    Mac上可以使用Homebrew安装jenvbrewinstalljenv配置jenv的环境变量,向~/.profile(bash用户)或者~/.zshrc(zsh用户)文件中添加如下代码exportPATH="$HOME/.jenv/bin:$PATH"eval"$(jenvinit-)"使用如下命令添加java_home的路径到jenvjenvadd/Library/Java/JavaVirtual......
  • Java NIO 中的 Buffer、Channel 和 Selector:高效的非阻塞 IO
    在Java中,标准的IO操作使用阻塞模式,这意味着每个IO操作都会阻塞当前线程直到操作完成。而JavaNIO(NewIO)提供了一种基于事件驱动的非阻塞IO模型,通过三大组件——Buffer(缓冲区)、Channel(通道)和Selector(选择器),可以实现更高效的IO操作。本文将详细介绍和说明这三大组件的......
  • [JavaScript]arguments对象
    当我们不确定有多少个参数传递的时候,可以使用arguments来获取。在JavaScript中,arguments实际上是充当函数的内置对象。所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有实参。arguments展示形式是一个伪数组,可以进行遍历。伪数组具有以下特点:1.......
  • javaSE基础06(final,常用类,基本数据类型的封装)
    Final关键字(用法:类不想被人继承 ):修饰类:最终的类,不可以被继承 比如String;修饰方法:方法不可以被重写;修饰属性:不可以被重新赋值,但是可以改变变量里的内容;publicclassStudent{Stringname;publicstaticvoidmain(String[]args){finalS......
  • Java程序的main主线程的运行过程
    在Java虚拟机进程中,执行程序代码的任务是由线程来完成的。每当用java命令启动一个Java虚拟机进程,Java虚拟机就会创建并启动一个main主线程,该线程从程序入口main()方法开始执行。main主线程执行main()方法下面以例程1的Sample为例,介绍线程的运行过程。例程1 Sample.javapublicclas......
  • Java零基础入门-基本类型转换、包装类、自动装箱、自动拆箱
    前言在Java编程中,经常需要进行基本类型之间的转换以及包装类与基本类型之间的转换。本文将介绍Java中基本类型转换、包装类、自动装箱与自动拆箱的概念和用法。摘要本文将介绍Java中的基本类型转换、包装类、自动装箱与自动拆箱的概念和应用。首先,我们将讨论基本类型转换的概念......