java虚拟机内存模型小结

Java虚拟机的主要任务是装载class文件并且执行其中的字节码

 

1、程序计数器

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

每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,是线程私有的。

此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域

 

2、Java 虚拟机栈

描述的是java方法执行的内存模型:每一个方法执行时都会创建一个栈帧来存储方法的变量表、操作数、栈动态链接方法、返回值、返回地址等信息。方法的调用对应着栈帧的入栈和出栈。栈的大小决定了方法调用的可达深度(递归的层次或嵌套调用多少层其他方法,-Xss参数可以设置虚拟机栈的大小)。栈的大小可以是固定的,也可以是动态扩展的。如果请求栈的深度大于最大可用深度,则抛出StackOverflowError;如果栈是可以动态扩展的,但没有内存空间支持扩展,则抛出 OutOfMemoryError 异常。

 

3.本地方法栈

与虚拟机发挥的作用是相似的,其区别在于Java虚拟机栈为执行java方法服务,而本地方法栈为执行 Native 方法服务。本地方法区也会抛出 StackOverflowError和OutOfMemoryError异常。

 

4.Java堆(Java Heap)

Java堆是虚拟机中所管理内存最大的一块。

Java堆是被所有线程共享的,在虚拟机启动时创建。

此区域主要是用来存放对象实例的。几乎所有的对象实例都存放在此区域。(是几乎,不是绝对,随着编译器的发展和逃逸技术的日趋成熟,对象实例也有可能被分配到栈里)

java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”。由于现在的回收算法基本都采用分代回收算法,所以可以将堆细分为:新生代和老年代,更细致一点有Eden区域,From Servivor 区域,To Servivor 区域。

Java堆可以处在物理上不连续的内存空间中,只要逻辑上连续即可。当前主流虚拟机都是可动态扩展的(通过-Xms和-Xmx来控制)。如果在堆中没有内存可以用来分配实例且无法动态扩展,则抛出OutOfMemoryError 异常。

 

5.方法区

方法区也是线程共享的。它用于存储被虚拟机加载的类信息,常量,静态变量,JIT编译器(即时编译器)编译后的代码等数据。方法区本质上是堆的一个逻辑部分。

 

6、运行时常量池

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

 

补充:

在这里说一下关于String的内存问题:直接在双引号中创建和new的区别是什么呢?

对于字符串:其对象的引用都是存储在java栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。

通过双引号,可能创建一个对象或不创建对象实例。首先通过双引号是将”ABC”存放在常量池中的,如果常量池中没有”ABC”,则或创建一个对象,并将其引用存放在java栈中的局部变量表中。常量池的内容只会有一份,如果”ABC”已经存在,则无需创建新的对象。

通过new关键字创建String实例,会在堆中开辟一块空间,无论之前”ABC”这个字符串是否出现过。每一次new都会创建一个新的对象。


String a = “ABC”;

String b = new String(“ABC”);

String c =”AB “ + “C”

String d = new String(“ABC”);

有以下结论: a == b ->false

  a == c –>true

 b == d ->false

String e =”A”

String f = e+”BC”

此时: f == a->false

为什么呢?e是一个引用,在编译的时候不会确定,而a 和 e确实都是在编译是确定的。所以 f 会在堆中创建一个新的对象而不是直接指向常量池中的”ABC”。

String 有一个intern() 方法,native,用来检测在常量池是否已经有这个String存在。

 

总结:

1.      java虚拟机栈和本地方法栈都是描述方法执行过程的内存模型。程序计数器,以及这两个栈都是线程私有的,他们的生命周期和所属线程一样。

2.      堆和方法区是线程共享的,在虚拟机启动时创建,关闭时销毁。

### Java内存地址的概念 在 Java 中,直接访问和操作内存地址并不是像 C 或者 C++ 那样直观。这是因为 Java 运行在一个虚拟机之上 (JVM),其设计初衷是为了提供更高的安全性和平台无关性。因此,在标准的 Java 编程接口中并没有直接暴露底层物理内存地址的方法。 然而,对于某些特定的需求来说,仍然可以通过一些间接的方式获取到类似于对象哈希码这样的标识符来代表对象的位置信息[^1]。 ```java public class Address { public static void main(String[] args) { Address address = new Address(); System.out.println(Integer.toHexString(System.identityHashCode(address))); } } ``` 上述代码通过 `System.identityHashCode()` 方法返回的是该对象的身份哈希值,并不是真正的内存地址;但是它能唯一地表示一个对象实例。为了更接近于实际的内存地址显示形式,这里还使用了 `Integer.toHexString` 将整数转换成十六进制字符串输出。 如果确实需要得到更加真实的 JVM 内部的对象存储位置,则可能涉及到JNI(Java Native Interface)编程或者是利用 Unsafe 类来进行低级别的操作。不过需要注意的是这些方法通常不被推荐用于常规开发当中因为它们可能会破坏应用程序的安全模型并带来兼容性问题[^4]。 另外一种方式是借助外部调试工具如 jstack 来分析堆栈跟踪信息以及线程状态等数据,这可以帮助开发者理解程序运行期间各个对象之间的关系及其所在的具体位置[^3]。 ### 获取对象近似内存地址的小结 虽然无法简单地打印出确切意义上的“内存地址”,但以上介绍了几种可以获得与之相似信息的技术手段: - 使用 `hashCode()`: 提供了一个基于对象身份计算出来的数值。 - 调用 `System.identityHashCode(object)` : 返回对象内部使用的 hash code, 更加稳定可靠。 - JNI 和 Unsafe API: 可以实现对硬件资源更为精细控制的功能,但这超出了大多数应用场景的要求范围之外。 - 外部诊断工具:例如 jstack 工具可用于监控正在执行中的 java 应用的状态变化情况。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值