Java内存结构(Java运行时数据区)及相关面试题

这里讨论的是Java运行时数据区,不是JMM内存模型,如果需要了解JMM内存模型,参考这篇文章:https://blog.csdn.net/Chang_Yafei/article/details/128184249

Java的内存结构大致分为5个部分: 

更详细的图:

 图中蓝色区域是线程私有(除了堆和方法区),黄色区域是线程共有的(堆和方法区)

1.PC(程序计数器)

当执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为“取指令”。与此同时,PC中的地址或自动加1或由转移指针给出下一条指令的地址。此后经过分析指令,执行指令。完成第一条指令的执行,而后根据PC取出第二条指令的地址,如此循环,执行每一条指令。

2.JVM stack(JVM栈)

每一个方法对应一个栈帧,每个栈帧包含

(1)局部变量表(Local Variable):一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量,以变量槽(Variable Slot)为最小单位,一个变量槽可以存放一个32位以内的数据类型,这些数据类型有boolean、byte、char、short、int、float、reference和returnAddress这8种类型,reference表示对一个对象实例的引用。对于64位的数据类型,Java虚拟机会以高位对齐的方式为其分配两个连续的变量槽空间,Java中明确的64位的数据类型只有long和double两种。由于局部变量表是建立在线程堆栈中的,属于线程私有的数据,所以不管读还是写两个连续的变量槽是否为原子操作,都不会引起数据竞争和线程安全问题。

类变量存在两个阶段赋值的过程:第一次是在准备阶段,赋予系统初始值(通常说的0值,也就是类型默认值)第二次是在初始化阶段,赋予程序员定义的初始值。因此类变量没有赋值也没有关系,仍然会有系统初始值。但是局部变量不像类的变量一样存在"准备阶段",如果没有赋初始值,是完全无法使用的,变异的时候就会报错

槽的第一个位置是方法的入参,比如main方法的args入参(非static方法第一个局部变量是this)

 

bipush 把后面的值当成byte类型,然后扩展为int类型的值压栈,后面跟int类型的数(大于128的时候使用sipush表示当成short类型)

istore: store int into local variable 把操作数栈的栈顶元素弹出存储到局部变量表的对应位置

iload: load into from local variable 从局部变量表拿变量值压栈

bipush 8 将8压栈

istore_1 将栈顶元素取出赋值给局部变量表的槽位1

iload_1 从局部变量表的槽位1的值拿出来压栈

iinc 1 by 1把局部变量表位置位1的数加1

istore_1 把栈顶元素弹出赋值给局部变量表的1位置的变量

所以这个面试题的结果是8,执行过程如下:

1. 往栈里放一个值为8的int类型的数(栈中为8)

2.弹出栈中的栈顶元素赋值给局部变量表里的槽位1的变量(栈为空)

3.从局部变量表拿到对应位置的变量值值压栈(将8压栈,栈顶元素为8)

4.局部变量表位置为1的变量加1(局部变量表1位置的数变为9)

5.弹出栈顶元素赋值给局部变量表里的位置位1的变量(栈中的数字是8,把局部变量表的9覆盖)

如果把代码改为i = ++i; ,则bytecode如下:

这个过程我们可以看到是这样的:

1. 往栈里放一个值为8的int类型的数(栈中为8)

2.弹出栈中的栈顶元素赋值给局部变量表里的槽位1的变量(栈为空)

3.局部变量表位置为1的变量加1(局部变量表1位置的数变为9)

4.从局部变量表拿到对应位置的变量值值压栈(将9压栈,栈顶元素为9)

5.弹出栈顶元素赋值给局部变量表里的位置位1的变量(栈中的数字是9)

iadd是从栈顶拿出两个int型的数计算然后结果放回栈里(栈顶)
(2)操作数栈(operand ):操作数做加减乘除的过程中分配的临时的内存空间,用完就没了。栈的最大深度在编译的时候就被写入到Code属性的max_stacks数据项之中了。它的每一个元素都可以是包括long和double在内的任意Java数据类型,32位的数据类型所占的栈容量是1,64位的为2
(3)动态链接(Dynamic Linking):把方法的符号如math.compute转换为方法执行的内存入口地址,比如A调用B方法,执行A方法的时候需要去常量池中找B方法
(4)方法出口(返回值地址,return address),方法(如compute)调用完的时候把调用它的方法(如main方法)的返回地址记录下来放在compute的方法出口,可以继续回到main方法继续往下执行。比如A方法调用了B方法,B方法执行完之后应该放哪里以及B执行完之后应该回到哪个地址继续执行

方法有两种方式退出:正常调用完成的退出或者抛出异常异常调用完成的退出。不管是哪种方式退出,在方法退出之前都必须返回到最初方法被调用时的位置,程序才能继续执行

方法退出的过程实际上等同于把当前的栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有)压入到调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等

3. Heap(堆)

对象

4.Method Area(方法区)

这是一个逻辑上的概念

具体实现在jdk1.8之是永久代,1.8及以后是元空间,存储:常量+静态变量+类信息

在JVM启动时被创建,并且物理内存可以不连续

大小可以固定也可以是动态扩展的(如果不加限制则最大可以用完物理内存,一般我们会加-XX:MetaspaceSize以及MaxMetaspaceSize做限制(一般设置为一样的))

方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类会出现OOM错误。

会随着JVM的关闭而释放这一区域的内存

5.本地方法栈

native关键字修饰的叫本地方法,底层实现不是java,是c或者c++,这些方法放到本地方法栈 

JVM为什么要区分栈和堆?

(1)从软件设计的角度来看,栈代表了处理逻辑,而堆代表了数据,这样分离使得处理逻辑更为清晰。这种隔离、模块化的思想在软件设计的方方面面都有体现。

(2)堆与栈的分离,使得堆中的内容可以被多个栈共享。这种共享有很多好处,一方面提供了一种有效的数据交互方式(如内存共享),另一方面,节省了内存空间。

(3)栈因为运行时的需要(如保存系统运行的上下文),需要进行址段的划分。由于栈只能向上增长,因此会限制住栈存储内容的能力。而堆不同,堆的大小可以根据需要动态增长。因此,堆与栈的分离,使得动态增长成为可能,相应栈中只需要记录堆中的一个地址即可。

(4)堆和栈的完美结合就是面向对象的一个实例。其实,面向对象的程序与以前结构化的程序在执行上没有任何区别,但是面向对象的引入使得对待问题的思考方式发生了改变,是更接近于自然的思考方式。当把对象拆开会发现,对象的属性其实就是数据,存放在堆中,而对象的方法就是处理逻辑,存放在栈中。我们编写对象的时候,其实即编写了数据结构,也编写了处理数据的逻辑。

总结:栈主要用来执行程序,堆主要用来存放对象,为栈提供数据存储服务。也正是因为堆与栈分离的思想才使得JVM的垃圾回收成为可能。
 

JVM GC只回收堆区和方法区内的对象,不回收虚拟机栈内的数据,栈内数据在超出作用域后会被JVM自动释放掉。

因为JVM GC回收堆区的对象,所以先了解学习一下堆内存的结构图:

堆内存分为年轻代(Young Generation)、老年代(Old Generation),年轻代和老年代所占空间比例默认是1:2。年轻代又分为Eden和Survivor区,Survivor区由FormSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。From和To主要是为了解决内存碎片化。

为什么使用元空间代替永久代?

我认为有三个方面的原因:
(1)在1.7 版本里面,永久代内存是有上限的,虽然我们可以通过参数来设置,但是
JVM 加载的class 总数、大小是很难确定的。所以很容易出现OOM 问题。但是元空间是存储在本地内存里面,内存上限比较大,可以很好的避免这个问题。
(2)永久代的对象是通过FullGC 进行垃圾收集,也就是和老年代同时实现垃圾收集。
替换成元空间以后,简化了Full GC。可以在不进行暂停的情况下并发地释放类
数据,同时也提升了GC 的性能

 (3)Oracle 要合并Hotspot 和JRockit 的代码,而JRockit 没有永久代。

关于元空间的内容可以参考这个文章:

Java中的元空间(Metaspace)_鱼跃鹰飞的博客-CSDN博客

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值