JVM & java的内存区域

本文主要讲java的运行时数据区域。

一、运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。如图:
在这里插入图片描述

1.程序计数器

描述: 程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

作用: 它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复 等基础功能都需要依赖这个计数器来完成。

线程私有的内存: 每一条线程都需要一个独立的程序计数器。
原因: java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的。在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。

注意:

  • 如果线程正在执行的是一个java方法,这个计数器记录的是正在执行得虚拟机字节码指令的地址
  • 如果正在执行的是本地(native)方法,这个计数器值则为空
  • 此内存区域是唯一一个在《java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域

2.Java虚拟机栈

描述: 虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行得时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程;

在这里插入图片描述
其中局部变量表包含:

  • 基本数据类型
  • 对象引用(reference类型)【可能是一个指向对象起始地址的引用指针, 也可能是指向一个对象的句柄或者其他与此对象相关的位置】
  • returnAdddress类型(指向了一个字节码指令的地址)

且这些数据类型在局部变量表的存储空间以局部变量槽(Slot)来表示。64位长度的long, 和double占用两个变量槽,其他为一个。

3.本地方法栈

描述: 本地方法栈是为虚拟机使用到的本地(Native)方法服务。

摘抄一段话,下面有翻译。

Any native method interface will use some kind of native method stack. When a thread invokes a Java method, the virtual machine creates a new frame and pushes it onto the Java stack. When a thread invokes a native method, however, that thread leaves the Java stack behind. Instead of pushing a new frame onto the threadís Java stack, the Java Virtual Machine will simply dynamically link to and directly invoke the native method. One way to think of it is that the Java Virtual Machine is dynamically extending itself with native code. It is as if the Java Virtual Machine implementation is just calling another (dynamically linked) method within itself, at the behest of the running Java program.

任何本地方法接口都会使用某种本地方法栈。当线程调用Java方法时,虚拟机会创建一个新的栈帧并压入java栈。然而当他调用的是本地方法时,虚拟机会保持Java栈不变 ,不再在线程的java栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。可以把这看做是虚拟机利用本地方法来动态扩展自己 。就如同Java虚拟机的实现在按照其中运行的Java程序的吩咐,调用属于虚拟机内部的另一个(动态连接的)方法。

原文链接:JVM学习笔记-本地方法栈(Native Method Stacks)

4.java堆

描述: java堆是被所有线程共享的一块内存区域,在虚拟机启动是创建。

作用: 唯一目的是存放对象实例,java世界里“几乎”所有的对象都在这里分配内存。

java堆细分: java堆是垃圾收集器的管理的内存区域。不少资料因垃圾回收机制,划分了新生代,老年代,永久代,Eden, From Survivor ,To Survivor等。时代发展,概念不一定完全对的上,但是将java堆细分的目的都是为了更好地回收内存,或者更快地分配内存。

操作: java堆可以被实现成固定大小,也可以可扩展。
通过参数 -Xmx 和Xms设定。
具体eclipse设置其大小,点这链接 用eclipse运行项目时怎么设置虚拟机内存大小

注意: java堆可以处于物理上不连续的内存空间,但逻辑上应是连续的。

5.方法区

描述: 和java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。 别名:非堆。

在这里插入图片描述
操作: 它不需要连续空间,可以固定大小和可扩展,甚至可以选择不实现垃圾收集。

注意:

  • 方法区是线程安全的。由于所有的线程都共享方法区,所以,方法区里的数据访问必须被设计成线程安全的。例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个类还没有被装入JVM,那么只允许一个线程去装载它,而其它线程必须等待 !
  • jdk1.6字符串常量池在方法区中,1.7之后在堆中。运行时常量池一直在方法区中。
  • jdk1.8开始,移除永久代概念,方法区用元空间实现。

6.运行时常量池

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

注意:

  • 除了保存Class文件中,描述的符号引用外,还会把符号引用翻译出来的直接引用也存储在运行常量池中。
  • 运行时常量池是具备动态性的,java语言并不要求常量一定只有编译期才能产生。也就是说,运行期间也可以将新的常量放入池中。这种特性被开发人员利用较多的便是String类的intern()方法。

补充:直接内存

描述: 直接内存并不是虚拟机运行时数据区的一部分,也不是《java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁地使用。

在JDK 1.4中新加入NIO(New Input/ Output)类,引用了一种基于通道与缓存区的I/O方式,它可以使用Native函数库直接分配对外内存,然后通过一个存储在java堆里面的DirectBytebuffer对象作为这块内存的引用进行操作。

这样能在一些场景中显著提高性能,因为避免了在java堆和Native堆中来回复制数据。

注意: 该内存不受Java堆大小限制,但会受到本机总内存大小及处理器寻址空间的限制。

可以通过MaxDirectMemorySize来设置( 默认与堆内存最大值一样) , 所以也会出现OOM异常。

不同版本下内存图的区别:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
本文主要是对《深入理解Java虚拟机》一书第二章有关Java内存区域的读书笔记与点点记录。
优秀其他文章:Java内存区域划分


笔者水平有限,目前只能描述以上问题,如果有其他情况,可以留言,有错误,请指教,有继续优化的,请分享,谢谢!
本篇文章是否有所收获?阅读是否舒服?又什么改进建议?希望可以给我留言或私信,您的的分享,就是我的进步。谢谢。


2020.9.10 网安院2楼

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值