作者:小傅哥

博客: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对比、有元空间OOM监控案例、有Java版虚拟机,综合实践学习!的更多相关文章

  1. JVM内存模型(五)

    一.JVM内存模型 1.1.与运行时数据区     前面讲过了运行时数据区那接下来我们聊下内存模型,JVM的内存模型指的是方法区和堆:在很多情况下网上讲解会把内存模型和运行时数据区认为是一个东西,这是 ...

  2. Java基础知识强化100:JVM 内存模型

    一. JVM内存模型总体架构图:  方法区和堆由所有线程共享,其他区域都是线程私有的 二. JVM内存模型的结构分析: 1. 类装载器(classLoader) 类装载器,它是在java虚拟机中用途是 ...

  3. jvm内存模型-回收算法-和内存分配以及jdk、jre、jvm是什么关系(阿里,美团,京东面试题)

    1.什么是jvm?(1)jvm是一种用于计算设备的规范,它是一个虚构出来的机器,是通过在实际的计算机上仿真模拟各种功能实现的.(2)jvm包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个 ...

  4. jvm内存模型-和内存分配以及jdk、jre、jvm是什么关系(阿里,美团,京东)

    参考:JVM的垃圾回收机制 总结(垃圾收集.回收算法.垃圾回收器) 1.什么是jvm?(1)jvm是一种用于计算设备的规范,它是一个虚构出来的机器,是通过在实际的计算机上仿真模拟各种功能实现的.(2) ...

  5. 深入理解JVM—JVM内存模型

    我们知道,计算机CPU和内存的交互是最频繁的,内存是我们的高速缓存区,用户磁盘和CPU的交互,而CPU运转速度越来越快,磁盘远远跟不上CPU的读写速度,才设计了内存,用户缓冲用户IO等待导致CPU的等 ...

  6. JVM初探 -JVM内存模型

    JVM初探 -JVM内存模型 标签 : JVM JVM是每个Java开发每天都会接触到的东西, 其相关知识也应该是每个人都要深入了解的. 但接触了很多人发现: 或了解片面或知识体系陈旧. 因此最近抽时 ...

  7. JVM内存模型和关键参数设置

    一. JVM内存模型: Jvm内存模型是学好Java很重要的一部分,该部分学习能让我们在系统运维的时候,或者优化服务器的时候能够有方法,懂原理. 二. Jvm关键参数: 1. 堆大小设置参数: -Xm ...

  8. JVM内存模型与GC算法

    1.JVM内存模型 JVM内存模型如上图,需要声明一点,这是<Java虚拟机规范(Java SE 7版)>规定的内容,实际区域由各JVM自己实现,所以可能略有不同.以下对各区域进行简短说明 ...

  9. Inside JVM 内存模型

    Inside JVM 内存模型 来源  原文:https://blog.csdn.net/silentbalanceyh/article/details/4661230 参考:IBM开发中心文档,&l ...

随机推荐

  1. 快速理解Python中使用百分号占位符的字符串格式化方法中%s和%r的输出内容的区别

    <Python中使用百分号占位符的字符串格式化方法中%s和%r的输出内容有何不同?>老猿介绍了二者的区别,为了快速理解,老猿在此使用另外一种方式补充说明一下: 1.使用%r是调用objec ...

  2. CNVD漏洞证书(2)

    第二张CNVD的原创漏洞证书. 关于证书申请可以看我之前写的这篇博客: https://www.cnblogs.com/Cl0ud/p/12720413.html 继续加油

  3. node.js、yarn、npm到底是什么?

    最近在部署环境,在没有开发脚本的情况下,自己根据以往其他项目中的脚本去生搬硬套,发现很难对项目的配置成功.对配置不成功的情况进行判断,发现是对脚本不熟悉,不了解其原理,实现方式也不知道,所以抽时间去了 ...

  4. Boost UDP Transaction Performance

    提高UDP交互性能 这是一篇个人认为非常非常厉害的文章,取自这里.讲述了如何提升UDP流的处理速率,但实际涉及的技术点不仅仅限于UDP.这篇文章中涉及的技术正好可以把前段时间了解的知识串联起来.作者: ...

  5. 原生js之事件解绑

    #removeEventListener ##html <button id='btn'>click</button> ##js ###第一种方式(错误方式) var btn ...

  6. react路由初探(2)

    对着官网的例子反正是没有搞出来,所以搜了一大堆,最终搞出来了,记录一下 import React from 'react'; // 首先我们需要导入一些组件... (这个是中文网示例,按这个做,报一大 ...

  7. Java并发编程的艺术(一)——并发编程的注意问题

    并发编程是为了使程序运行得更快,但是,不是启动更多得线程就能最大限度地执行并发,也不是线程更多就能使得程序运行得更快,而且并发编程更容易产生错误,如果要高效且正确地执行并发,就需要注意这三种问题 上下 ...

  8. libev的用法

    本例是以linux环境c++的用法,ide用的是vs2019 一.libev的安装 我们采用的是apt-get方法(偷懒^_^),你也可以采用源码方式安装 sudo apt-get install l ...

  9. uni-app中使用sass

    uni-app在创建时,工程目录下会有个uni.scss文件,我们可以直接在里面定制化scss变量. 全局scss中的坑: 1.如果要引用全局外部scss文件,可以考虑在uni.scss这个系统全局s ...

  10. MobaXterm无法退格删除

    MobaXterm退格删除出现^H,总是要取消输入重新敲语句,很麻烦 解决方法:打开MobaXterm-->settings-->Configuration,把"Backspac ...