Java内存区域

运行时数据区

JVM所管理的内存包括以下运行时数据区域

程序计数器

线程私有,是一块较小的内存空间,相当与当前线程执行的字节码的的行号指示器,计数器的值就是字节码解释器执行的下一条指令,程序的循环、跳转、异常处理、线程恢复都要依赖这个计数器

JVM的多线程是通过线程间的轮流切换来获取CPU的执行时间来实现的,一个CPU在一个时刻只能执行一个线程,所以当线程切换回来的时候要依靠计数器来找到 之前线程执行的位置,每个线程都需要由一个单独的程序计数器,在内存中属于线程私有的

当线程执行的是Java方法时,计数器记录的是字节码指令的地址,当执行的是Native方法时,值为空,是唯一一个没有规定OOM的内存区域

Java虚拟机栈

线程私有,生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,存储局部变量表,操作数栈,方法的出口信息等, 每一个方法的调用到执行完成,都代表了一个栈帧从入栈到出栈的过程

通常说的栈内存存储的就是虚拟机栈中的局部变量表

局部变量表存储的是编译期可知的各种数据类型,包括 boolean, byte, char, short, int, float, long, double及对象引用(可能是一个指向对象内存地址的指针,也肯能是一个代表对象的句柄)

本地方法栈

线程私有,与虚拟机栈的作用相似,虚拟机栈存储的是Java中的方法,而本地方法栈存储的是使用到的Native方法(非Java语言实现),比如String的hashCode(), intern(), 并发包中Atomic中的weakCompareAndSet()方法

Java堆

线程共享,JVM内存中最大的一块,是所有线程共享的内存区域,存放所有的对象实例,
从GC的角度看,可分为新生代和老年代,再细一点分为Eden空间,From Survivor空间,To Survivor空间。
从内存分配的角度看,可划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。 多种分配策略,都是为了再不同场景下能更好的回收内存,更快的分配内存
Java堆在物理上可以是不连续的空间,但在逻辑上必须是连续的

方法区

线程共享,该区域存储的是JVM加载的类信息,常量,静态变量,编译后的代码等数据,也称为永久代(Permanent Generation),
内存的占用主要是常量池,GC的主要目标也是常量池和类型的卸载,但在JDK1.7中,已经把常量池移到堆中,同时在JDK1.8中移除了整个永久代, 取而代之的是一个叫元空间(Metaspace)的区域,PermSize和MaxPermSize参数也一并移除了

对象的创建

当虚拟机遇到 new 指令时,会先在常量池中寻找是否有该类的符号引用,并检查这个类是否已经被加载、解析、初始化过,如果没有,就会先执行类加载过程
类加载检查通过后,JVM就会为对象分配内存,就是在堆中划分出一块确定大小的内存,分配方式有两种

  • 指针碰撞(Bump the Pointer):堆中内存绝对规整,只需将指针向内存的空闲空间移动和对象大小相等的距离
  • 空闲列表(Free List):堆中内存不规整,已使用和未使用的内存相互交错,虚拟机就需要维护一个列表,记录哪些内存是可用的,在分配的时候找到一个足够大的空间进行分配

Java堆是否规整由采用的垃圾收集器是否带有压缩整理功能决定,因此,在使用Serial、ParNew等带Compact的收集器时,系统采用的时指针碰撞,而使用 CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表

怎么确定对象所需内存的大小?

类加载完成之后,加载类的继承体系关系得以明确,父级继承链中所有非静态域成员的FieldsLayout和size就已知了,这个size就是当前加载类的base值; 对于当前加载类,根据域成员的定义顺序,依次迭代成员域数组_fields,根据数组中的每一个域成员的类型,在base值的基础上累加该类型的偏移量, 这样就能计算出来所有非静态成员的size总和,考虑到虚拟机所属平台是否是64bit,或是32bit,指针大小是8字节还是4字节, 在上述迭代过程中会对被迭代的每一个non_static_field做内存对齐处理(加上内存对齐导致的padded_offset)——以上过程称之为layout_fields, 最后考虑到对象头的存在,再加上对象头的header_size(),就可以决定使用new指令创建对象时所需要allocate的堆内存大小了。

对象创建非常频繁,还需要经常修改指针的位置,在并发情况先不是线程安全的,可能出现对象A在分配内存,指针还没来得及修改,对象B又同时使用了原来 的指针分配了内存,所以需要做同步处理,采用CAS配上失败重试的方式保证更新操作的原子性,另一种是在线程执行时单独在内存划分出空间(TLAB), 只有TLAB用完需要分配新的时,才需要同步锁定,可通过-XX:+/-UserTLAB参数来设定是否使用TLAB

  • 虚拟机分配内存之后会对对象的实例字段初始化为零值,但TLAB在内存分配的时候就可以赋值直接使用

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦