JVM之运行时数据区

目录

  1. jvm命令
  2. jvm的运行时数据区
  3. jvm会发生哪些ERROR
  4. 从一个class出发理解数据区

jvm命令

JVM参数类型

  1. 标准: 稳定的,长期没有变化
  2. X: 相对变化较少的
  3. XX: 变化较大,JVM调优重点

设置参数时,idea指定在VM options里面,命令行直接加在java命令后

java -Xss10m -XX:+PrintGCDetails JVMParams

常见的XX类型的参数

  1. -XX:+PrintGCDetails: 打印GC日志

  2. -XX:+PrintFlagsInitial: 打印所有初始的参数信息

  3. -XX:+PrintFlagsFinal: 打印所有最终的参数信息

  4. -Xms设置堆的最小空间大小。

  5. -Xmx设置堆的最大空间大小。

  6. -XX:NewSize设置新生代最小空间大小。

  7. -XX:MaxNewSize设置新生代最大空间大小。

  8. -XX:PermSize设置永久代最小空间大小。

  9. -XX:MaxPermSize设置永久代最大空间大小。

  10. -Xss设置每个线程的堆栈大小。

  11. 没有直接设置老年代的参数,但是可以设置堆空间大小和新生代空间大小两个参数来间接控制

    老年代空间大小=堆空间大小-年轻代大空间大小

例如:

java -XX:+PrintFlagsFinal -version

...
uintx MaxHeapSize := 2048917504 {product}
intx MaxInlineLevel = 9 {product}
intx MaxInlineSize = 35 {product}
bool ParGCTrimOverflow = true {product}
bool ParGCUseLocalOverflow = false {product}
...

上面只显示部分参数,但是能够说明我们需要理解的内容,即 ‘=’ 表示默认值,‘:=’ 表示被修改过的值。同时还有数值类型和布尔类型。

几个特殊的XX类型参数

-Xms、-Xmx、-Xss 实际上是XX类型的缩写

-Xms ==> -XX:InitialHeapSize: 表示为: -Xms10m
-Xmx ==> -XX:MaxHeapSize: 表示为: -Xmx10m
-Xss ==> -XX:ThreadStackSize

常用命令

  1. 查看java进程:jps

    [hadoop@hadoop ~]$ jps
    13612 JVMParams
    13644 Jps
  2. 查看java进程的参数信息
    jinfo -flag name pid

    [hadoop@hadoop ~]$ jinfo -flag MaxHeapSize 13612
    -XX:MaxHeapSize=2048917504
    [hadoop@hadoop ~]$ jinfo -flag InitialHeapSize 13612
    -XX:InitialHeapSize=130023424
    [hadoop@hadoop ~]$ jinfo -flag ThreadStackSize 13612
    -XX:ThreadStackSize=1024

    怎么理解-XX:MaxHeapSize=2048917504,-XX:InitialHeapSize=130023424 ?

    分析:

    我主机的物理内存为8G,2048917504k = 1.9G,130023424 = 124M

    理论上heap的最大值为物理内存的1/4,最小值为物理内存的1/64

    但是一般情况下,我们会把MaxHeapSize和InitialHeapSize设置相同的值,防止内存抖动

  3. 查看java进程的默认和设置的参数

    jinfo -flags pid

    [hadoop@hadoop ~]$ jinfo -flags  13612
    Non-default VM flags: -XX:CICompilerCount=2 ...
    Command line: -Xss10m -XX:+PrintGCDetails ...

jvm的运行时数据区

运行时数据区

程序计数器

程序计数器是每个线程私有的

程序计数器是一块较小的内存空间,它可以看做是当前线程的行号指示器,这在多线程环境下非常有用。使得线程切换后能够恢复到正确的执行位置。

在java虚拟机规范中,这是唯一一个没有规定任何OutOfMemoryError的地方

Java虚拟机栈

java虚拟机栈也是每个线程私有的,它的生命周期和线程相同

虚拟机栈描述的是java方法执行的线程内存模型: 每个方法被执行的时候,java虚拟机栈都会同步创建一个Frame(栈帧)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直到执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

最常用的就是局部变量表,局部变量表存放了编译期可知的各种java虚拟机的基本数据类型(boolean、byte、char、sort、int、long、float、double)、对象引用(reference类型)和returnAddress类型,这部分在后面讲有详细的解释。

在java虚拟机规范中,这个区域可能存在两种异常,如果线程请求的栈深度大于虚拟机所允许的深度,会抛出StackOverflowError异常,常见的有循环调用方法名;如果java虚拟机栈容量可以动态扩展,当栈无法申请到足够的内存时就会抛出OutOfMemoryError异常。

本地方法栈

本地方法栈是线程私有的

本地方法栈与虚拟机栈所发挥的作用是非常类似的,其区别只是虚拟机栈为虚拟机执行java方法服务,而本地方法栈则为本地(native)方法服务

常见的本地方法有getClass、hashCode、clone、notify、notifyAll、wait、sleep等

与java虚拟机栈一样,本地方法栈也会在栈深度溢出的时候或者栈扩展失败的时候抛出StackOutflowError和OutOfMemoryError异常。

java堆

java堆是所有线程共享的,是虚拟机所管理的内存中最大的一块,在虚拟机启动时创建。

此内存区域的唯一目的是存放对象实例,对象实例包括对象和数组。

如果在java堆中没有内存完成实例分配,并且堆也无法扩展,java虚拟机将会抛出OutOfMemoryError异常。

方法区

方法区是所有线程共享的

它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据,简单点说就是Class。

方法区只是java虚拟机的规范,它属于堆的一个逻辑部分,为了和堆区分开,也叫非堆。在jdk8之前,方法区的具体实现叫做永久代,

  1. 由于类及方法的信息大小很难确定,所以内存设置小了会发生OOM,设置大了又浪费
  2. GC复杂度高,回收效率低
  3. 合并 HotSpot 与 JRockit

所以在jdk8完全用元空间替换了永久代,元空间直接使用的系统内存。

在java虚拟机规范中,如果方法区无法满足内存分配需求时,会抛出OouOfMemoryError

直接内存

直接内存不是java虚拟机规范中定义的内存区域,但是这部分也会被频繁使用,所以也可能会抛出OutOfMemoryError。

jvm会发生哪些ERROR

java堆内存OOM异常测试

-Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError
public class HeapOOM {

static class OOMObject{}

public static void main(String[] args) {
ArrayList<OOMObject> list = new ArrayList<>();
while(true){
list.add(new OOMObject());
}
}
}

运行结果:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid184.hprof ...
Heap dump file created [10209413 bytes in 0.080 secs]

java虚拟机栈和本地方法栈SOF测试

-Xss2M
public class JavaVMStackSOF {

private int stackLength=1;

public void stackLeak(){
stackLength++;
stackLeak();
}

public static void main(String[] args) {
JavaVMStackSOF stackSOF = new JavaVMStackSOF();
try {
stackSOF.stackLeak();
} catch (Throwable e) {
System.out.println("栈深度:"+stackSOF.stackLength);
throw e;
}
}
}

运行结果:

栈深度:41075
Exception in thread "main" java.lang.StackOverflowError

java虚拟机栈和本地方法栈OOM测试

-Xss4m
public class JavaVMStackOOM {

private void dontStop(){
while(true){
}
}

public void stackleakByThread() {
while(true){
new Thread(() -> {
dontStop();
}).start();
}
}

public static void main(String[] args) {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackleakByThread();
}
}

运行结果:

死机

方法区和运行时常量池OOM

-XX:PerSize=6m -XX:MaxPermSize=6M
import java.util.HashSet;

public class RuntimeConstantPoolOOM {

public static void main(String[] args) {
HashSet<String> set = new HashSet<String>();
short i = 0;
while(true){
set.add(String.valueOf(i++).intern());
}
}
}

运行结果:

jdk8没有测试出来

从一个class出发理解数据区

class类对比运行时数据区

如图所示,很容易的理解各区分别保存java代码中的哪些部分

堆区: 保存的People对象

栈区: 保存的栈帧,栈帧中保存了引用name和age和引用people

方法区: 保存的People.class相关的,包括类型信息、常量、静态变量sss

运行时常量池: 保存name和age字符串内容

Author: Tunan
Link: http://yerias.github.io/2020/04/15/jvm/1/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.