通过new 关键字创建的对象都会使用堆内存。 (1)JVM规范描述:所有的实例对象和数组都应当在运行时分配在堆上,但在HotSpot中不是所有实例对象和数组都是分配到堆上的,也可能会分配在虚拟机栈上。(后文会解释) (2)JVM规范规定:堆可以处于物理上不连续的内存空间,但在逻辑上应该视为连续的。虚拟内存和物理内存的映射解决了这个问题。 (3)一个JVM实例只存在一个堆内存,在JVM启动的时候创建,其空间大小也就确定了,但启动时就可以指定创建堆的大小。xms、xmx (4)线程共享,堆中对象都需要考虑线程安全问题。但堆空间不是完全线程共享的,也存在线程私有的缓存区,来解决线程并发性能问题。 (5)数组和对象保存在堆上,但栈帧中会报保存会保存它们的引用。 (6)在方法结束后,堆中的对象不会马上移除,仅仅在垃圾回收的时候才会被移除。 (5)堆是垃圾回收的重点区域
现在的垃圾回收器大部分都是基于分代收集理论设计
java8之前逻辑上堆内存分为:新生代+老年代+永久代Java8开始逻辑上堆内存分为:新生代+老年代+元空间,其中元空间实际上不占用堆空间,是占用的操作系统内存,JVM不负责分配java的所有版本中,新生代也叫年轻代 ,新生代实际又分为Eden区(伊甸园)和两个Survivor区(幸存区)Survivor区又分为Survivor0区和Survivor1区,也可以成为from区和to区永久代和元空间实际上是JVM规范中方法区的实现,逻辑上属于堆(1)Java程序启动的时候,JVM会先操作系统申请分配一块堆内存(-Xms即初始堆内存大小)。 (2)当申请的堆内存使用完时,JVM会再次向操作系统申请堆内存,但是JVM不会无限制的问操作系统申请堆内存。 (3)当应用占用实际内存达到设置的最大堆内存(-Xmx即堆可以申请的最大堆内存)时,JVM再向操作系统申请堆内存,而JVM又无法通过垃圾回收机制回收当前的堆内存来满足应用的空间需求时,就会给程序抛出异常:java.lang.OutOfMemoryError
未设置Xms和Xmx时,默认堆的初始大小为为可用物理内存的1/64,最大内存为:可以物理内存的1/4当应用超过堆内存初始大小后,堆内存会进行扩容,当GC垃圾回收后又会调整堆内存的大小,这样频繁调整堆内存大小会影响性能,因此: 通常会将-Xms和-Xmx两个参数配置相同的值,其目的是:为了能够在Java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能。每次GC时,都会通过GC算法判断对象是否被引用,不被引用才会被清理。 英文意思:minor是微小的,major是重要的
Minor GC是从年轻代空间(包括 Eden 和 Survivor 区域)回收内存。Major GC 是清理永久代Full GC 是清理整个堆空间,包括年轻代和永久代。垃圾回收频繁回收年轻代,很少回收老年代,几乎不在方法区(老年代/元空间)进行垃圾回收。统计表明,80%的对象在年轻代就被回收了。 (1)当Enden区满时,会触发Minor GC,此时会回收Survivor区的垃圾对象。Minor GC时会出现Stop The World现象,所有用户线程停止,但很短暂,回收结束后用户进程恢复运行 当经历过一次MinorGC后,Eden区和Suvivor区的幸存对象就会被存入两个survivor区中空的一个,另一个Eden区会被清空,当再次MinorGC时,幸存对象又会被放入刚才清空的那个Survivor区,如此交替进行,始终有一个survivor区是空的。 (2)每个对象进入survivor后会进行年龄计数,刚进入时年龄为1,此后每MinorGC一次年龄加1。当年龄达到15时,GC幸存对象会被放入老年代。默认年轻代GC15次的幸存对象放入老年代,但可以通过设置参数进行调整: -XX:MaxTenuringThreshold=3。 (3)GC幸存对象不一定要经历15次Minor GC才会晋升到老年代中。当survivor区空间不够时,GC幸存对象便会提前进入老年代,但这个次数一定不大于设置的最大阈值。另外,Eden区的幸存对象无法放入Survivor区时,也会直接放入老年代。java对象的对象头的运行时元数据markword中年龄是用4个bit位表示的,能表示的最大十进制数就是15。
老年代空间不足时就会进行Full GC,如果GC之后的空间仍然不足以存放新对象,就会抛出OutofMemoryError异常。
JVM进行GC时,并非每次都是对新生代、老年代、方法区一起进行回收,大部分时候回收的是新生代,因此按回收的范围可以分为:
部分回收:不是完整的回收整个Java堆 (1)新生代GC,也就是Minor GC,也称为Young GC (2)老年代GC,也就是Major GC,也称为Old GC,目前只有CMS GC有单独回收老年代的行为 (3)混合GC:回收整个新生代和部分老年代,目前只有G1 GC有这种行为整堆GC:也就是Full GC,回收整个堆和方法区的垃圾Minor GC触发机制:Eden区满的时候才会触发MinorGC,Survivor区满的时候不会触发Minor GC。Survivor区满时,只有等Eden区满触发GC时,才会被垃圾回收,GC后仍然放不下新对象,则新对象会被直接放入老年代。
Major GC触发时机:当老年代空间不足时,会先尝试触发Minor GC,如果之后空间不足时,才会触发Major GC
Full GC触发时机: (1)代码中调用System.gc()方法,系统建议执行Full GC,但不是必然执行 (2)老年代空间不足 (3)方法区空间不足 (4)通过minor gc后进入老年代的平均大小大于老年代的可用内存 (5)由Eden区、survivor区向survivor区复制对象时,对象大于survivor区的大小,则把该对象放入老年代,且老年代的可用内存小于该对象
注意: full gc是开发或调优过程中尽量避免的,这样Stop the world现象的时间会短一些
常见的堆外存储技术:逃逸分析
如果经过逃逸分析后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样无需在堆上分配内存,也无需垃圾回收。
结论: 开发中能使用局部变量的,就不要使用在方法外定义。