Java虚拟机运行时数据区

本文详细介绍了Java虚拟机的运行时数据区,包括程序计数器、虚拟机栈、本地方法栈、堆、方法区和直接内存。讨论了各区域的作用、特点以及可能出现的溢出问题。堆和方法区是线程共享的,而虚拟机栈、本地方法栈和程序计数器则是线程独享。此外,还提到了直接内存的使用以及垃圾回收机制。通过对这些区域的理解,有助于优化Java应用程序的性能和内存管理。

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

总览

在这里插入图片描述

  • 以是否为线程独享作为区分度
    在这里插入图片描述

  • 引入本地内存一览
    在这里插入图片描述

程序计数器

程序计数器也叫PC计数器,用于记录程序当前所运行到的指令行。在多线程中,CPU会对就绪状态的线程进行随机调度,此时就需要程序计数器来记录不同线程所执行到的位置。

程序计数器是唯一不会发生OOM(OutOfMemory)的地方。

虚拟机栈

其实虚拟机栈和本地方法栈都是栈结构。在方法调用中就容易看出这个结构:

public void one(){
        System.out.println("one start");
        two();
        System.out.println("one finish");
    }

    public void two(){
        System.out.println("two start");
        three();
        System.out.println("two finish");
    }

    public void three(){
        System.out.println("three start");
        System.out.println("three finish");
    }

在这里插入图片描述

不难看出,在方法调用中先调用的方法最后结束;这个结构就跟栈一样。

虚拟机栈主要是用于普通方法。每个方法在执行的时候都会创建一个栈帧,其包含了局部变量表操作数栈动态链接返回出口

可以反编译全字节码出得相关信息:

  • 先通过javac -g XXX.java 编译成字节码包含所有调试信息
    在这里插入图片描述

  • 通过javap -v XX.class命令,打印出反编译后的字节码文件
    在这里插入图片描述

  • 最终得到反编译后的结果(下图只附上了add方法)
    在这里插入图片描述

在这里插入图片描述

下面是《深入理解虚拟机》关于局部变量表的一段话:

局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、 float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始 地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型

说明局部变量不仅存储八大基本类型,还存放引用类型(即对象实例的引用)。

并且,在栈中会发生栈溢出以及OOM

先来说栈溢出。当发生疯狂递归调用并且没有出口的时候就会爆栈;在平时刷题或者编程写递归的时候就要主要达到某个条件就结束递归这个basecase

其次是内存溢出。对象的创建主要是在堆空间,但是在栈中也会发生内存溢出。首先是每个栈的内存大小可以控制,但是总的大小无法控制,如果开千万个线程是有可能会爆的;其次就是申请不到栈空间,因为内存已满造成的内存溢出错误。

本地方法栈

本地方法栈就是执行本地方法,即native方法。该方法就是已经被封装好了,由虚拟机底层c++代码进行调用。

可以通过一些方法来查看相关native方法对应C++/C中的底层源码。

堆空间又称为GC堆。GC即垃圾收集,因为在所有的对象都在堆中创建生成,分配内存(抛开栈上逃逸)。并且在JDK1.7以后,常量池从方法区迁移到了堆中

说到这里就要类比C++/C了,在Java中虚拟机有垃圾回收机制,即使创建的对象不做释放处理垃圾回收也会根据特定的规则判断是否是垃圾需要回收。

而C++/C中谁创建的(molloc),谁负责释放。

关于堆的分区:
在这里插入图片描述

  • 大体分成新生代老年代
  • 新生代又分成EdenFromTo三个区
  • 分区的目的是为了更好的进行垃圾回收

关于虚拟机的简单调参:

N表示具体数值

  • Xmx:Nm 堆空间最大的容量
  • Xms:Nm 堆空间初始容量
  • Xmn:Nm 新生代最大空间

OOM演示:

这块要保证创建的对象都要用=赋值(强引用),保证对象不会被垃圾回收器回收。

  • 设置最大空间
    在这里插入图片描述

  • 使用容器保证对象不被回收
    在这里插入图片描述

  • 强引用保证对象不被回收
    在这里插入图片描述

方法区

主要存放静态变量相关类信息常量常量池(1.7及以前)

在1.7及以前叫永久代,跟堆处一块;1.8以后叫元空间,扔到直接内存去。

因为在之前,使用HotSpot虚拟机。堆跟方法区使用同样的垃圾回收器,在堆中随时可能产生垃圾;但是在方法区的可能要很久才会产生垃圾。

相当于叫人不停的去打扫比较干净的区域,浪费了性能。所以在1.8后就放到直接内存去,采用不同的垃圾回收器进行回收,带来了一定的效率。

直接内存

在引用NIO加入了缓冲区以后,避免操作系统不断的进行读写拷贝,实现零拷贝操作。直接在宿主机内存区域中划出一块直接内存。该区域不属于虚拟机规范,即不算运行时数据区,但是因为被频繁使用,所以才划出这一块。

在java堆内可以用directByteBuffer对象直接引用并操作

虽然不属于虚拟机规范,但是也是可控制的,并且也会发生OOM的错误。

参数设置
在默认的情况下,他的大小跟-Xmx大小一样。即堆的最大容量就是直接内存的大小。

  • -XX:MaxDirectMemorySize=Nm
  • 限制最大直接内存大小100m

可以通过ByteBuffer.allocateDirect(128*1024*1204)来分配直接内存,可以做直接内存溢出的错误示例:

在这里插入图片描述

小结

关于运行时数据区

线程共享:堆,方法区,直接内存

线程独有:虚拟机栈,本地方法栈,程序计数器

线程独有的空间会随着线程的结束而消亡,而线程共享生命周期跟随整个应用

如果需要查看每个区存储内容(例如方法区放类信息,静态变量,常量),可以设置虚拟机参数打印垃圾回收细节(命令-XX:+PrintGCDetails)。根据控制变量法对比即可得出相关结论。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值