前言
本文为JVM入门基础知识,Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
本文上接:【JavaSE】之JVM入门(上)
五、堆
1.堆简介
Heap(堆),一个JVM只有一个堆内存,堆内存的大小是可以调节的
类加载器读取了类文件后,一般会把什么东西放到堆中?类、方法、常量、变量、保存我们所有引用类型的真实对象
堆内存中细分为三个区域:新生区(伊甸园区)Young/New;养老区 old;永久区 Perm
2.新生区与养老区
新生区是类诞生,成长,消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。
新生区又分为两部分:伊甸区(Eden Space)和幸存者区(Survivor Space),所有的类都是在伊甸区被new出来的,幸存区有两个:0区 和 1区,当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC)。将伊甸园中的剩余对象移动到幸存0区,若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区,那如果1区也满了呢?(这里幸存0区和1区是一个互相交替的过程)再移动到养老区,若养老区也满了,那么这个时候将产生MajorGC(Full GC),进行养老区的内存清理,若养老区执行了Full GC后发现依然无法进行对象的保存,就会产生OOM异常 “OutOfMemoryError ”。如果出现 java.lang.OutOfMemoryError:java heap space异常,说明Java虚拟机的堆内存不够,原因如下:
1、Java虚拟机的堆内存设置不够,可以通过参数 -Xms(初始值大小),-Xmx(最大大小)来调整。
2、代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)或者死循环。
3.永久区
永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存。
如果出现 java.lang.OutOfMemoryError:PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包,例如:在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致Perm区被占满。
4.永久代与元空间
什么是永久代和元空间?
方法区是一种规范,不同的虚拟机厂商可以基于规范做出不同的实现,永久代和元空间就是出于不同jdk版本的实现。
方法区就像是一个接口,永久代与元空间分别是两个不同的实现类。
只不过永久代是这个接口最初的实现类,后来这个接口一直进行变更,直到最后彻底废弃这个实现类,由新实现类—元空间进行替代。
jdk1.8之前:
jdk1.8以及之后:在堆内存中,逻辑上存在,物理上不存在(元空间使用的是本地内存)
5.常量池
- 在jdk1.7之前,运行时常量池+字符串常量池是存放在方法区中,HotSpot VM对方法区的实现称为永久代
在jdk1.7中,字符串常量池从方法区移到堆中,运行时常量池保留在方法区中
jdk1.8之后,HotSpot移除永久代,使用元空间代替;此时字符串常量池保留在堆中,运行时常量池保留在方法区中,只是实现不一样了,JVM内存变成了直接内存
6.堆内存调优
-Xms:设置初始分配大小,默认为物理内存的 “1/64”。
-Xmx:最大分配内存,默认为物理内存的 “1/4”。
-XX:+PrintGCDetails:输出详细的GC处理日志。
代码查看内存使用情况:
默认的情况下分配的内存是总内存的 1/4,而初始化的内存为 1/64
public class Demo01 { public static void main(String[] args) { // 返回虚拟机试图使用的最大内存 long max = Runtime.getRuntime().maxMemory(); // 字节:1024*1024 // 返回jvm的总内存 long total = Runtime.getRuntime().totalMemory(); System.out.println("max=" + max + "字节\t" + (max/(double)1024/1024) + "MB"); System.out.println("total=" + total + "字节\t" + (total/(double)1024/1024) + "MB"); // 默认情况下:分配的总内存是电脑内存的1/4,初始化的内存是电脑的1/64 } }
IDEA中进行VM调优参数设置(设置完成后需要重启IDEA):
VM参数调优:把初始内存,和总内存都调为 1024M,运行,查看结果!
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
经计算得到:新生区加老年区内存总量等于jvm的总内存,可以证明:元空间并不在虚拟机中,而是使用本地内存。
六、使用JProfiler工具分析OOM原因
1.Dump内存快照
在运行java程序的时候,有时候想测试运行时占用内存情况,这时候就需要使用测试工具查看了。在eclipse里面有 Eclipse Memory Analyzer tool(MAT)插件可以测试,而在idea中也有这么一个插件,就是JProfiler,一款性能瓶颈分析工具!
作用:
分析Dump文件,快速定位内存泄漏
获得堆中对象的统计数据
获得对象相互引用的关系
采用树形展现对象间相互引用的情况
2.安装JProfiler
IDEA插件安装
安装JProfiler监控软件
下载地址:https://www.ej-technologies.com/download/jprofiler/version_92
可以使用以下注册码:
// 注册码仅供参考 L-Larry_Lau@163.com#23874-hrwpdp1sh1wrn#0620 L-Larry_Lau@163.com#36573-fdkscp15axjj6#25257 L-Larry_Lau@163.com#5481-ucjn4a16rvd98#6038 L-Larry_Lau@163.com#99016-hli5ay1ylizjj#27215 L-Larry_Lau@163.com#40775-3wle0g1uin5c1#0674
配置IDEA运行环境
Settings–Tools–JProflier–JProflier executable选择JProfile安装可执行文件。(如果系统只装了一个版本, 启动IDEA时会默认选择)保存。
3.JProfiler使用测试
- 编写问题代码进行测试
package com.wang.JVM.Demo02; import java.util.ArrayList; public class Demo03 { byte[] byteArray = new byte[1*1024*1024]; // 1M = 1024K public static void main(String[] args) { ArrayList<Demo03> list = new ArrayList<>(); int count = 0; try { while (true) { list.add(new Demo03()); // 问题所在 count = count + 1; } } catch (Error e) { System.out.println("count:" + count); e.printStackTrace(); } } }
设置vm参数
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
设置完后再运行问题代码发生内存溢出时会自动生成dump文件
使用 Jprofiler 工具分析查看
双击.hprof文件默认使用 Jprofiler 进行 Open大的对象!
从软件开发的角度上,dump文件就是当程序产生异常时,用来记录当时的程序状态信息(例如堆栈的状态),用于程序开发定位问题