JVM内存模型分析

class 文件解读

https://blog.csdn.net/lzy_zhi_yuan/article/details/104528820

类加载器

双亲委派模型:如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求转交给父类加载器去完成。每一个层次的类加载器都是如此。因此所有的类加载请求都应该传递到最顶层的启动类加载器中,只有到父类加载器反馈自己无法完成这个加载请求(在它的搜索范围没有找到这个类)时,子类加载器才会尝试自己去加载。委派的好处就是避免有些类被重复加载。

java默认有三种类加载器,BootstrapClassLoader、ExtensionClassLoader、App ClassLoader三种。

BootstrapClassLoader是嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负载加载JAVA_HOME/lib下的类库

ExtensionClassLoader是用JAVA编写,且它的父类加载器是Bootstrap,是由sun.misc.Launcher$ExtClassLoader实现的,主要加载JAVA_HOME/lib/ext目录中的类库
App ClassLoader是应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文件。

方法区:

1.方法区是各个线程共享的内存区域,在虚拟机启动时创建
2.虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来
3.用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
4.当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常

说到方法区肯定需要了解《Java虚拟机规范》中对内存管理的划分。如图所示
在这里插入图片描述
这是《规范》中对内存的具体划分,说白了这只是个规范,是理论的东西。具体的实现因人而异。因此不同的jvm实现方法区的方式不同,甚至不同版本的实现也不同。目前除了openjdk和甲骨文的hotspotvm,商业上说得出名字的jdk只有IBM的j9vm和Azul的zingvm了。hotspotvm 的JDK 1.7、JDK 1.8实现就不同
永久代和元空间就是hotspot的不同版本的实现方式,分别属于JDK 1.7之前和之后

常量池:

什么是常量池?

  1. 字节码文件中有个constant pool,就是常量池。保存了各种字面量和对类型、域和方法的符号引用。
  2. 当字节码文件被加载到内存中之后,方法区中会存放字节码文件的constant pool相关信息,这时候就成为了运行时常量池

为什么需要常量池

一个java源文件中的类、接口,编译后会产生一个字节码文件,而java中的字节码文件需要其他的数据支撑,通常这种数据很大,不能直接存放到字节码里面。所以把对这些数据的引用存放到常量池,在真正需要使用的时候,通过动态链接将符号引用转换为直接引用

常量池分类:

  • 静态常量池,即*.class文件中的常量池(constant pool),包含类、方法的信息、字符串(数字)字面量。(字面量就是不要使用new创建的数据,基本类型,字符串等)1.8 之后存放在元数据空间,元数据空间在本地内存。
  • 而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并存放到方法区的运行时常量池中,我们常说的常量池,就是指方法区中的运行时常量池。
    class文件中的常量池被类加载之后,存放到方法区的运行时常量池中。运行时常量池具有动态性。也就是在方法区中的运行时常量池是可以发生变化的。而常量池就不行,它是静态的,当编译生成字节码文件直接就不变了

在这里插入图片描述
个人认为,静态常量池是针对每个被加载进入内存的class文件解析后,存放各个字面量值,符号引用的数据,而运行时常量区就是把所有的静态常量的数据汇总到一起(模糊来说)

方法调用过程

更底层到汇编层面来说,内存中的各个数据没有类这个概念,每次都是一个类一个方法的调用,就是相当于寄存器的相对变址寻址过程。

在这里插入图片描述
下面是编译后的代码,存放再方法区
在这里插入图片描述
(上图中#1 #2等是否就是计算实际地址的符号哪?)

运行时方法区就是把每个类的唯一标识作为他的段地址(DS),而内部的各个变量字段方法等都是偏移地址(BX),等到真正入栈执行时候这些字段方法的相对定位符等被解析成为真正的地址,从而进入CS IP被识别执行;那么进一步猜想java的权限包的概念是否也可以由此得到全部的类信息汇总后进行进一步控哪

对象内存布局:

三个部分: 对象头,实例数据,对齐填充

在这里插入图片描述

栈桢:

栈桢内容

栈中存放局部变量表,操作数栈,动态链接,方法返回地址

  • 局部变量表:用于存放方法参数和方法内部定义的局部变量。
    局部变量表有以下特征:
  1. 局部变量表的容量以变量槽(Slot)为最小单位,每个Slot都应该至少能存放一个boolean、byte、char、short、int、float、reference类型的数据
  2. reference数据类型称为引用类型,虚拟机可以通过此类型数据直接或间接地查找对象实例数据在java堆中的索引和对象所对应类在方法区中的类型信息。
  3. 方法内部定义的变量存放在局部变量表中,称为局部变量。局部变量不像类变量那样有加载过程中有准备阶段,所有局部变量不会被被赋予默认初始值,必须手动赋值
  • 操作数栈: 后入先出,操作数栈的最大深度也在编译的时候就已经确定,不会在运行期间动态变化。
  • 动态连接: 这个为了支持动态调用,子节码文件被装载进jvm内部时,如果无法确定调用方法位置(比如父类和子类的相同方法),调用方法是用符号引用表示,动态链接就是将符号引用转换为直接引用
  • 方法返回地址: 方法完成后,执行引擎读取栈桢中方法返回地址,将返回值传递给上层的方法调用者,也就是把返回值压入调用者栈桢的操作数栈。

栈中可能出现哪些异常

StackOverflowError:栈溢出错误

如果一个线程在计算时所需要用到栈大小 > 配置允许最大的栈大小,那么Java虚拟机将抛出 StackOverflowError

OutOfMemoryError:内存不足

栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。

Native Method Stacks(本地方法栈):

如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中执行。 可以使用Native 函数库直接分配堆外内存

栈指向堆: 局部方法里面调用: Object o = new Object(),
方法区指向堆: 静态变量 public static Object o = new Object();

堆指向方法区: 在堆中的对象,每个对象都有对象头,对象头中有个class point 指向方法区中类的元数据信息。

句柄池

在这里插入图片描述

使用句柄访问对象,会在堆中开辟一块内存作为句柄池,句柄中储存了对象实例数据(属性值结构体)的内存地址,访问类型数据的内存地址(类信息,方法类型信息),对象实例数据一般也在heap中开辟,类型数据一般储存在方法区中。优点:reference存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要改变。缺点:增加了一次指针定位的时间开销。直接访问:直接指针访问方式指reference中直接储存对象在heap中的内存地址,但对应的类型数据访问地址需要在实例中存储。优点:节省了一次指针定位的开销。缺点:在对象被移动时(如进行GC后的内存重新排列),reference本身需要被修改。

方法区

说到方法区肯定需要了解《Java虚拟机规范》中对内存管理的划分。如图所示
在这里插入图片描述
这是《规范》中对内存的具体划分,说白了这只是个规范,是理论的东西。具体的实现因人而异。因此不同的jvm实现方法区的方式不同,甚至不同版本的实现也不同。目前除了openjdk和甲骨文的hotspotvm,商业上说得出名字的jdk只有IBM的j9vm和Azul的zingvm了。hotspotvm 的JDK 1.7、JDK 1.8实现就不同

永久代和元空间

永久代和元空间就是hotspot的不同版本的实现方式,分别属于JDK 1.7之前和之后。

PermGen(永久代)

永久代是hotspot 的jdk1.8以前的实现,使用jdk1.7的老司机肯定以前经常遇到过“java.lang.OutOfMemoryError: PremGen space”异常。这里的“PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是JVM的规范,而后者则是JVM规范的一种实现,并且只有HotSpot才有“PermGen space”。

元空间

其实,移除永久代的工作从JDK 1.7就开始了。JDK 1.7中,存储在永久代的部分数据就已经转移到Java Heap或者Native Heap。但永久代仍存在于JDK 1.7中,并没有完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了Java heap;类的静态变量(class statics)转移到了Java heap。

JDK1.8对JVM架构的改造将类元数据放到本地内存中,另外,将常量池和静态变量放到Java堆里。HotSpot VM将会为类的元数据明确分配和释放本地内存。在这种架构下,类元信息就突破了原来-XX:MaxPermSize的限制,现在可以使用更多的本地内存。

对于僵死的类及类加载器的垃圾回收将在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。
适时地监控和调整元空间对于减小垃圾回收频率和减少延时是很有必要的。持续的元空间垃圾回收说明,可能存在类、类加载器导致的内存泄漏或是大小设置不合适

使用元空间的优点

1.不会出现“java.lang.OutOfMemoryError: PremGen space”异常
2.字符串常量池迁移到堆中,避免溢出
3,永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
4.通过类加载器来控制垃圾回收。

什么是字面量:

字面量=字面值:
解释:创建一个对象会用到new关键字,但是给一个基本数据类型变量赋值是不需要new关键字滴,基本类型的变量在java中是一种特别的内置数据类型,并非某个对象
定义:给基本类型变量赋值的方式就叫做字面量或者字面值
Float hp=120;
Int armor=10;

字面量就是数据/数值,例如:1234,true,”abc”,’中’,3.14。在现实生活中每天都会接触到数据,例如:你今天的体重是86Kg,你今天花了500元,买了个西瓜重量是8.6Kg,外面明明是晴天,你却说狂风暴雨,你说的是假话(false),你明明喜欢她,却嘴上说不喜欢,撒谎(false)。软件其实就是为了解决现实生活当中的问题,解决生活当中的问题其实就是处理生活当中的数据,所以一门编程语言首先要能够表示数据,通过字面量就可以表示数据。

在编程语言中数据一般会被分门别类,所以每个数据都是有数据类型的,不同的数据类型会分配不同的内存空间去存储它,数据通常被分为:整数型、浮点型、字符型、布尔型、字符串型等。
在这里插入图片描述

方法区中包含静态常量池,运行时常量池。

方法区:各个线程共享区域,存储.class文件、常量、静态变量、即时编译器编译后的代码等数据、运行时常量池,虚拟机描把方法区描述为堆的一个逻辑部分,有一个别名Non-Heap(非堆)。方法区是一个规范,是一个逻辑区域,不通虚拟机不同实现。

  • 静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
  • 而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

方法区:

各个线程共享,存储.class文件、常量、静态变量,编译后的代码信息。
这也是一个逻辑区域,在1.8中类的元数据信息保存再元数据空间,元数据空间在本地内存。
方法区是各个线程共享区域,存储.class文件、常量、静态变量、即时编译器编译后的代码等数据、运行时常量池,虚拟机描把方法区描述为堆的一个逻辑部分,有一个别名Non-Heap(非堆)。方法区是一个规范,是一个逻辑区域,不同虚拟机不同实现

线程栈

栈中存放局部变量表,操作数栈,动态链接,方法返回地址

  • 局部变量表:用于存放方法参数和方法内部定义的局部变量。
  • 操作数栈: 后入先出,操作数栈的最大深度也在编译的时候就已经确定,不会在运行期间动态变化。
  • 动态连接: 这个为了支持动态调用,子节码文件被装载进jvm内部时,如果无法确定调用方法位置(比如父类和子类的相同方法),
    调用方法是用符号引用表示,动态链接就是将符号引用转换为直接引用
  • 方法返回地址: 方法完成后,执行引擎读取栈桢中方法返回地址,将返回值传递给上层的方法调用者,也就是把返回值压入调用者栈桢的操作数栈。

问题QA:

  • 静态常量池: 在方法区中,class文件中类和方法信息,符号引用等。 1.8 之后存放在元数据空间,元数据空间在本地内存。
  • 运行时常量池: 在方法区,将class文件中的常量池载入到内存中,并存放到方法区的运行时常量池中,具有动态性,常量池就不行,它是静态的,当编译生成字节码文件直接就不变了
  • 静态变量: 也在方法区,也是在非堆中的一部分。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EmineWang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值