JVM内功心法-JVM内存模型之内存溢出
jvm常用参数【想在本地更快复现一些问题就可以调节这些参数】:
(1)-Xms20M 表示设置JVM启动内存的最小值为20M,必须以M为单位
(2)-Xmx20M 表示设置JVM启动内存的最大值为20M,必须以M为单位。将-Xmx和-Xms设置为一样可以避免JVM内存自动扩展。大的项目-Xmx和-Xms一般都要设置到10G、20G甚至还要高
(3)-verbose:gc 表示输出虚拟机中GC的详细情况
(4)-Xss128k 表示可以设置虚拟机栈的大小为128k
(5)-Xoss128k 表示设置本地方法栈的大小为128k。不过HotSpot并不区分虚拟机栈和本地方法栈,因此对于HotSpot来说这个参数是无效的
(6)-XX:PermSize=10M 表示JVM初始分配的永久代的容量,必须以M为单位
(7)-XX:MaxPermSize=10M 表示JVM允许分配的永久代的最大容量,必须以M为单位,大部分情况下这个参数默认为64M
(8)-XX:+HeapDumpOnOutOfMemoryError 表示可以让虚拟机在出现内存溢出异常时Dump出当前的堆内存转储快照
(9)-XX:+UseG1GC 表示让JVM使用G1垃圾收集器
(10)-XX:+PrintGCDetails 表示在控制台上打印出GC具体细节
(11)-XX:+PrintGC 表示在控制台上打印出GC信息
(12)-XX:HeapDumpPath=/home/liuke/jvmlogs/2 生成堆文件地址
1. 堆溢出
在计算机科学中, 动态内存分配(Dynamic memory allocation)又称为堆内存分配,是指计算机程序在运行期中分配使用内存。它可以当成是一种分配有限内存资源所有权的方法。 动态分配的内存在被程序员明确释放或被垃圾回收之前一直有效。与静态内存分配的区别在于没有一个固定的生存期。这样被分配的对象称之为有一个“动态生存期”。 《维基百科》
java.lang.OutOfMemoryError: Java heap space
OOM典型的堆内存溢出
思考1:遇到代码不能一眼看出来是哪里出现泄露如何排查?
答:可以借助jproiler工具来分析具体是那一行代码存在大对象导致内存溢出了
堆溢出示例代码:
/**
* Animal
*
* @author OMZ
* @create 2022/05/03
*/
public class OomTest {
byte[] bytes = new byte[1 * 1024 * 1024 * 1024];//1G的字节数组
public static void main(String[] args) {
while (true) {
list.add(new OomTest()); //循环new OomTest的时候字节数组会加载存入堆中
}
}
1)生成本次代码运行的dump文件
JVM生成dump文件一般有两种方式
一、 出现OOM时自动生成dump文件
JVM启动命令增加两个参数:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/opt/dump
二、获取PID直接生成当前JVM的dump文件
例如:
jmap -dump:format=b,file=/opt/dump/dump.hprof 1234
其中1234是JVM的当前进程号【jps查询或者ps -ef|grep 项目名查询】
三,idea配置jvm参数生成dump文件
将生成的文件到导入jprofiler
可以看到是代码中List占了堆内存,使堆内存溢出了
2. 栈溢出
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
思考1:什么情况下容易栈溢出?
/**
* Animal
*
* @author OMZ
* @create 2022/05/03
*/
public class StackDemo {
public static void main(String[] args) {
new StackDemo().method();
}
public void method() {
method();
}
}
通常的原因都是无限的递归调用导致的,也就是无限的调用函数,导致空间用完。
3. 方法区溢出
方法区用于存放Class的相关信息,如:类名,访问修饰符,常量池,字符描述,方法描述等。对于这个区域的测试,基本思路是运行时产生大量的类去填满方法区,直到溢出。虽然直接使用Java SE API也可以动态产生类(如反射时的GeneratedConstructorAccessor和动态代理等),但在本次试验使用CGLIB直接操作字节码运行时,生成大量的动态类,当前主流的很多框架 如:Spring,Hibernate对类进行增强时,都会使用到类似CGLIB这类字节码技术,增强的类越多,就需要越大的方法区来保证动态生成的Class可以加载入内存。
要让方法区溢出就要产生大量类且不能实例化,如果实例化的话会引起堆溢出
4. 永久代溢出(JDK1.7)
5. 元空间溢出(JDK1.8)
其内存空间直接使用的是本地内存
,要想让它溢出,那么这台服务器的磁盘一定是接近于爆掉下的情况,一般生产的机器磁盘这些都会有监控,根本达不到溢出的情况