6.1.2. 堆空间内部结构(JDK7)
6.1.3. 堆空间内部结构(JDK8)
6.2. 设置堆内存大小与 OOM
6.2.1. 堆空间大小的设置
Java 堆区用于存储 Java 对象实例,那么堆的大小在 JVM 启动时就已经设定好了,大家可以通过选项"-Xmx"和"-Xms"来进行设置。
“-Xms"用于表示堆区的起始内存,等价于-XX:InitialHeapSize
“-Xmx"则用于表示堆区的最大内存,等价于-XX:MaxHeapSize
一旦堆区中的内存大小超过“-Xmx"所指定的最大内存时,将会抛出 OutOfMemoryError 异常。
通常会将-Xms 和-Xmx 两个参数配置相同的值,其目的是为了能够在 Java 垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能。
默认情况下
初始内存大小:物理电脑内存大小 / 64
最大内存大小:物理电脑内存大小 / 4
代码演示:
/** * 1. 设置堆空间大小的参数 * -Xms 用来设置堆空间(年轻代+老年代)的初始内存大小 * -X 是jvm的运行参数 * ms 是memory start * -Xmx 用来设置堆空间(年轻代+老年代)的最大内存大小 * * 2. 默认堆空间的大小 * 初始内存大小:物理电脑内存大小 / 64 * 最大内存大小:物理电脑内存大小 / 4 * 3. 手动设置:-Xms600m -Xmx600m * 开发中建议将初始堆内存和最大的堆内存设置成相同的值。(避免堆区的扩容和释放,降低性能消耗) * * 4. 查看设置的参数:方式一: jps / jstat -gc 进程id * 方式二:-XX:+PrintGCDetails //程序执行完之后打印 * @author shkstart shkstart@126.com * @create 2020 20:15 */ public class HeapSpaceInitial { public static void main(String[] args) { //返回Java虚拟机中的堆内存总量 long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024; //返回Java虚拟机试图使用的最大堆内存量 long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024; System.out.println("-Xms : " + initialMemory + "M"); System.out.println("-Xmx : " + maxMemory + "M"); System.out.println("系统内存大小为:" + initialMemory * 64.0 / 1024 + "G"); System.out.println("系统内存大小为:" + maxMemory * 4.0 / 1024 + "G"); // try { // Thread.sleep(1000000); // } catch (InterruptedException e) { // e.printStackTrace(); // } } }
结果分析
不进行任何配置,运行之
手动设置:-Xms600m -Xmx600m,运行之. 我们出现了疑惑,后面进行解密!哈哈哈.
打开try…catch…,进行查看分析.
使用PrintGCDetails,查看堆内存的详细信息.
6.2.2. OutOfMemory 举例
/** * -Xms600m -Xmx600m * @author shkstart shkstart@126.com * @create 2020 21:12 */ public class OOMTest { public static void main(String[] args) { ArrayList<Picture> list = new ArrayList<>(); while(true){ try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } list.add(new Picture(new Random().nextInt(1024 * 1024))); } } } class Picture{ private byte[] pixels; public Picture(int length) { this.pixels = new byte[length]; } }
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.atguigu.java.Picture.<init>(OOMTest.java:29) at com.atguigu.java.OOMTest.main(OOMTest.java:20)
运行观察结果:
6.3. 年轻代与老年代
存储在 JVM 中的 Java 对象可以被划分为两类:
一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速
另外一类对象的生命周期却非常长,在某些极端的情况下还能够与 JVM 的生命周期保持一致
Java 堆区进一步细分的话,可以划分为年轻代(YoungGen)和老年代(oldGen)
其中年轻代又可以划分为 Eden 空间、Survivor0 空间和 Survivor1 空间(有时也叫做 from 区、to 区)
下面这参数开发中一般不会调:
配置新生代与老年代在堆结构的占比。
默认-XX:NewRatio=2,表示新生代占 1,老年代占 2,新生代占整个堆的 1/3
可以修改-XX:NewRatio=4,表示新生代占 1,老年代占 4,新生代占整个堆的 1/5
在 HotSpot 中,Eden 空间和另外两个 survivor 空间缺省所占的比例是 8:1:1 (因为有个自适应的内存分配原则,实际测试6:1:1)
当然开发人员可以通过选项“-xx:SurvivorRatio”调整这个空间比例。比如-xx:SurvivorRatio=8
几乎所有的 Java 对象都是在 Eden 区被 new 出来的。绝大部分的 Java 对象的销毁都在新生代进行了。
IBM 公司的专门研究表明,新生代中 80%的对象都是“朝生夕死”的。
可以使用选项"-Xmn"设置新生代最大内存大小,这个参数一般使用默认值就可以了。
图解:对象在Eden一段时间内没有被销毁,进入Survivor区,长时间仍未销毁进入老年区.
代码演示:
/** * -Xms600m -Xmx600m * * -XX:NewRatio : 设置新生代与老年代的比例。默认值是2. * -XX:SurvivorRatio :设置新生代中Eden区与Survivor区的比例。默认值是8 * -XX:-UseAdaptiveSizePolicy :关闭自适应的内存分配策略 (暂时用不到) * -Xmn:设置新生代的空间的大小。 (一般不设置) * 备注:-Xmn优先级比-XX:NewRatio高,所以当两个同时设置时(不推荐),会以前者为主 * @author shkstart shkstart@126.com * @create 2020 17:23 */ public class EdenSurvivorTest { public static void main(String[] args) { System.out.println("我只是来打个酱油~"); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } }
结果分析
6.4. 图解对象分配过程
6.4.1. 对象分配基本过程分析
为新对象分配内存是一件非常严谨和复杂的任务,JVM 的设计者们不仅需要考虑内存如何分配、在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还需要考虑 GC 执行完内存回收后是否会在内存空间中产生内存碎片。
new 的对象先放伊甸园区。此区有大小限制。
当伊甸园的空间填满时,程序又需要创建对象,JVM 的垃圾回收器将对伊甸园区进行垃圾回收(MinorGC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区
然后将伊甸园中的剩余对象移动到幸存者 0 区。
如果再次触发垃圾回收,此时上次幸存下来的放到幸存者 0 区的,如果没有回收,就会放到幸存者 1 区。
如果再次经历垃圾回收,此时会重新放回幸存者 0 区,接着再去幸存者 1 区。
啥时候能去养老区呢?可以设置次数。默认是 15 次。
可以设置参数:-Xx:MaxTenuringThreshold= N进行设置
在养老区,相对悠闲。当养老区内存不足时,再次触发 GC:Major GC,进行养老区的内存清理
若养老区执行了 Major GC 之后,发现依然无法进行对象的保存,就会产生 OOM 异常。
java.lang.OutofMemoryError: Java heap space
总结
- 针对幸存者 s0,s1 区的总结:复制之后有交换,谁空谁是 to
- 关于垃圾回收:频繁在新生区收集,很少在老年代收集,几乎不再永久代和元空间进行收集