jvm 03-java堆内存模型
- java中最大的特点在于其具备良好的垃圾收集特性
- GC是整个java之中最重要的安全保证
- 整个JVM中的GC的处理机制:对不需要的对象进行标记,而后进行清除
JVM堆内存划分
- 在JDK1.8之后,将最初的永久带内存空间取消了,该图为JDK1.8之前的内存空间组成
- 取消永久代目的是为了将HotSpot于JRockit两个虚拟机标准联合为一个
- 在整个JVM堆内存之中实际上将内存分为了三部分:
- 新生带(年轻代):新对象和没达到一定年龄的对象都在年轻代
- 老年代:被长时间使用的对象,老年代的内存空间应该要比年轻代更大
- 元空间(JDK1.8之前叫永久代):像一些方法中的操作临时对象等,JDK1.8之前是占用JVM内存,JDK1.8之后直接使用物理内存
GC流程
- 基本所有数据都会保存在JVM的堆内存之中
- 对于整个的GC流程里面,最需要处理的事年轻代与老年代的内存清理操作
- 元空间(永久代)都不在GC范围内
具体流程:
- 当现在有一个新的对象产生,JVM需要为该对象进行内存空间的申请
- 先判断Eden区是否有内存空间,如果有,直接将新对象保存在Eden区
- 如果Eden区的内存空间不足,会自动执行一个Minor GC操作,将Eden区的无用内存空间进行清理
- 清理Eden区之后继续判断Eden区内存空间情况,如果充足,则将新对象直接保存在Eden区
- 如果执行了Minor GC之后发现Eden区的内存依然不足,那就判断存活区的内存空间,并将Eden区的部分活跃对象保存在存活区
- 活跃对象迁移到存活区后,继续判断Eden区内存空间情况,如果充足,则将新对象直接保存在Eden区
- 如果存活区也没有空间了,则继续判断老年区,如果老年区充足,则将存活区的部分活跃对象保存在老年区
- 存活区的活跃对象迁移到老年区后,则将Eden区的部分活跃对象保存在存活区
- 活跃对象迁移到存活区后,继续判断Eden区内存空间情况,如果充足,则将新对象直接保存在Eden区
- 如果老年区也满了,这时候产生Major GC(Full GC)进行老年区的内存清理
- 如果老年区执行了Major GC之后发现无法进行对象保存,会产生OutOfMemoryError异常
堆内存参数调整(调优关键)
- 实际上每一块子内存区中都会存在有一部分的可变伸缩区
- 如果空间不足时,则在可变范围之内扩大内存空间
- 当一段时间后,内存空间有余,再将可变空间进行释放
堆内存空间调整参数
- -Xms:设置初始分配大小,默认为物理内存的1/64
- -Xmx:最大分配内存,默认为物理内存的1/4
- -XX:+PrintGCDetails:输出详细的GC处理日志
- -XX:+PrintGCTimeStamps:输出GC的时间戳信息
- -XX:+PrintGCDateStamps:输出GC的时间戳信息(以日期的形式)
- -XX:+PrintHeapAtGC:在GC进行处理的前后打印堆内存信息
- -Xloggc:(SavePath):设置日志信息保存文件
- 在堆内存的调整策略中,基本上只要调整两个参数:-Xms和-Xmx
可通过Runtime类获取内存的整体信息
代码如下:
package cn.liang.jvm; public class memoryTest { public static void main(String[] args) { Runtime runtime = Runtime.getRuntime(); long maxMemory = runtime.maxMemory(); long totalMemory = runtime.totalMemory(); System.out.println("max_memory=" + maxMemory /(double)1024/1024 + "M"); System.out.println("total_memory=" + totalMemory /(double)1024/1024 + "M"); } }
输出结果:
max_memory=3641.0M total_memory=245.5M
说明整个内存空间的可变范围(伸缩区):245.5M ~ 3641.0M之间,有可能造成整个程序的性能
为了避免伸缩区的可调策略,使初始化内存等于最大内存,从而提升整个程序性能
输出结果:max_memory=981.5M total_memory=981.5M Heap PSYoungGen total 305664K, used 15729K [0x00000007aab00000, 0x00000007c0000000, 0x00000007c0000000) eden space 262144K, 6% used [0x00000007aab00000,0x00000007aba5c420,0x00000007bab00000) from space 43520K, 0% used [0x00000007bd580000,0x00000007bd580000,0x00000007c0000000) to space 43520K, 0% used [0x00000007bab00000,0x00000007bab00000,0x00000007bd580000) ParOldGen total 699392K, used 0K [0x0000000780000000, 0x00000007aab00000, 0x00000007aab00000) object space 699392K, 0% used [0x0000000780000000,0x0000000780000000,0x00000007aab00000) Metaspace used 2708K, capacity 4486K, committed 4864K, reserved 1056768K class space used 293K, capacity 386K, committed 512K, reserved 1048576K
观察GC的触发操作
代码如下:
package cn.liang.jvm; import java.util.Random; public class gctest { public static void main(String[] args) { Random random = new Random(); String str = "hello liang"; while (true) { str +=str + random.nextInt(99999999); str.intern(); } } }
输出结果:
[GC (Allocation Failure) [PSYoungGen: 1769K->511K(2560K)] 1769K->775K(9728K), 0.0015982 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2374K->240K(2560K)] 2638K->1119K(9728K), 0.0011725 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2100K->256K(2560K)] 7841K->5996K(9728K), 0.0005402 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 256K->240K(2560K)] 5996K->5980K(9728K), 0.0005811 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 240K->0K(2560K)] [ParOldGen: 5740K->3925K(7168K)] 5980K->3925K(9728K), [Metaspace: 2662K->2662K(1056768K)], 0.0064126 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 41K->32K(2560K)] 6397K->6388K(9728K), 0.0003653 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 32K->32K(1536K)] 6388K->6388K(8704K), 0.0003294 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 32K->0K(1536K)] [ParOldGen: 6356K->2710K(7168K)] 6388K->2710K(8704K), [Metaspace: 2662K->2662K(1056768K)], 0.0035285 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 19K->0K(2048K)] 5160K->5140K(9216K), 0.0004489 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 5140K->5140K(9216K), 0.0003114 secs] [Times: user=0.00 sys=0.01, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 5140K->5140K(7168K)] 5140K->5140K(9216K), [Metaspace: 2662K->2662K(1056768K)], 0.0030502 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 5140K->5140K(9216K), 0.0003198 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 5140K->5127K(7168K)] 5140K->5127K(9216K), [Metaspace: 2662K->2662K(1056768K)], 0.0039555 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3332) at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448) at java.lang.StringBuilder.append(StringBuilder.java:136) at cn.liang.jvm.gctest.main(gctest.java:11) Heap PSYoungGen total 2048K, used 40K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000) eden space 1024K, 3% used [0x00000007bfd00000,0x00000007bfd0a120,0x00000007bfe00000) from space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000) to space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000) ParOldGen total 7168K, used 5127K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000) object space 7168K, 71% used [0x00000007bf600000,0x00000007bfb01c78,0x00000007bfd00000) Metaspace used 2693K, capacity 4486K, committed 4864K, reserved 1056768K class space used 292K, capacity 386K, committed 512K, reserved 1048576K
日后如果发现你的程序执行速度变慢了,可以针对程序的运行内存进行分析
- 可视化工具:..\Java\jdk1.8.0_131\bin\jvisualvm.exe
- 命令查看:jmap(jmap -heap JavaPID)