JVM堆内存划分
- 在JDK1.8之后,将最初的永久带内存空间取消了,该图为JDK1.8之前的内存空间组成
- 取消永久代目的是为了将HotSpot于JRockit两个虚拟机标准联合为一个
- 在整个JVM堆内存之中实际上将内存分为了三部分:
- 新生带(年轻代):新对象和没达到一定年龄的对象都在年轻代
- 老年代:被长时间使用的对象,老年代的内存空间应该要比年轻代更大
- 元空间(JDK1.8之前叫永久代):像一些方法中的操作临时对象等,JDK1.8之前是占用JVM内存,JDK1.8之后直接使用物理内存
年轻代
- 年轻代属于JVM堆内存空间的一个组成部分
- 所有使用关键字new的新实例化对象都会在Eden区进行保存
- 在存活区保存的一定是已经在Eden区中存活好久的,并经历或多次Minor GC的活跃对象
- 存活区一般有两块空间,这两块空间大小相等且两块中一定有一块是空的
- 存活区两块空间的作用:
- 一块存活区为了晋升
- 一块存活区为了对象回收
Minor GC算法
- 年轻代中的Minor GC算法采用的事复制算法
- 复制算法:从根集合扫描出存活对象,并将该对象复制到一块新的完全为使用的空间中
(红色为不存活对象所占用的内存空间;绿色为存活对象所占用的内存空间)
- 由于Eden区保存的对象可能大部分为临时对象,容易频繁发生Minor GC
- 为此HotSpot虚拟机采用了两种技术加快空间的内存分配操作
- Bump-The-Pointer
- 该技术跟踪Eden区保存的最后一个对象,该对象一般会保存在Eden区的顶部
- 当创建新对象时,需要检测最后保存的对象后面是否有足够的空间
- 这样可以很快的判断Eden区是否还有剩余空间
- 该技术可以极大提高内存分配速度
- TLAB(Thread-Local Allocation Buffers)
- 为了适应多线程环境,TLAB算法将Eden区分为多个数据块
- 每个数据块分别使用Bump-The-Pointer技术进行对象保存与内存分配
年轻代内存调整参数
- -Xmn:设置年轻代堆内存大小,默认为物理内存的1/64
- -Xss:设置每个线程栈大小,JDK 1.5之后默认为每个线程分配1M的栈大小
- 减少此数值可以产生更多的线程对象,但是不能无限生成
- -XX:SurvivorRatio:设置Eden与Survivor空间的大小比例,默认8:1:1
- -XX:NewSize:设置年轻代内存区大小
- -XX:NewRatio:设置年轻代与老年代的比率
例如:改变存活区比率
-Xmx10m -Xms10m -XX:SurvivorRatio=6 -XX:+PrintGCDetails
输出结果:
max_memory=9.5M total_memory=9.5M Heap PSYoungGen total 2560K, used 606K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000) eden space 2048K, 29% used [0x00000007bfd00000,0x00000007bfd97a38,0x00000007bff00000) from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000) ParOldGen total 7168K, used 0K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000) object space 7168K, 0% used [0x00000007bf600000,0x00000007bf600000,0x00000007bfd00000) Metaspace used 2708K, capacity 4486K, committed 4864K, reserved 1056768K class space used 293K, capacity 386K, committed 512K, reserved 1048576K
老年代
- 主要接收由年轻代发送来的对象
- 经历过多次Minor GC后还保存下来的对象才会进入到老年代
- 如果需要保存的对象超过了Eden区大小,那此对象也将直接保存到老年代中
- 当老年代内存不足时将引发Full GC(Major GC)
- 老年代采用两种算法结合模式实现GC处理:整理-压缩
Full GC算法
- 标记-清楚(Mark-Sweep)
- 从根集合开始扫描,对存活的对象进行标记,标记完毕后,在扫描整个空间中未标记的对象,并进行回收
- 优缺点:在空间中存活对象较多的情况下较为高效,但由于该算法为直接回收不存活对象所占用的内存,因此会造成内存碎片。
- 标记-压缩(Mark-Compact)
- 在回收不存活对象所占用的内存空间后,会将其他所有存活对象都往左端空闲的空间进行移动,并更新引用其对象指针。
- 优缺点:在标记-清除的基础上还需要进行对象移动,成本相对较高,好处则是不产生内存碎片
老年代内存调整参数
- -XX:NewRatio:设置年轻代与老年代的比率
- -XX:+UseAdaptiveSizePolicy:控制是否采用动态控制策略
- 如果动态控制,则动态调整java堆中各个区域的大小以及进入老年代的条件
- -XX:PretenureSizeThreshold:控制直接进入老年代对象的大小,大于该值的对象会直接分配在老年代中
例如:设置老年代参数
-Xmx10m -Xms10m -XX:PretenureSizeThreshold=512k -XX:+PrintGCDetails
元空间
- 元空间是在JDK 1.8之后新增的,功能上和永久代一样
- 永久代使用的是JVM堆内存空间,元空间使用的是物理内存
- 元空间直接受到本机的物理内存限制
元空间内存调整参数
- -XX:MetaspaceSize:设置元空间大小
- -XX:MaxMetaspaceSize:这种元空间最大容量,默认没限制,直接受到本地物理内存限制
- -XX:MinMetaspaceFreeRatio:执行GC之后,最小的剩余元空间百分比,减少为分配空间所导致垃圾收集
- -XX:MaxMetaspaceFreeRatio:执行GC之后,最大的剩余元空间百分比,减少为释放空间所导致的垃圾收集
例如:设置一些参数导致元空间出错
-XX:MetaspaceSize=1m -XX:MaxMetaspaceSize=1m
输出结果:
Error occurred during initialization of VM OutOfMemoryError: Metaspace
总结:
- Java中的GC会有两种回收:
- 年轻代的Minor GC -- 新对象创建时如果Eden区空间不足会触发Minor GC
- 老年代的Full GC -- 如果老年代内存空间不足会触发Full GC,也是gc()方法执行
- 如果所有空间都不足则抛出OutOfMemoryError
- 如果整个java项目运行缓慢,为了避免堆内存的伸缩空间进行操作,可将初始化内存空间和整体堆内存空间大小设置为一样。(使用 -Xms、-Xmx)