liufangbaishi0 2020-06-08
Java虚拟机在执行Java程序的过程中会把它所管理的划分为若干个不同的数据区域,这些区域有各自的用途,以及创建和销毁时间。
程序计数器
Java虚拟机栈
本地方法栈
Java堆
方法区
? 是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。
? 就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都是依赖字节码解释器来完成。
? 在Java虚拟机的多线程是通过线程轮流切换,分配处理起执行时间的方式来实现的。一个处理器(单核)都会执行一条线程中的指令,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,保证各个线程之间计数器互不影响,独立存储。也可称为“线程私有”内存
? Java虚拟机栈和程序计数器一样,线程私有、生命周期和线程相同。
? Java虚拟机栈主要的就是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步船舰一个栈帧(Stack Frame)用来存储局部变量表,操作数栈、动态连接、方法出口等信息。每一个方法被调用一直到执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
? 虚拟机中的栈在大多数情况下指的是虚拟机栈中的局部变量表部分。
? 局部变量表存放了编译期可知的各种Java虚拟机基本数据类型:
boolean
byte
char
short
int
float
long
double
对象引用(reference类型)
returnAddress类型
上述的数据类型在局部变量表中的存储空间以局部变量槽(solt)来表示。其中long和doulbe占用两个变量槽,其他的数据类型占用一个变量槽。一个变量槽的大小是32byte
? Java堆是被所有线程共享的一块内存区域,用来存储对象实例。基本上所有的对象都是在这里分配内存
? 当Java虚拟机遇到一条字节码new指令的时候,首先检查这个指令的参数是否在常量池定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析、和初始话。当类加载检查通过后,那么就会在虚拟机为新生对象分配内存。
? 内存分配两种方式:
? 假设Java堆中内存是绝对规整的,所有被使用锅的内存都放在一边,空闲的内存被放到另一边,中间放着指针作为分界点的指示器,那么所分配内存就仅仅是把指针向空闲内存方向挪动与对象大小相等的距离。
? Java堆中的内存不是规整的,已被使用的内存和空闲的内存相互交错在一起,这样就没有办法向指针碰撞一样去分配内存了。
那么虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从泪飙中找到一块足够大的空间划分给对象实例,并且更新列表上的记录。
? 对象在堆内存中划分为一个部分:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)
? 对象头部分包括两类信息:
? 1、存储对象自身的运行时数据:哈希码(hashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向锁ID、偏向时间戳等等。
? 这部分数据长度在32位和64位虚拟机中分别对应32bit和64bit,官方称为“Mark Word”
? 2、类型指针
? 对象指向它的类型元数据指的指针,通过这个指针来确定该对象是哪个类的实例
? 3、对象是数组
? 如果对象是数组,那么在对象头中出了上面说的两种之外,还有记录数组长度的数据
? 对象真正存储的有效信息,简单来讲就是我们在程序中定义的各种类型的字段内容,无论是从父类继承下来,还是我们在子类中定义的字段都会在实例数据中记录起来。
? HotSpot虚拟机默认分配顺序:longs/doubles 、ints、shorts、chars、bytes/booleans、oops(Ordinary Object Pointers)。
? 使用-XX:FiledsAllocationStyle 参数会影响Java源码中定义顺序。
? 这个不是必然存在的,没有什么特别的意义。
? HotSpot虚拟机的自动内存管理要求对象起始地址必须是8字节整数倍,如果对象实例数据部分不是8的整数倍,那么就需要对其填充来补全。
? 方法区和Java堆一样,线程共享内存区域。用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
? 运行时常量属于方法区的一部分。Class文件中除了有类的版本、字段、方法、接口、等描述信息外,还有一项信息就是常量池表。用于存放编译期生成的各种字面量于符号引用。
?
?