写点什么

JVM-- 运行时数据区与内存模型,java 开发基础知识点

作者:Java高工P7
  • 2021 年 11 月 10 日
  • 本文字数:2410 字

    阅读完需:约 8 分钟

问题:那一个线程执行的状态如何维护?一个线程可以执行多 少个方法?这样的关系怎么维护呢?


虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。换句话说,一个 Java 线程的运行状态,由一个虚拟机栈来保存,所以虚拟机栈肯定是线程私有的,独有的,随着线程的创建而创建。



每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。


栈帧:


栈帧:每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间。


栈帧中包括局部变量 表、操作数栈、动态链接、方法返回地址和即时信息。


局部变量 表:方法中定义的局部变量以及方法的参数存放在这张表中,局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令将其加载至操作数栈中作为操作数使用。



操作数 栈:以压栈和出栈的方式存储操作数的。如 1+1 两个 1 存储在局部变量 表中, 1+1 这个操作在操作数栈中完成。



动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(符号引用编程直接引用)。因为类加载机制


仅仅是将本文件的符号引用变成直接引用 ,当遇到多态的调用时,只能通过运行来确定子类对接的引用是哪一个。



方法返回地址:当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇


见异常,并且这个异常没有在方法体内得到处理。


本地方法栈


当前线程执行的方法是 Native 类型的,这些方法就会在本地方法栈中执行


思考:在 Java 方法执行的时候如何调用 native 的方法呢?


通过动态链接来进行调用。




程序计数器


作用:是记录当前线程的执行位置,线程私有。


如果线程正在执行 Java 方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;



如果正在执行的是 Native 方法,则这个计数器为空。


除了上面五块内存之外,其实我们的 JVM 还会使用到其他两块内存


直接内存(Direct Memory)


并不是虚拟机运行时数据区的一部分,也不是 JVM 规范中定义的内存区域,但是这部分内存也被频 繁地使用,而且也可能导致 OutOfMemoryError 异常出现,所以我们放到这里一起讲解。在 JDK 1.4 中新加入了 NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区 (Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆 里面的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能, 因为避免了在 Java 堆和 Native 堆中来回复制数据。


本机直接内存的分配不会受到 Java 堆大小的限制,但是,既然是内存,则肯定还是会受到本机总 内存的大小及处理器寻址空间的限制。因此在分配 JVM 空间的时候应该考虑直接内存所带来的影 响,特别是应用到 NIO 的场景。


其他内存:


Code Cache:**JVM 本身是个本地程序,还需要其他的内存去完成各种基本任务,比如,JIT 编译器在运行时对热点方法进行编译,就会将编译后的方法储存在 Code Cache 里面;GC 等 功能。需要运行在本地线程之中,类似部分都需要占用内存空间。这些是实现 JVM JIT 等功能 的需要,但规范中并不涉及


其中栈可以指向堆 c


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


ase: Object obj=new Object()



方法区指向堆 case: private static Object obj=new Object();



堆指向方法区 case: Class 类对象指向它的元数据。 new Object().getClass();


Java 对象内存模型


一个 Java 对象在内存中包括 3 个部分:对象头、实例数据和对齐填充。




内存模型设计之–大小端存储


小端存储:便于数据之间的类型转换,例如:long 类型转换为 int 类型时,高地址部分的数据可以 直接截掉。



大端存储:便于数据类型的符号判断,因为最低地址位数据即为符号位,可以直接判断数据的正 负号。


内存模型设计之–Class Pointer


引用定位到对象的方式有两种,一种叫句柄池访问,一种叫直接访问





区别:


句柄池:


使用句柄访问对象,会在堆中开辟一块内存作为句柄池,句柄中储存了对象实例数据(属性值结构体) 的内存地址,访问类型数据的内存地址(类信息,方法类型信息),对象实例数据一般也在 heap 中开 辟,类型数据一般储存在方法区中。


优点:reference 存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为) 时只会改变句柄中的实例数据指针,而 reference 本身不需要改变。


缺点:增加了一次指针定位的时间开销。 直接访问:


直接指针访问方式指 reference 中直接储存对象在 heap 中的内存地址,但对应的类型数据访问地址需要 在实例中存储。


优点:节省了一次指针定位的开销。 缺点:在对象被移动时(如进行 GC 后的内存重新排列),reference 本身需要被修改


内存模型设计之–指针压缩


指针压缩的目的:



为了保证 CPU 普通对象指针(oop)缓存


为了减少 GC 的发生,因为指针不压缩是 8 字节,这样在 64 位操作系统的堆上其他资源空间就少了。



64 位操作系统中 内存 > 4G 默认开启指针压缩技术,内存**< 4G**,默认是 32 位系统默认不开启。内存 > 32G


指针压缩失效。所以我们通常在部署服务时,JVM 内存不要超过 32G,因为超过 32G 就无法开启 指针压缩了。



内存 > 32G 指针压缩失效的原因是:4G_8 = 32G



32 位系统的 CPU 最大支持 2^32 = 4G ,如果是 64 位系统,最大支持 2^64,但是对其填充是按照 8 字节进行填充,指针压缩可以理解为在 32 位系统在 64 位上面使用,因为 32 位系统的 CPU 寻址空间最大支持 4G,对其填充_8= 32G,这就是内存>32G 指针压缩失效的原因。



关闭指针压缩 : -XX:+UseCompressedOops


内存模型设计之–对齐填充


对齐填充的意义是提高 CPU 访问数据的效率,主要针对会存在该实例对象数据跨内存地址区域存储的情况。


例如:在没有对齐填充的情况下,内存地址存放情况如下:



因为处理器只能 0x00-0x07,0x08-0x0F 这样读取数据,所以当我们想获取这个 long 型的数据时,处理 器必须要读两次内存,第一次(0x00-0x07),第二次(0x08-0x0F),然后将两次的结果才能获得真正的数值。

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
JVM--运行时数据区与内存模型,java开发基础知识点