java 源代码可通过 javac 命令编译成字节码文件,通过在 jvm 上运行来屏蔽不同平台的差异,实现一次编译多次运行的目的,在此记录一些 jvm 的基础知识。
JVM内存区域
名词解析
- 虚拟机栈:方法执行时的内存模型,是线程私有的,生命周期与线程相同。每个执行方法时会创建栈帧(存放局部变量表、操作数栈、动态连接和返回地址等信息),开始执行方法入栈,执行完成出栈,出入栈的时机很明确,所以不需要GC进行管理。
- 本地方法栈:和虚拟机栈功能类似,主要区别在于虚拟机栈是为虚拟机执行java方法时服务,本地方法栈是为虚拟机执行本地方法时服务,所以也不需要进行GC。
- 程序计数器:记录线程执行现场,保证线程从挂起重新执行时能知道需要从哪里开始执行。需要注意一点,这块是唯一一个在java虚拟机规范中没有规定任何OOM情况的区域,所以它也不需要进行GC。
- 本地内存:线程共享区域,在Java8中也是我们通常说的堆外内存,包含元信息和方法区。Java8之前有一块叫永生代/方法区的区域在堆中,永生代主要用来存储类的信息、常量、静态变量、即时编译器编译后的代码等。由于永生代有 -XX:MaxPermSize 的限制,所以如果动态生成类或大量执行String.intern方法(将字符串放到永生代的常量区),很容易造成OOM。所以在Java8中就把方法区的实现迁移到本地内存中的元空间里了,这样方法区就不受JVM控制了,所以就不会造成GC,也因此提升了性能。Java8之后这块区域也不需要进行GC。
- 堆:发生GC的区域,对象实例和数组都是在堆上分配的,GC也主要对这两类数据进行回收。
JVM参数简介
JVM中的栈、堆大小、使用的垃圾收集器等都可通过JVM参数来设置。
- 标准参数(-),例如 -server 或 -client 来指定启动JVM进程以那种方式运行。
- 非标准参数(-X),栈、堆的大小都可通过这个参数来配置。常用的有:
参数示例 | 含义 |
---|---|
-Xms512m | JVM启动时设置的初始堆大小为512M |
-Xmx512m | JVM可分配的最大堆大小为512M |
-Xmn200m | 设置年轻代大小为200M |
-Xss128k | 设置每个线程的栈大小为128k |
- 非Stable参数(-XX)
常见OOM场景
Java 虚拟机规范中描述在栈上主要会发生以下两种异常
- StackOverflowError,这种情况主要是因为单个线程请求栈的深度大于虚拟机所允许的最大深度(如递归调用层级过深)再比如单个线程定义了大量的本地变量,导致方法帧中本地变量表长度过大等也会导致 StackOverflowError 异常, 一句话:在单线程下,当栈桢太大或虚拟机容量太小导致内存无法分配时,都会发生 StackOverflowError 异常。
- java.lang.OutOfMemoryError: unable to create new native thread,创建线程太多导致无法创建线程。可通过减小最大堆(-Xms)和减小虚拟机每个线程栈大小(-Xss)进行调整。
堆溢出 (java.lang.OutOfMemoryError:Java heap space)
- 大对象的分配,最有可能是大数组分配。
- 内存泄露,程序编码问题导致无用的对象一直被引用无法被回收,最终导致OOM。
还有其他几种OOM的错误信息,不太常见,先不记录了。
(转载本站文章请注明作者和出处 wyc1856)