0%

堆与虚拟机栈不同,堆的生命周期与JVM实例是一致的,一个JVM实例只存在一个堆内存,在JVM启动的时候就会被创建,其大小也就被确定了,所有的线程共享堆内存(不过还存在线程私有的缓冲区Thread Local Allocation Buffer,简称TLAB)

对于对象而言,对象的引用存在于栈帧中,这个引用指向了对象所在的堆中的位置

在方法执行结束后,堆中的对象不会被马上移除,会在垃圾回收时在进行移除

堆的组成结构

在java7的时候分为新生代、老年代和永久代,而在java8的时候改为了新生代、老年代和元空间

在Hotspot中元空间是方法区的实现,jdk7及以前使用的是永久代,jdk8改为元空间,元空间使用的不是虚拟机的内存,而是使用的本地内存

新生代和老年代

堆区可以进一步换分为新生代和老年代,而新生代有划分为Eden空间、Survivor0空间和Survivor1空间(也称为from区和to区)

堆区新生代和老年代
对象的走向
  • 首先new出来的对象会先放入Eden空间

  • 当Eden空间填满之后,且还在创建对象,垃圾回收GC就会对Eden空间进行垃圾回收(Minor GC),Eden不再使用的对象进行销毁,剩余的还在使用的对象移入Survivor0空间(也可能是Survivor1空间,哪个区是空的放哪个区,from区和to区是每次变化的,谁空谁是to),此时这些对象的年龄是1

    如果对象太大,Eden空间放不下,也有可能直接放入老年代

  • 如果再次触发垃圾回收,且Survivor0空间中对象依然存在其他对象引用,则将Survivor0中剩余的对象移至Survivor1空间

  • 再次触发垃圾回收,Survivor1空间中对象没有被垃圾回收的,被移入Survivor0中,如此重复,每次换区年龄都会加一

  • 这个重复的次数可以进行设置 -XX:MaxTenuringThreshold=20 (默认是15)

  • 当老年代内存不足时,会触发Major GC进行老年代的内存清理

  • 如果老年代触发Major GC后依然无法满足对象的存储,则会触发OOM异常

TLAB

由于堆是对于所有线程共享的,在高并发的情况下,为了保证数据线程安全,需要进行频繁的加锁,严重影响性能,所以存在了TLAB(线程私有的缓冲区Thread Local Allocation Buffer)使得每个线程都存有一份自己的数据,TLAB是在Eden区的,默认情况下TLAB占用Eden区的1%

可以使用-XX:UseTLAB来设置开启TLAB空间,使用-XX:TLABWasteTargetPercent来设置TLAB空间占用Eden空间的百分比大小,如果TLAB空间分配失败,才会使用锁机制来确保数据的原子性,在Eden进行分配

逃逸分析

并不是所有的对象都存储在堆空间,通过逃逸分析,java Hotspot虚拟机能够分析出一个对象的引用的使用范围从而决定是否要分配到堆中

逃逸分析主要是根据对象的作用域来进行确定

  • 当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸,没有发生逃逸的可以分配到栈上
  • 当一个对象在方法中被定义后,会被外部方法引用,则认为会发生逃逸

jdk6u23时Hotspot默认开始逃逸分析,之前的版本可以使用-XX:+DoEscapeAnalysis来进行开启

堆的参数设置

对于堆的内存大小的设置主要有两个参数(这个大小只是设置的新生代和老年代的内存,并不会计算元空间的大小)

-Xms:表示堆的起始内存,等价于-XX:InitialHeapSize,默认是电脑物理内存的1/64

-Xmx:表示堆的最大内存,等价于-XX:MaxHeapSize,默认是电脑物理内存的1/4

一般情况下这两个参数设置为相同的值,使得java垃圾回收机制完成后不需要重新计算堆区的大小,从而提升性能

配置新生代和老年代内存占比

-XX:NewRatio=2 表示新生代:老年代 = 1:2,默认为2

新生代设置

-Xmn可以设置新生代的最大内存,一般使用默认

-XX:SurvivorRatio 设置新生代中Eden和Survivor0/Survivor1的比例,对于新生代三个分区的划分默认是Eden: Survivor0:Survivor1 = 8:1:1,可以使用-XX:SurvivorRatio = 8来设置

-XX:MaxTenuringThreshold 设置新生代对象的最大年龄,默认是15

打印信息

-XX:+PrintFlagsInitial查看所有的参数的默认初始值,+代表开启,-代表关闭

-XX:+PrintFlagsFinal查看所有参数的最终值,+代表开启,-代表关闭

这个看到的可能不是最终的,可能还会改变,如果具体查看某个参数的话,可以使用jps来找到该进程的id,然后使用jinfo -flag 参数名 进程id来查看

-XX:+PrintGCDetails 输出详细的GC处理日志,+代表开启,-代表关闭

-XX:+PrintGC 输出GC处理的简要信息,+代表开启,-代表关闭

-XX:+PrintEscapeAnlysis 查看逃逸分析的筛选结果,+代表开启,-代表关闭