从头开始学JVM--------内存管理
一、基本内存分布
堆内存:堆内存是所有线程共享的一块内存区域,由虚拟机在启动的时候创建。唯一目的就是存放对象实例,几乎所有对象实例都在堆里分配内存。数组也在这里分配内存。这里也是垃圾回收器工作的主要区域。
栈内存:栈内存存储基本数据类型,方法引用以及方法里面的临时变量。
方法区:用于存储已被虚拟机加载的类信息、静态变量等数据,还有就是常量池,比如字符串常量池。
类一般都会有其 接口信息,父类信息,方法信息。在Java堆中必须能查找到此对象的这些信息。
本地方法栈:本地方法栈就是一块为native方法服务的内存区域。
程序计数器:程序计数器是用于记录程序执行到哪的一个指针。因为多个线程之间来回切换,需要记录上次程序执行到哪里。
二、堆内存溢出OutOfMemoryError
配置JVM参数, -Xms5m -Xmx5m -XX:+HeapDumpOnOutOfMemoryError
-Xms5m 设置堆内存最小值5m
-Xmx5m 设置堆内存最大值5m 最小值与最大值设置一样,可以避免堆内存自动扩展
-XX:+HeapDumpOnOutOfMemoryError 记录当出现堆内存溢出时的信息
使用一个死循环,不断创建对象,将其放入List集合中。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class TestBean {
private static final Logger LOGGER = LoggerFactory.getLogger(TestBean.class);
static class OOMObject {
}
/**
* VM Args : -Xms5m -Xmx5m -XX:+HeapDumpOnOutOfMemoryError
*/
public static void main(String[] args) {
int count = 0;
List<OOMObject> list = new ArrayList<>();
while (true) {
LOGGER.info("count={}", count++);
list.add(new OOMObject());
}
}
}
我本机执行结果,当执行到创建第160065个对象的时候,加入List集合,集合最后一次扩容。在ArrayList扩容过程中,出现了OOM。ArrayList实际上就是一个数组,下图中红色部分的堆栈信息应该很熟悉,就是数组在扩容的代码。ArrayList源码参考链接
总结:
Java堆内存的OOM异常是实际中最常见的内存溢出情况。
异常提示信息为: java.lang.OutOfMemoryError :Java heap space。
解决思路为:找出是 内存泄露 还是 内存溢出。
三、栈内存溢出StackOverflowError
配置JVM参数, -Xss128k
-Xss128k 配置栈内存为128k
跑下面这段代码。
public class TestOsfBean {
private int count;
public void stackOverflowMethod() {
this.count++;
//递归调用,直到栈内存溢出
this.stackOverflowMethod();
}
/**
* VM Args: -Xss128k
*/
public static void main(String[] args) {
TestOsfBean osfBean = new TestOsfBean();
try {
osfBean.stackOverflowMethod();
} catch (Throwable e) {
e.printStackTrace();
System.out.println("栈内存溢出,count = " + osfBean.count);
}
}
}
运行结果:递归调用了993次后,栈内存顶不住了,抛出 java.lang.StackOverflowError。
总结:
栈内存溢出的异常提示信息为: java.lang.StackOverflowError
解决思路为:排查递归方法,如果某个递归方法确实需要超过上千次,应该考虑提高栈内存大小。配置示例 -Xss1024k
四、方法区内存溢出
本来想测试方法区内存溢出的,使用字符串String的intern()方法来测试。但是怎么测试好像都没有效果。
错误信息总是提示 java.lang.OutOfMemoryError : Java heap space 。 这显然是堆内存溢出的情况。
错误日志里面显示 :翻阅相关资料,说JDK1.8之后,permgen空间不再存在,现在是普通堆的一部分。我服了。
最直接的后果,就是以后再也不会有outofmemoryerror permgen space这个错误了。
jdk8移除了永久代PermGen,取而代之的是元空间MetaSpace。
替换理由:
permSize:原来的jar包及你自己项目的class存放的内存空间,这部分空间是固定的,启动参数里面-permSize确定,如果你的jar包很多,经常会遇到permSize溢出,且每个项目都会占用自己的permGen空间改成metaSpaces,各个项目会共享同样的class内存空间,比如两个项目都用了fast-json开源包,在metaSpaces里面只存一份class,提高内存利用率,且更利于垃圾回收。
原来如此啊!