JVM|JVM系列之运行时数据区与内存异常演示

JVM概述 JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
众所周知,java是跨平台语音,重点就在这,是通过JVM实现跨平台的,开发阶段共用一套代码,编译后在java虚拟机中运行时屏蔽了平台差异,即JVM在不同平台的实现是不一样的,所有我们在装jdk环境时针对不同平台需要下载不同的包。
流行JVM JVM|JVM系列之运行时数据区与内存异常演示
文章图片

tip:本文以HotSpot为例
JVM运行流程 JVM|JVM系列之运行时数据区与内存异常演示
文章图片

流程:
1、java文件编译成class文件
2、类加载器将class文件加载到JVM中
3、执行引擎执行不同JVM指令,最终调用操作系统
JVM体系结构 JVM|JVM系列之运行时数据区与内存异常演示
文章图片

类加载器将class文件加载到JVM中,后就是通过执行引擎实现程序调度,调度过程中数据存储的区域就是运行时数据区。执行引擎是基于栈的字节码解释执行引擎,执行引擎的任务就是将字节码指令解释/编译为对应平台上的本地机器指令,通过调用本地库接口。大家有兴趣可自行了解,比较底层较复杂。
tip:类加载机制后续会有文章详谈
运行时数据区 程序计数器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为(Undefined)。
程序计数器是唯一不会发生OOM的数据区域。
虚拟机栈 虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都
会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表:存放了编译期可知的各种Java虚拟机基本数据类型)、对象引用和returnAddress类型。
操作数栈:可理解为java虚拟机栈中的一个用于计算的临时数据存储区。

public int add(){ int a = 1; int b = 2; int c = a + b; return c; }

add方法中,1 2入栈,在操作数栈中计算出值,c出栈
本地方法栈 本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的本地(Nat ive)方法服务。
堆 堆空间是JAVA对象生死存亡的地区,JAVA对象的出生,成长,死亡都在这个区域完成。JAVA程序在运行时创建的所有类实例或数组都放在堆中。每一个JAVA程序独占一个JVM实例,一个JVM实例只存在一个堆空间,因些每个JAVA程序都有它自己的堆空间,它们不会彼此干扰.同一个JAVA程序的多个线程都共享着同一个堆空间,所以就需要考虑多线程访问对象(堆数据)的同步问题。
我们经常看到的 新生代、老年代、Eden空间、From Survivor、To Survivor等名词,都是基于“ 经典分代”来设计,需要新生代、老年代收集器搭配才能工作。但从G1收集器后,S henandoah和ZGC等垃圾回收期,采用基于Region的堆内存布局,不再按照新生代、老年代设计,而是把整个堆内存切割成一个个小块的区域。
元空间 元空间方与Java堆一样,是各个线程共享的内存区域,它用于存储已加载的类的信息、类中的静态变量、类中final类型的常量、类中的Field信息、类中的方法信息等。我们日常开发中通过Class对象的getName等方法获取信息时,这些数据来源都是此区域。
在jdk1.8之前,此快区域被称作 方法区。
内存异常演示 Java 堆溢出 ?案:通过创建?量对象,内存泄漏,导致OOM
参数:调整堆最?值:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
/** * VM args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError */ public class HeapOom { byte [] bytes = new byte[1024]; public static void main(String[] args) throws InterruptedException{ List list = new ArrayList<>(); while (true){ list.add(new HeapOom()); } } }

异常信息:
java.lang.OutOfMemoryError: Java heap space Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.suning.demo.jvm.HeapOom.(HeapOom.java:20) at com.suning.demo.jvm.HeapOom.main(HeapOom.java:25) Disconnected from the target VM, address: '127.0.0.1:59497', transport: 'socket'

虚拟机栈和本地?法栈溢出 ?案:通过递归调?,导致StackOverflowError
参数:-Xss128k (默认1M)
public class StackOverflowErrorDemo { /** * VM args:-Xss128k */ public void test(){ //栈中内容大小影响深度 int a = 1; int b = 2; int c = 3; Object o = new Object(); //递归调用 test(); } public static void main(String[] args) { new StackOverflowErrorDemo().test(); } }

异常信息:
Exception in thread "main" java.lang.StackOverflowError at com.suning.demo.jvm.StackOverflowErrorDemo.test(StackOverflowErrorDemo.java:15)

多线程情况下,假设一直创建线程,也会报OOM。
java.lang.OutOfMemoryError: unable to create new native thread
大家有兴趣可以试试,就是可能会把电脑搞死机。
public static void main(String[] args) { //无限循环,创建线程,休眠无限长时间 for (int i = 0; ; i++) { new Thread(() -> { try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } }, "" + i).start(); } }

元空间溢出 ?案:通过动态代理的?式?成?量类信息使?法区/元空间内存不?导致OOM
参数:-XX:MetaspaceSize=12m -XX:MaxMetaspaceSize=12m (JAVA8)
/** * VM args: -XX:MetaspaceSize=12m -XX:MaxMetaspaceSize=12m */ public class MetaspaceOom { public static void main(String[] args) { while (true){ Enhancer eh = new Enhancer(); //设置被代理对象的类为父类 eh.setSuperclass(Object.class); eh.setUseCache(false); //设置代理对象的回调 eh.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invoke(o,objects)); eh.create(); } } }

异常信息:
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace

运行时常量池溢出 ?案:根据JDK版本常量池存放位置的不同?构造案例
JDK1.7之前:?法区,通过控制永久代??可以模拟因运?时常量池暴增导致OOM:
java.lang.OutOfMemoryError: PermGen space
JDK1.7之后:堆,需要控制堆内存??模拟因运?时常量池暴增导致OOM,-XX:MaxPermSize,-XX:MaxMetaspace 参数设置?效。-Xms6m -Xmx6m
/** * VM args: -Xms6m -Xmx6m */ public class RuntimeConstantPoolOom { public static void main(String[] args){ List> list = new ArrayList<>(); int i = 0; while (true){ list.add(String.valueOf(i++).intern()); } } }

异常信息:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded at java.lang.Integer.toString(Integer.java:403) at java.lang.String.valueOf(String.java:3099) at com.suning.demo.jvm.RuntimeConstantPoolOom.main(RuntimeConstantPoolOom.java:16)

本机直接内存溢出 ?案:通过Unsafe类分配直接内存导致本机直接内存溢出
参数:-Xmx20M -XX:MaxDirectMemorySize=10M
public class DirectMemoryOom {private static final int _1M = 1024 * 1024; /** * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M * DirectMemory容量可通过-XX:MaxDirectMemorySize指定 * 如果不指定,则默认与Java堆的最大值(-Xmx指定) */ public static void main(String[] args) throws IllegalAccessException{ Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe =(Unsafe) unsafeField.get(null); while(true) { unsafe.allocateMemory(_1M * 100); } } }

【JVM|JVM系列之运行时数据区与内存异常演示】异常信息:
Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at com.suning.demo.jvm.DirectMemoryOom.main(DirectMemoryOom.java:19)

    推荐阅读