JVM探索

小白懂JVM系列-运行时数据区之堆(heap)

2021-03-25  本文已影响0人  余生爱静
Jvm运行时数据结构

是什么

java堆是用来存放系统创建的对象数组,所有线程共享java堆。
java堆是在运行期间动态分配内存大小,自动进行垃圾回收。

java堆结构

堆结构
堆是Java虚拟机所管理的内存中最大的一块存储区域。堆内存被所有线程共享。主要存放使用new关键字创建的对象。所有对象实例以及数组都要在堆上分配。垃圾收集器就是根据GC算法,收集堆上对象所占用的内存空间(收集的是对象占用的空间而不是对象本身)。

Java堆分为新生代(Young Generation)和老年代(Old Generation);新生代又分为伊甸园区(Eden)和存活区(Survivor区);存活区又分为From Survivor空间和 To Survivor空间。

新生代存储“新生对象”,我们新创建的对象存储在新生代中。当新生代内存占满后,会触发Minor GC,清理新生代内存空间。

老年代存储长期存活的对象和大对象。新生代中存储的对象,经过多次GC后仍然存活的对象会移动到老年代中进行存储。老年代空间占满后,会触发Full GC

注:Full GC是清理整个堆空间,包括新生代和老年代。如果Full GC之后,堆中仍然无法存储对象,就会抛出OutOfMemoryError异常。

整个堆大小=Young Generation+Old Generation

Young Generation
1、新生代用来存放新分配的对象;新生代中经过垃圾回收,没有回收掉的对象,被复制到老年代。
2、新生代的内粗暴大小=Eden+存活区(From Space+To Space)
3、新生代的存活区一般采用复制算法机制,进行垃圾回收的

Old Geration
1、老年代存储对象比新生代存储的对象的年龄大的多,老年代储存一些大的对象。
2、老年代采用的垃圾回收算法通常为标记整理

Java堆对象的创建

虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可以完全确定,为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来。

内存分配完成之后,虚拟机需要将分配到的内存空间都初始化为零值。

接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中。

在上面工作完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从java程序的视角来看,对象创建才刚刚开始——方法还没有执行,所有的字段都还为零。所以,一般来说,执行new指令之后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

Java堆对象的内存布局

在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

HotSpot虚拟机的对象头

包括两部分信息,
1、Mark Word:用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
2、类型指针,即对象指向他的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

实例数据

是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来,这部分的存储顺序会受到虚拟机分配策略参数和字段在java源码中定义顺序的影响。

对齐填充

并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。

java堆对象的访问定位

建立对象是为了使用对象,我们的java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在java虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式也是取决于虚拟机实现而定的。目前主流的访问方式有使用句柄直接指针两种

句柄
句柄

使用句柄:Java堆中会划分出一块内存来作为句柄池,reference中存储句柄的地址,句柄中存储对象的实例数据和类元数据

直接指针
指针

使用指针:Java堆中会存放访问类元数据的地址,reference存储的就直接是对象的地址。

这两种对象访问方式各有优势:
1、句柄来访问的最大的好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为) 时只会改变句柄中的实例数据指针,而reference 本身不需要修改。
2、直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。

对于Oracle的HotSpot虚拟机而言,它是使用第二种方式进行对象访问的,但从整个软件开发的范围来看,各种语言和框架使用句柄来访问的情况也十分常见

Java堆参数设置

-Xms 初始堆内存 默认物理内存1/64,也是最小分配堆内存。当空余堆内存小于40%时,会增加到-Xms的最大限制

-Xmx 最大堆内存分配 默认物理内存1/4,当空余堆内存大于70%时,会减小到-Xms的最小限制。

-XX:NewRatio:老年代与新生代的比值,如果xms=xmx,且设置了xmn的情况下,该参数不用设置

-XX:SuevivorRatio:Eden区和Survivor区的大小比值,如果设置为8,则两个Survivor区和一个Eden区的比值为2:8,一个Survivor占整个新生的1/10

-XX:+HeapDumpOnOutOfMemoryError:OOM时导出堆到文件

-XX:+HeapDumpPath:导出OOM的路径

-XX:OnOutMemoryError:在OOM时,执行一个脚本

上一篇下一篇

猜你喜欢

热点阅读