关于java:JVM内存模型总结有各版本JDK对比有元空间OOM监控案例有Java版虚拟机综合实践学习

42次阅读

共计 7518 个字符,预计需要花费 19 分钟才能阅读完成。


作者:小傅哥
博客:https://bugstack.cn
Github:https://github.com/fuzhengwei/CodeGuide/wiki

积淀、分享、成长,让本人和别人都能有所播种!????

一、前言

看了一篇文章 30 岁有多难!

每篇文章的开篇总喜爱写一些,从集体视角看这个世界的感悟。

最近看到一篇文章,30 岁有多难。文中的一些主人公如同在学业、工作、生存、恋情等方面都过的都不如意。要不是错过这,要不是走错那。总结来看,就像是很晦气的一群倒霉蛋儿在跟生存对干!

但其实每个人可能都遇到过生存中最难的时候,或早或晚。就像我刚毕业不久时一连串遇到;冬天里丢过第一部手机 修一个进了水的电脑 租的房子第一次被骗,一连串下来头一次要赶在工资没发的时候,抉择少吃早饭还是午饭,看看能扛过去那顿。

哈哈哈哈哈,当初想想还挺有意思的,不过这些乱遭的事很多是本人的意识和能力有余时做出的谬误抉择而导致的。

人那,想开车就要考驾照,想走远就要有能力。多晋升认知,多拓宽眼界!生存的意义就是一直的更新本人!

二、面试题

谢飞机,小记!,冬风吹、战鼓擂。被窝里,谁怕谁。

谢飞机:歪?大哥,你在吗?

面试官:咋了,大周末的,这么早打电话!?

谢飞机:我梦见,我去谷歌写 JVM 了,给你们公司用,之后蹦了,让我起来改 bug!

面试官:啊!?啊,那我问你,JDK 1.8 与 JDK 1.7 在运行时数据区的设计上,你都怎么做的优化策略的?

谢飞机:我没写这,我不晓得!

面试官:擦。。。

三、JDK1.6、JDK1.7、JDK1.8 内存模型演变

如图 25-1 是 JDK 1.6、1.7、1.8 的内存模型演变过程,其实这个内存模型就是 JVM 运行时数据区按照 JVM 虚拟机标准的具体实现过程。

在图 25-1 中各个版本的迭代都是为了更好的适应 CPU 性能晋升,最大限度晋升的 JVM 运行效率。这些版本的 JVM 内存模型次要有以下差别:

  • JDK 1.6:有永恒代,动态变量寄存在永恒代上。
  • JDK 1.7:有永恒代,但曾经把字符串常量池、动态变量,寄存在堆上。逐步的缩小永恒代的应用。
  • JDK 1.8:无永恒代,运行时常量池、类常量池,都保留在元数据区,也就是常说的 元空间。但字符串常量池依然寄存在堆上。

四、内存模型各区域介绍

1. 程序计数器

  • 较小的内存空间、线程公有,记录以后线程所执行的字节码行号。
  • 如果执行 Java 办法,计数器记录虚拟机字节码以后指令的地址,本中央法令为空。
  • 这一块区域没有任何 OutOfMemoryError 定义。

以上,就是对于程序计数器的定义,如果这样看没有感觉,咱们举一个例子。

定义一段 Java 办法的代码,这段代码是计算圆形的周长。

public static float circumference(float r){
        float pi = 3.14f;
        float area = 2 * pi * r;
        return area;
}

接下来,如图 25-2 是这段代码的在虚拟机中的执行过程,左侧是它的程序计数器对应的行号。

  • 这些行号每一个都会对应一条须要执行的字节码指令,是压栈还是弹出或是执行计算。
  • 之所以说是线程公有的,因为如果不是公有的,那么整个计算过程最终的后果也将谬误。

2. Java 虚拟机栈

  • 每一个办法在执行的同时,都会创立出一个栈帧,用于寄存局部变量表、操作数栈、动静链接、办法进口、线程等信息。
  • 办法从调用到执行实现,都对应着栈帧从虚拟机中入栈和出栈的过程。
  • 最终,栈帧会随着办法的创立到完结而销毁。

可能这么只从定义看上去依然没有什么感觉,咱们再找一个例子。

这是一个对于 斐波那契数列(Fibonacci sequence)求值的例子,咱们通过斐波那契数列在虚拟机中的执行过程,来领会 Java 虚拟机栈的用处。

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子滋生为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递推的办法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在古代物理、准晶体结构、化学等畛域,斐波纳契数列都有间接的利用,为此,美国数学会从 1963 年起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。

  • 整个这段流程,就是办法的调用和返回。在调用过程申请了操作数栈的深度和局部变量的大小。
  • 以及相应的信息从各个区域获取并操作,其实也就是入栈和出栈的过程。

3. 本地办法栈

  • 本地办法栈与 Java 虚拟机栈作用相似,惟一不同的就是本地办法栈执行的是 Native 办法,而虚拟机栈是为 JVM 执行 Java 办法服务的。
  • 另外,与 Java 虚拟机栈一样,本地办法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异样。
  • JDK1.8 HotSpot 虚拟机间接就把本地办法栈和虚拟机栈合二为一。

对于本地办法栈在以上的例子曾经波及了这部分内容,这里就不在赘述了。

4. 堆和元空间

  • JDK 1.8 JVM 的内存构造次要由三大块组成:堆内存、元空间和栈,Java 堆是内存空间占据最大的一块区域。
  • Java 堆,由年老代和年轻代组成,别离占据 1 / 3 和 2 /3。
  • 而年老代又分为三局部,EdenFrom SurvivorTo Survivor,占据比例为 8:1:1,可调。
  • 另外这里咱们特意画出了元空间,也就是间接内存区域。在 JDK 1.8 之后就不在堆上调配办法区了。
  • 元空间 从虚拟机 Java 堆中转移到本地内存,默认状况下,元空间的大小仅受本地内存的限度,说白了也就是当前不会因为永恒代空间不够而抛出 OOM 异样呈现了。jdk1.8 以前版本的 class 和 JAR 包数据存储在 PermGen 上面,PermGen 大小是固定的,而且我的项目之间无奈共用,私有的 class,所以比拟容易呈现 OOM 异样。
  • 降级 JDK 1.8 后,元空间配置参数,-XX:MetaspaceSize=512M XX:MaxMetaspaceSize=1024M。教你个小技巧通过 jps、jinfo 查看元空间,如下:

    • 通过 jinfo 查看默认 MetaspaceSize 大小(约 20M),MaxMetaspaceSize 比拟大。

其余:对于 JDK1.8 元空间的介绍: Move part of the contents of the permanent generation in Hotspot to the Java heap and the remainder to native memory. http://openjdk.java.net/jeps/122

5. 常量池

  • 从 JDK 1.7 开始把常量池从永恒代中剥离,直到 JDK1.8 去掉了永恒代。而字符串常量池始终放在堆空间,用于存储字符串对象,或是字符串对象的援用。

五、手撸虚拟机(内存模型)

其实以上的内容,曾经残缺的介绍了 JVM 虚拟机的内存模型,也就是运行时数据区的构造。然而这货色看完可能就遗记了,因为短少一个可亲手操作的代码。

所以,这里我给大家用 Java 代码写一段对于数据槽、栈帧、局部变量、虚拟机栈以及堆的代码构造,让大家更好的加深对虚拟机内存模型的印象。

1. 工程构造

运行时数据区
├── heap
│   ├── constantpool
│   ├── methodarea
│   │   ├── Class.java
│   │   ├── ClassMember.java
│   │   ├── Field.java
│   │   ├── Method.java
│   │   ├── MethodDescriptor.java
│   │   ├── MethodDescriptorParser.java
│   │   ├── MethodLookup.java
│   │   ├── Object.java
│   │   ├── Slots.java
│   │   └── StringPool.java
│   └── ClassLoader.java
├── Frame.java
├── JvmStack.java
├── LocalVars.java
├── OperandStack.java
├── Slot.java
└── Thread.java

以上这部分就是应用 Java 实现的局部 JVM 虚拟机性能,这部分次要包含如下内容:

  • Frame,栈帧
  • JvmStack,虚拟机栈
  • LocalVars,局部变量
  • OperandStack,操作数栈
  • Slot,数据槽
  • Thread,线程
  • heap,堆,外面包含常量池和办法区

2. 重点代码

操作数栈 OperandStack

public class OperandStack {

    private int size = 0;
    private Slot[] slots;

    public OperandStack(int maxStack) {if (maxStack > 0) {slots = new Slot[maxStack];
            for (int i = 0; i < maxStack; i++) {slots[i] = new Slot();}
        }
    }
    //...
}

虚拟机栈 OperandStack

public class JvmStack {

    private int maxSize;
    private int size;
    private Frame _top;
    
    //...
}

栈帧 Frame

public class Frame {

    //stack is implemented as linked list
    Frame lower;

    // 局部变量表
    private LocalVars localVars;

    // 操作数栈
    private OperandStack operandStack;

    private Thread thread;

    private Method method;

    private int nextPC;
 
    //...
}
  • 对于代码构造看到这有点感觉了吗?
  • Slot 数据槽,就是一个数组构造,用于存放数据的。
  • 操作数栈、局部变量表,都是应用数据槽进行入栈入栈操作。
  • 在栈帧里,能够看到连贯、局部变量表、操作数栈、办法、线程等,那么文中说到的当有一个新的 每一个办法在执行的同时,都会创立出一个栈帧,是不就对了上,能够真的了解了。
  • 如果你对 JVM 的实现感兴趣,能够浏览 用 Java 实现 JVM 源码:https://github.com/fuzhengwei/itstack-demo-jvm

六、jconsole 监测元空间溢出

不是说 JDK 1.8 的内存模型把永恒代下掉,换上 元空间 了吗?但不测试下,就感触不到呀,没有证据!

所有对于代码逻辑的学习,都须要有数据根底和证实过程,这样能力有粗浅的印象。走着,带你把元空间干满,让它 OOM!

1. 找段继续创立大对象的代码

public static void main(String[] args) throws InterruptedException {Thread.sleep(5000);
    
    ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
    while (true) {Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MetaSpaceOomMock.class);
        enhancer.setCallbackTypes(new Class[]{Dispatcher.class, MethodInterceptor.class});
        enhancer.setCallbackFilter(new CallbackFilter() {
            @Override
            public int accept(Method method) {return 1;}
            @Override
            public boolean equals(Object obj) {return super.equals(obj);
            }
        });
        System.out.println(enhancer.createClass().getName() + loadingBean.getTotalLoadedClassCount() + loadingBean.getLoadedClassCount() + loadingBean.getUnloadedClassCount());
    }
}
  • 网上找了一段基于 CGLIB 的,你能够写一些其余的。
  • Thread.sleep(5000);,睡一会,不便咱们点检测,要不程序太快就异样了。

2. 调整元空间大小

默认状况下元空间太大了,不不便测试出后果,所以咱们把它调的小一点。

-XX:MetaspaceSize=8m
-XX:MaxMetaspaceSize=80m

3. 设置监控参数

基于 jconsole 监控,咱们须要设置下参数。

-Djava.rmi.server.hostname=127.0.0.1
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=7397
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false

4. 测试运行

4.1 配置参数

以上的测试参数,配置到 IDEA 中运行程序里就能够,如下:

另外,jconsole 能够通过 IDEA 提供的 Terminal 启动,间接输出 jconsole,回车即可。

4.2 测试后果

org.itstack.interview.MetaSpaceOomMock$$EnhancerByCGLIB$$bd2bb16e999099900
org.itstack.interview.MetaSpaceOomMock$$EnhancerByCGLIB$$9c774e64999199910
org.itstack.interview.MetaSpaceOomMock$$EnhancerByCGLIB$$cac97732999299920
org.itstack.interview.MetaSpaceOomMock$$EnhancerByCGLIB$$91c6a15a999399930
Exception in thread "main" java.lang.IllegalStateException: Unable to load cache item
    at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:79)
    at net.sf.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:119)
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)
    at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
    at net.sf.cglib.proxy.Enhancer.createClass(Enhancer.java:337)
    at org.itstack.interview.MetaSpaceOomMock.main(MetaSpaceOomMock.java:34)
Caused by: java.lang.OutOfMemoryError: Metaspace
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:467)
    at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:339)
    at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:96)
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:94)
    at net.sf.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)
    at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
    at java.util.concurrent.FutureTask.run(FutureTask.java)
    at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
    ... 6 more
  • 要的就是这句,java.lang.OutOfMemoryError: Metaspace,元空间 OOM,证实 JDK1.8 曾经去掉永恒代,换位元空间。

4.3 监控截图

  • 图 25-6,就是监测程序 OOM 时的元空间体现。这回对这个元空间就有感觉了吧!

七、总结

  • 本文从 JDK 各个版本对于内存模型构造的演变,来理解各个区域,包含:程序计数器、Java 虚拟机栈、本地办法栈、堆和元空间。并理解从 JDK 1.8 开始去掉办法区引入元空间的外围目标和作用。
  • 在通过手撸 JVM 代码的形式让大家对运行时数据区有一个整体的认知,也通过这样的形式让大家对学习这部分常识有一个抓手。
  • 最初咱们通过 jconsole 检测元空间溢出的整个过程,来学以致用,看看元空间到底在解决什么问题以及怎么测试。

八、系列举荐

  • 为了搞清楚类加载,居然手撸 JVM!
  • JDK、JRE、JVM,是什么关系?
  • LinkedList 插入速度比 ArrayList 快?你确定吗?
  • 认知本人的技术栈盲区,有指标的学习
  • 谁说今天上线,这货压根不晓得开发流程!

正文完
 0