java堆
java堆是虚拟机管理内存中最大的一块。被所有的线程共享.这块地方唯一的目的就是所有的对象实例到要在这里分配内存。
也是垃圾收集器管理的内存区域。也被称为GC堆,所以会有一些永久代新生代老年代的名词
方法区
和堆一样,属于线程共享的区域。储存一些已经被虚拟机加载的类型信息,常量,静态变量等。
方法区的实现在java各个版本是由不同的,
-
jdk1.6时,是永久代,包含字符串常量池,静态变量,运行时常量池都是在永久代。
-
1.7之后字符串常量池和静态变量被移动到了堆里,但是运行时常量池还是存在在永久代。
-
1.8 字符串常量池和静态变量仍然在堆中,其他的都已经被移动到元空间里面。元空间是用本地内存实现的
虚拟机栈
每个线程被创造出来就会同步创建一个栈帧,用于存储局部变量表,操作数栈,动态链接等。每个方法调用到执行完毕的过程,就对应着一个栈帧在虚拟机栈从入栈到出栈的过程。
局部变量表中存放着编译期可知的基本数据类型,对象引用(引用指针或者一个句柄)
本地方法栈
和虚拟机栈作用相似,只不过是为虚拟机使用本地(native)方法服务的。
程序计数器
pc,可以看作当前线程所执行字节码的行号指示器,用来选取下一条要执行的字节码。
是唯一一个不会oom的区域,补充一下OOM知识点
内存溢出 (OOM)
内存区域满了,放不下了,除了pc(程序计数器)之外jvm其他的区域都有可能发生。
其中方法区和堆是线程共享的,虚拟机栈,本地方法栈和程序计数器值线程独享的
对象的创建
指针碰撞
假设Java堆中内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump ThePointer)。
空闲列表
如果Java堆中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)。
本地线程分配缓冲(TLAB)
除了分配空间外,还有频繁的指针指向,多线程的情况下,这种情况也是不安全的所以要保证操作的原子性,每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local AllocationBuffer,TLAB)哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。
对象的内存布局
对象头
虚拟机对象的对象头部分包括两类信息。第一类是用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等(Mark word)
另外一部分是类型指针,即对象指向它的类型元数据的指针。Java虚拟机通过这个指针来确定该对象是哪个类的实例。
实例数据
是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。
对齐填充
这并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。
对象的访问定位
句柄
使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
直接指针
好处就是速度快,只需要一次定位
内存分配原则
对象的内存分配,从概念上讲,应该都是在堆上分配。