- 1、运行时数据区:java虚拟机在执行JAVA程序过程中会将他管理的内存区域进行划分程不同的数据区域:
- 程序计数器:当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的命令。在多线程时,每个线程都有独立的程序计数器,各条线程的计数器互不影响,独立存储。
- Java虚拟机栈:线程私有,生命周期和线程相同。是java方法执行的内存模型:每个方法在创建时都会创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法的调用都是一个栈帧从虚拟栈入栈到出栈的过程。
- 局部变量表存放了编译器可知的所有基本类型数据,对象引用,其中long和double占用2个空间,其余占用一个,空间分配在编译期完成,在运行期不会改变。
- StackOverflowError:线程请求的栈深度过大;
- OutOfMemoryError:若虚拟机栈可以动态扩展,当扩展时申请不到足够的内存。
- 动态连接:eg:Map<String,String > map = new HashMap<>(); map.put()时,map时需要找到hashmap的put方法,就将局部变量表的对象引用直接执行堆内存的对象实例进行操作
- 方法出口: 在程序执行完毕后,找到方法的调用者(线程)
- 本地方法栈:为Native方法服务,native方法主要用于加载文件和动态链接库,由于Java语言无法访问操作系统底层信息。用c语言实现的方法调用dll文件,动态连接库文件调用;
- JAVA堆(Java Heap):被所有线程共享。在虚拟机启动时,此区域唯一目的就是存放对象实例,所有对象实例以及数组都要在堆上进行分配。从内存分配角度上,线程共享的java堆可能划分出许多线程私有的分配缓冲区。
- GC堆:垃圾收集管理器在JAVA堆上,从内存回收看可以分为新生代和老生代
- 方法区:线程共享区域,用于存储虚拟机的加载类信息、常量、静态变量、即时编译器编译后代码等数据。
- 运行时常量池:方法区的一部分,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载(Class文件)后进入方法区运行时在常量池中存放。运行期间产生的新常量也可进入池中。
- 2、对象
- 对象的创建:1)当虚拟机遇到一条new指令时,会在常量池中进行定位到一个类的符号引用,并检查这个类是否被加载、解析和初始化给IP,若没有,将进行类的加载;2)类加载检查通过后,将为新生对象分配内存空间,所需空间将在类加载完成会便可确定。(分配方式指针碰撞—内存空间完整连续/空闲列表);3)多并发下内存指针的修改;两种解决方案:第一种时分配内存空间的同时同步处理(CAS+失败重试保证操作的原子性)另一种是把内存分配的动作安装线程划分的不同划分在不同的空间之中,即每个线程在java堆中都预先分配以可内存,称为本地线程分配缓冲。4)在内存分配完成后将区域初始化为零值。5)虚拟机对对象进行必要设置,这些设置信息会存在对象的对象头中。6)在执行完new之后会接着执行<init>方法,把对象按照意愿进行初始化。
- 对象的内存分布:对象在内存中的布局分为3部分:对象头,实例数据和对齐填充。
- 对象头:一部分存储运行时自身数据(哈希码、GC分代年龄、锁和锁的状态等等);另一部分时类型指针,通过这个指针来确定这个对象属于那个类,若这个对象是一个数组,还需要记录数组长度的数据。
- 实例数据:是对象真正存储的信息
- 对齐填充:占位符的作用,不是必然存在的。
- 对象的访问定位:在栈上的reference数据在来操作堆上的具体对象,主流的访问方式主要有句柄(已经不在使用)和直接指针
- 直接指针访问:Java堆对象的布局必须考虑如何放置访问类型的数据的相关信息,而reference中存储的直接就是对象地址, 这种定位方式也就导致了在对象被移动时,reference本身必须发生改变。栈内存的局部变量表(reference存储对象对象的引用-----也就是对象的堆内存地址值)--------------堆内存创造出的对象实例--------------方法区的class模板文件创造
- 直接指针访问:Java堆对象的布局必须考虑如何放置访问类型的数据的相关信息,而reference中存储的直接就是对象地址, 这种定位方式也就导致了在对象被移动时,reference本身必须发生改变。栈内存的局部变量表(reference存储对象对象的引用-----也就是对象的堆内存地址值)--------------堆内存创造出的对象实例--------------方法区的class模板文件创造
- 堆内存:额外空间-元空间(Meta Space)=用户自己的内存条