JVM运行时数据区 --- 堆
JVM运行时数据区 — 堆
堆的核心概述
首先 我们先了解堆空间位于运行时数据区的哪一个位置
-
在这里我们强调一个概念 一个进程对应一个JVM实例 一个JVM实例对应一个运行时数据区 运行时数据区独立的只有一个方法区和一个堆 也就是说一个进程里的多个线程要共享一个方法区和堆空间 而每个线程各自拥有一份程序计数器 本地方法栈 和 虚拟机栈
-
Java虚拟机规范中对Java堆的描述是 所有的对象实例和数组都应该在运行时分配在堆上
-
数组和对象可能永远不会存储在栈上 因为栈帧当中保存引用 这个引用指向对象或者数组在堆中的位置
-
在方法结束后 堆中的对象不会被马上的移除 仅仅在垃圾回收的时候才会被移除
-
堆的内存细分
设置堆内存大小与OOM
-
-Xms用来设置堆空间的初始内存大小 -Xmx用来设置堆空间的最大内存大小
-
默认情况下 堆空间的大小
初始内存大小 物理电脑内存大小/64
最大内存大小 物理电脑内存/4
-
查看设置的参数
方式一 jps / jstat -gc 进程id
方式二 -XX:printGCDetails
-
OOM
此时发生了OOM异常 java.lang.OutOfMemoryError: Java Heap space
年轻代与老年代
- 默认情况 新生代与老年代的比例是1:2
- 默认情况 伊甸园与两个幸存者的比例是8:1:1
- 当我们加上 -XX: SurvivorRatio: 8 我们看到的结果就是8:1:1
图解对象分配过程
1.首先 但我们最先开始new 对象的时候 我们将对象放到伊甸园区 当我们伊甸园区放满的时候 就会触发一次YGC 这时 我们先把垃圾先回收掉 也就是上图的红色部分是垃圾就会被回收了同时 我们将还存活的对象移动到幸存者0区 并将他们的年龄+1
2 通过上一次GC 我们伊甸园区已经空了 这是我们又在伊甸园区不断的new对象 直到又一次把伊甸园区放满了
这个时候我们就触发了第二次GC 这个时候我们进行2个操作
第一 把Eden中存活的对象移动到s1区
第二 对s0区的对象做一次判断 看他们是不是垃圾现在 如果不是 就把s0区当中存活的对象移动到s1区
每次执行完gc之后 这两个幸存者区当中谁空谁就是to区 这个to区表明了下一次YGC的时候Eden区中存活的对象 要往哪里放
3 我们来看一种较为特殊的情况 当我们执行一次YGC之后 我们还是按照刚刚的两步(这时s0区是空的 作为to区) 先是将Eden中的对象放到空的s0区当中 然后将s1区当中年龄是1的对象也放到s0区当中 然后把s1区当中年龄为15的对象晋升到了老年区
特别的 这里有一个注意点
什么时候会触发YGC
触发YGC的条件是 当伊甸园区满的时候会触发YGC
survivor区满了的时候不会
触发YGC 这个一定注意
当触发YGC/Minor GC的时候 会将Eden中和survivor区中的对象一起回收
Minor GC — Major GC — Full GC
注意 Major GC只是老年代的垃圾收集 Full GC是整个Java 堆和方法区的收集
- Minor GC的触发机制
- 当年轻代空间不足会触发Minor GC 这里的年轻代满指的是Eden满了 Survivor满不会触发GC
- Java对象大多朝生夕死 所以Minor GC非常频繁
- Minor GC会引发STW 暂停用户线程 等垃圾回收结束 用户线程才恢复运行
内存分配策略
-
优先分配到Eden
-
大对象直接分配到老年代
-
长期存活的对象分配到老年代