Java由于虚拟机自动内存管理机制的存在,不需为每一个对象进行delete/free方法,不易出现内存泄漏和内存溢出。但如果全权由Java虚拟机控制,出现内存泄漏的问题时,如果不了解虚拟机如何使用内存,会难以排查错误。
1.运行时数据区域
程序计数器、Java虚拟机栈、本地方法栈、堆、方法区、运行时常量池、直接内存
程序计数器:
由于Java虚拟机的多线程通过线程轮流切换实现,但CPU是怎么知道记住之前的线程,执行到哪一处的?程序计数器里面保存的当前线程执行的字节码的行号(看着像行号,其实是指令地址)。这样线程切换后就能够恢复到正确的执行位置。
Java虚拟机栈(java栈):
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行时会创建一个帧栈用于存储局部变量表(各种基本数据类型Boolean/byte/char/...对象引用及returnAddress)、操作数栈、动态链接、方法出入口等等信息。
因此每个方法从调用到执行完成就是一个帧栈从虚拟机栈中入栈到出栈。
本地方法栈:
虚拟机栈为虚拟机执行Java方法,本地方法栈为虚拟机用到的Native方法服务。
Java堆:
是被所有线程共享的一块内存区域,在虚拟机启动时创建,用于存放对象实例(所有对象实例及数组都要在堆上分配),是垃圾收集器管理的主要区域(CG堆),可以将对象实例分为更细的新生代、老年代等
Java堆可以处在物理上不连续的内存空间中,但要求逻辑连续。
方法区:
又叫静态存储区,也是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息(class)、常量、静态变量、编译后的代码等数据。
运行时常量池:
是方法区的一部分。Class文件除了有类的版本、字段、方法、接口等,还有用于存放编译器生成的各种字面量和符号引用的信息。
2.虚拟机对象
2.1. 对象创建
创建对象通过new关键字,在虚拟机中,对象创建的过程为:
1.检查指令参数是否能够在常量池中定位到,代表的类是否被加载,执行相应类加载过程。
2.类加载检查通过后,通过“指针碰撞”(指针——内存规整)或“空闲列表”(空闲内存表——内存不规整)方法为新生对象分配内存
3.分配到的内存空间初始化0值
4.对对象进行设置(对象为哪个类的实例、HashCode、类的元数据信息、GC年龄等——对象头)
5.(从Java程序的视角)Init方法
2.2.对象内存布局
内存分为对象Header、Instance、对齐填充
Header:部分一(对象自身的运行时数据如HashCode、GC年龄等),部分二(类型指针,指向类元数据,通过指针确定这个对象是哪个类的实例)
Instance:定义的各种类型的字段内容,是对象真正存储的有效信息
对齐填充:对象大小必须为8的整数倍,对象头是8的整数倍,因此需要对对象实例部分进行对齐填充(若有必要)
2.3.对象的访问定位
1.句柄访问
Java栈中的reference ——>Java堆中的句柄池(指针*2——指向Java堆中的对象实例&方法区中的对象类型数据)
优点:存储的是句柄地址,在对象被移动时(如GC过程)只改变句柄中的实例指针即可
2.直接指针(常用)
Java栈中的reference ——>Java堆中的对象实例(包括了指向对象类型数据的指针)——>方法区中的对象类型数据
优点:速度更快,节省了一次指正定位的开销