1、java虚拟机运行时数据区域
所有线程共享的数据区:Heap、Method Area
线程隔离的数据区:VM Stack、Native Method Stack、Program Counter Register
1.1在概念模型里,字节码解释器工作时就是通过改变当前线程的程序计数器的值来选取下一条要执行的字节码指令
1.2虚拟机栈为虚拟机执行java方法服务,每个方法被执行的同时都会创建一个帧栈用于存储局部变量表、操作栈、动态链接、方法出口等信息。
每个方法被调用直至执行完的过程就对应着一个帧栈在虚拟机栈中从入栈到出栈的过程。
1.3本地方法栈为虚拟机执行本地方法服务
1.4heap在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,heap可以细分为新生代和老年代
1.5方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
2、对象访问
主流的对象访问方式有两种:使用句柄和直接指针
3、实战oom异常
3.1java堆溢出
/**
* @Title: HeapOOM
* @Description: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/home/
* @date 2020/4/15 17:57
*/
public class HeapOOM {
static class OOMObject{}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true){
list.add(new OOMObject());
}
}
}
运行结果:
3.2虚拟机栈与本地方法栈溢出
/**
* @Title: JavaVMStackSOF
* @Description: -verbose:gc -Xms20M -Xss128K -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/home/
* @date 2020/4/16 9:08
*/
public class JavaVMStackSOF {
private int stackLength = 1;
private void stackLeak(){
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
运行结果只有StackOverflowError
3.3创建线程导致内存溢出
/**
* @Title: javaVMStackOOM
* @Description: -verbose:gc -Xms20M -Xss2M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/home/
* @date 2020/4/16 9:33
*/
public class JavaVMStackOOM {
private void dontStop(){
while (true){}
}
public void stackLeakByThread(){
while (true){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
运行后容易死机,没有运行出结果。
3.4运行时常量池溢出
当按照文档2.4.3方法区溢出代码测试时发现过了很长时间,也没有发生内存溢出,阅读文档后得知jdk1.7以后移除了永久代
//-XX:PermSize=10M -XX:MaxPermSize=10M public class RuntimeConstantPoolOOM { public static void main(String[] args) { List<String> list = new ArrayList<>(); int i = 0; while (true){ list.add(String.valueOf(i++).intern()); } } }
在这儿之前,除了即时编译的代码存放在Native memory,其它的数据,包括类信息,常量,静态变量,常量池等均存放在永久代中,但是,这样存在一个问题,就是容易发生内存泄露。
所以在jdk8中,彻底移除了永久代,取而代之的是元空间(metaSpace),方法区的概念保留存放于元空间,常量池在堆中。
方法区(method area)只是JVM规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,具体放在哪里,不同的实现可以放在不同的地方。而永久代是Hotspot虚拟机特有的概念,是方法区的一种实现,别的JVM都没有这个东西。
jdk1.7之前:方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时可以设置一个固定值,不可变;
jdk.7:存储在永久代的部分数据就已经转移到Java Heap或者Native memory。但永久代仍存在于JDK 1.7中,并没有完全移除,譬如符号引用(Symbols)转移到了native memory;字符串常量池(interned strings)转移到了Java heap;类的静态变量(class statics variables )转移到了Java heap;
jdk1.8:仍然保留方法区的概念,只不过实现方式不同。取消永久代,方法存放于元空间(Metaspace),元空间仍然与堆不相连,但与堆共享物理内存,逻辑上可认为在堆中。
1)移除了永久代(PermGen),替换为元空间(Metaspace);
2)永久代中的 class metadata 转移到了 native memory(本地内存,而不是虚拟机);
3)永久代中的 interned Strings 和 class static variables 转移到了 Java heap;
4)永久代参数 (PermSize MaxPermSize) -> 元空间参数(MetaspaceSize MaxMetaspaceSize)。
3.5方法区溢出
public class JavaMethodAreaOOM { static class OOMObject{} public static void main(String[] args) { while (true){ Enhancer enHancer = new Enhancer(); enHancer.setSuperclass(OOMObject.class); enHancer.setUseCache(false); enHancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { return methodProxy.invoke(o, args); } }); } } }
jdk1.7虚拟机参数: -XX:PermSize=10M -XX:MaxPermSize=10M
运行结果:
jdk1.8虚拟机参数: -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
运行结果:
jdk1.7的运行结果堆溢出,说明类信息确实放到堆中。
jdk1.8的运行结果Metaspace溢出,说明类信息确实放在Metaspace。
3.6本机直接内存溢出
//-Xmx20M -XX:MaxDirectMemorySize=10M public class DirectMemoryOOM { private static final int _1M = 1024 * 1024; public static void main(String[] args) throws Exception { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true){ unsafe.allocateMemory(_1M); } } }
运行结果: