轻松get JVM——Java运行时的数据区域

本文深入探讨Java内存区域管理,包括程序计数器、虚拟机栈、本地方法栈、Java堆、方法区、运行时常量池及直接内存的运作原理。特别关注Java8对内存区域的革新,以及垃圾回收策略和对象晋升机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

更多精彩,访问可乐可乐可的博客https://blog.youkuaiyun.com/weixin_44494373

Java内存区域的那些事

楼房之地基,知晓内存区域,才能知晓JVM

Java的内存区域的管理是“全自动”的,每一个刚刚见识的人都会觉得神奇

Java的自动管理内存,使得我们不需要操作释放内存的代码,不容易出现内存泄漏与溢出的现象。

但是我们不得不提防,一旦出现了内存问题,知晓虚拟机的工作模式,才能排出错误。

Java的内存区域有几个时间点,产生了比较重要的变动,分别是JDK1.7,Java8

  1. 在JDK1.7前,字符串存放在方法区之中
  2. 在JDK1.7后字符串被放在了堆
  3. 在Java8,取消了方法区,改用了直接使用直接内存的的元空间

Java8之前的运行时数据区域

这张图很重要,要熟记

Java虚拟机在执行Java程序时,将其所管理的内存划分为若干个不同的数据区域,这些区域有各自的用途,以及创建和销毁时间,有的区域,随着虚拟机进程的启动而存在,有的依赖用户线程的启动和结束。

1、程序计数器

程序计数器(Program Counter Register)

一块较小内存空间,是当前线程所执行的字节码行号指示器。字节码解释器工作时通过改变计数器达到语句的切换功能。

“线程私有”,各线程都拥有,互相独立,互不影响。

PC计数器是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域

tips

若为Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址

若为Native方法,这个计算器值为

Native方法为Java的本地方法,Java依赖了很多本地其他语言的代码,这些代码将使用Native注册在Java虚拟机中,供Java使用

2、Java虚拟机栈

Java虚拟机栈(Java Virtual Machine Stacks)

描述Java方法执行的内存模型,每个方法在执行时都会创建一个栈帧(Stack Frame,方法运行时的基础数据结构),存放局部变量表(编译期可知的基础数据类型)、操作数栈、动态链接、方法出口等等信息

线程私有,生命周期与线程相同。

空间以Slot为单位,64位数据占两个。

两个异常:

  • StackOverflowError:线程请求深度(方法调用,算术运算会使用)大于虚拟机所允许的深度(编译时指定)。
  • OutOfMemoryError:虚拟机栈在动态扩展时,无法请求到足够的内存

3、本地方法栈

本地方法栈(Native Method Stack)

类似虚拟机栈,为虚拟机使用到的Native方法服务

两个异常:

  • StackOverflowError:线程请求深度(方法调用,算术运算会使用)大于虚拟机所允许的深度(编译时指定)。
  • OutOfMemoryError:虚拟机栈在动态扩展时,无法请求到足够的内存

4、Java堆

Java堆(Java Heap)

Java虚拟机所管理的最大块,垃圾收集器管理的主要区域,故也称“GC堆”

存放对象实例,几乎所有的对象实例都在这里分配内存

被所有线程共享

异常

  • OutOfMemoryError:堆中没有内存完成实例分配,并且堆无法再扩展

从垃圾回收的角度,Java堆可以细分为:老年代和新生代。

Java堆可能划分出多个线程私有的分配缓冲区

Java堆可能在不连续的内存空间,但是逻辑上是连续的

5、方法区

方法区(Method Area)

用于存储已被虚拟机加载的类信息常量静态常量即时编译器(JLT,将字节码直接翻译为机器代码,加速执行)编译后的代码等数据

被所有线程共享

Java虚拟机规范将其描述为堆的一个逻辑部分,但有一个别名“非堆”,被多数人称为“永久代

关于字符串常量池,在JDK1.6中,字符串在方法区拥有对象,在JDK1.7中只有引用,在jdk8中,永久代(方法区)被元空间取代了。

https://juejin.im/post/5c160420518825235a05301e

6、运行时常量池

运行时常量池(Runtime Constant Pool),方法区的一部分,

用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放

除了Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。

动态性:运行期间也能将新变量放入常量池。(如**String类的intren()**方法)

7、直接内存

直接内存(Direct Memory)

虚拟机外的内存

NIO:JDK1.4中新引入,引入一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,可以直接使用Native函数库分配堆外内存,然后通过一个存储在Java堆中DirectByteBuffer对象作为这块内存的引用进行操作。

可能的异常:OutOfMemoryError

Java8以后的内存区域

在这里插入图片描述

在Java8,取消了方法区,使用元空间顶替了原来方法区的功能,元空间使用直接内存,效率更高。

原有的老年代新生代在堆内进行划分。

新的内存如何进行垃圾回收

看图,我们能得到,新生代被分为了三个区域:一个较大的Eden和两个较小的Survivor
Survivor中总是有一个是空的,当发生GC时,使用标记-复制算法将Eden与一个正在使用的survivor中的对象一起复制到空闲的Survivor中。
默认Eden与Survivor大小比例为8:1 (8+1+1=10)

在这里插入图片描述
若Survivor不足以放存活的对象,则会使用其他空间(一般是老年代)做分配担保,
在这里插入图片描述

其中,使用动态年龄计算的方法,将对象一级一级的升级
Eden->From Survivor0 ->To Survivor1 -> OldMemory

对象如何被添加到老年代

  1. 当一次新生代垃圾回收Minor GC过后,若对象仍然存活,则进入To Survivor,并年龄+1
  2. To Survivor成为新的From Survivor
  3. 当From Survivor中的对象超过了年龄阈值则进行晋升
  4. 若To Survivor在GC后,填入被移动的对象,成为新的From前,被填满,则整体移动到老年代

动态年龄计算的方法

每次新生代垃圾回收,都会将存活对象年龄加一
若累计的某个年龄大小超过了Survivor区的一半,则取这个年龄与MaxTenuringThreshold中更小的一个值,作为晋升的年龄阈值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值