JVM内存结构->看这篇文章就够了

  1. 序言:工作多年,一直没有系统的整理过JVM相关的知识以及在工作中出现相关问题如何排查,此文接下来将介绍JVM相关内容:

  1. 理论知识:

  1. JDK1.8作为一个分水岭,首先要清楚JVM的内存结构是怎样的:

  1. JDK1.8之前:

  1. JDK1.8:

  1. JVM内存结构介绍:

  1. 程序计数器:

  1. 存储当前线程执行程序的序号。类似指针的数据结构。

  1. 字节码解释器工作的时候就是通过改变这个计数器的值来获取下一条需要执行的字节码指令。

  1. 常见的代码中,if-else操作、循环操作、异常处理、线程恢复等都需要依赖这个计数器来完成。

  1. JVM的多线程是通过CPU时间片轮转来实现的,线程在执行等过程中可能因为时间片耗尽而被挂起,当再次获取时间片时,需要从挂起的地方再继续执行。在JVM中,通过程序计数器来记录程序字节码的执行位置。程序计数器具有线程隔离性,每个线程都有自己的程序计数器。

  1. 虚拟机栈:

  1. 栈的特点:先进后出。

  1. JAVA虚拟机栈表示的是JAVA方法执行时的内存模型,每个方法执行的时候都会创建一个栈帧。而每一个栈帧又包含:局部变量表、操作数栈、动态连接、方法出口等信息。

  1. JAVA虚拟机栈 栈顶的栈帧就是当前方法的栈帧,而方法调用其他方法时又会创建一个新的栈帧放到栈顶。so->压栈。

  1. 栈帧:

  1. 局部变量表:存放的是八大基本数据类型以及对象的引用类型reference类型,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄。

  1. 操作数栈:存放的是局部变量表的复制内容,计算的中间值或临界值等,比如将局部变量表中的a,b两个值压入操作数栈中,计算他们的结果,然后将结果又压入操作数栈。

  1. 动态连接:常量池中有大量的符号引用,字节码中的方法调用指令就是以常量池中方法的符号引用为参数,这些符号引用一般会在类加载阶段或者第一次调用的时候转化为直接引用,这部分又叫做静态解析,另一部分在每一次运行期间转化为直接引用,这部分称为动态连接。

  1. 方法出口信息(方法返回地址):方法调用结束后,必须返回该方法最初被调用的位置,所以在栈帧中要保存一些信息用来恢复它的上层主调方法的执行状态。方法的返回地址就是主调方法在调用该方法的指令的下一条指令的地址。

  1. 本地方法栈:是java调用Native方法时开辟的内存。Native方法是java通过JNI直接调用本地C/C++库。可以认为Native方法是C/C++暴露给java的一个接口。

  1. 堆:所有的对象实例以及数组都应当在堆上分配,常说的垃圾回收GC也在这里进行。由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以堆中又出现:新生代、老年代、永久代、Eden空间、From Survivor空间、To Survivor空间等。

  1. ⭐对于应当解释:由于即时编译技术的进步,尤其是“逃逸分析技术”的日渐强大,栈上分配、标量替换优化手段已经导致一些变化发生。所以java对象的实例都分配在堆上也就不是那么绝对了。

  1. 方法区(元空间):主要存放的是已加载的类信息、方法信息、属性信息、常量池、静态变量、即时编译器编译后的代码缓存。

  1. 已加载的类信息:

  1. 类的完整名称:包名和类名。

  1. 类的直接父类完整名称。

  1. 类的修饰符。

  1. 类的直接接口列表。

  1. 属性信息(类中的成员变量):修饰符、类型、属性名称。

  1. 方法信息(类的方法信息):方法名称、方法的返回类型、方法的参数、方法的修饰符、方法的局部变量表和操作数栈、异常信息表。

  1. 常量池:

  1. Class文件常量池:包含字面量和符号引用。

  1. 字面量:类中定义的字符串、final修饰的变量等。

  1. 符号引用:方法名、方法描述、类名、字段名、字段描述符。

  1. 比如在类中定义了String name变量,他会将String这个类型转化为符号引用,这样在运行中就能通过这个引用找到对应的类进行解析。

  1. 运行时常量池:

  1. 当类加载到内存中后,jvm就会将Class文件常量池的内容放到运行时常量池中,运行时常量池也是每个类都有一个,并且在类加载的解析阶段会把运行时常量池的符号引用转化为直接引用,这个其中的过程需要查找字符串常量池。

  1. 字符串常量池:

String name1 = "xiaoming";
String name2 = new String("xiaoming");
  1. name1:"xiaoming"在编译期就已经确定,它会直接进入Class文件常量池中,当运行期间在全局字符串常量池中会保存它的一个引用,实际上最终还是要在堆上创建一个"xiaoming"对象。

  1. name2:使用了new String(),调用了String的一个构造函数,new指令是创建一个类的实例对象并完成加载初始化的,因此这个字符串对象在运行期才能确定,创建的字符串对象是在堆内存上。

  1. 静态变量:类变量、类的所有实例都共享,在方法区有个静态区,静态区专门存放静态变量和静态代码块。

  1. 方法区(元空间)在JDK1.8之后使用的是直接内存,也就是系统的实际内存。

  1. 直接内存:

  1. 在JDK1.4中新加入了NIO(New Input/Output)类,引入了基于通道channel和缓冲区buffer的I/O方式,他可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。避免了Java堆和Native堆中来回复制数据,从而提高性能。

  1. 显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器寻址空间的限制。所以在根据实际内存设置-Xmx(设置最大 Java 堆大小)等参数信息要考虑上直接内存,不然可能会导致内存区域总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值