2019独角兽企业重金招聘Python工程师标准>>>

前言

本笔记参照了周志明《深入理解Java虚拟机:JVM高级特性与最佳实践》第三版,读完之后受益匪浅,让我对Java虚拟机有了一个深刻的认识,这也是Jvm书籍中最好的读物之一。

1. 什么是GC

GC(Garbage Collected)表示垃圾收集器

2. Java内存区域

java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。如下图所示:

程序计数器

程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器

虚拟机栈

与程序计数器一样,虚拟机栈也是线程私有的,它的生命周期与线程(创建、可运行、不可运行)相同。 它描述的是java方法执行的内存模型:每个方法在执行的同时,都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

程序调用所使用的系统栈,如下图所示:

本地方法栈

本地方法栈与虚拟机栈作用相似,区别是虚拟机栈为虚拟机执行java方法(字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,其唯一目的是存放对象实例。Java堆可以处于物理上不连续的内存空间中。在实现时,既可以实现成固定大小的,也可以是可拓展的(通过-Xmx和-Xms控制)。

方法区(也称永久代)

方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

运行时常量池

运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

3. 虚拟机对象揭秘

对象的创建

虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程(加载、验证、准备、解析、初始化、使用和卸载)。

对象的内存布局

对象在内存中存储的布局可以分为3块区域:对象头、实例数据和对齐填充。

对象头

对象头包括2部分信息:

  1. 用于存储对象自身的运行时数据

    • 哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳

虚拟机对象头Mark Word
|存储内容|标志位|状态| |:---:|:---:|:---:| |对象哈希码、对象分代年龄|01|未锁定| |指向锁记录的指针|00|轻量级锁定| |指向重量级锁的指针|10|膨胀(重量级锁定)| |空,不需要记录信息|11|GC标记| |偏向线程ID、偏向时间戳、对象分代年龄|01|可偏向|

  1. 类型指针,即对象指向他的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

4. 内存溢出异常

OutOfMemoryError异常

堆溢出

Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。
代码如下:

/*** @Description java堆溢出* VM args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError* @Author loubobooo* @Date 2019/3/9 11:11**/
public class HeapOOM {static class OOMObject{}public static void main(String[] args) {List<OOMObject> list = new ArrayList<>();while (true){list.add(new OOMObject());}}
}

堆内存问题检测思路

  1. 如果是内存泄露

    可通过工具(JProfiler或者MAT)查看泄露对象到GC Roots的引用链。于是就能找到泄露对象是通过怎样的路径与GC Roots相关联,并导致垃圾收集器无法自动回收它们的,掌握了泄露对象的类型信息及GC Roots引用链的信息,就可以比较准确地定位出泄露代码的位置

  2. 若果不存在泄露(内存溢出了),就是内存中的对象确实都还必须存活着

    那就应当检查虚拟机的堆参数(-Xms与-Xmx),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。

虚拟机栈和本地方法栈溢出

栈容量只由-Xss参数设定。
关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

代码如下:

/*** @Description StackOverflowError* VM Args: -Xss160k* @Author loubobooo* @Date 2019/3/9 13:45**/
public class JavaVMStackSOF {private int stackLength = 1;public void stackLeak(){stackLength++;stackLeak();}public static void main(String[] args) throw Throwable{JavaVMStackSOF oom = new JavaVMStackSOF();try{oom.stackLeak();}catch (Throwable e){System.out.println("stack length : " + oom.stackLength);throw e;}}
}
  • 使用-Xss参数减少毡内存容量。结果:抛出StackOverflowError异常,异常出现时输出的堆栈深度相应缩小。
  • 定义了大量的本地变量,增大此方法帧中本地变量表的长度。结果:抛出StackOverflowError异常时输出的堆栈深度相应缩小。

参照输出结果表明:

在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常。

方法区和运行时常量池溢出

String.intern()是一个Native方法,它的作用是:

如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

JDK1.6版本代码如下:

/*** @Description 运行时常量池导致的内存溢出异常* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M* 永久代对象大小* @Author loubobooo* @Date 2019/3/9 14:17**/
public class RuntimeConstantPoolOOM {public static void main(String[] args) {// 使用List保持着常量池引用,避免Full GC回收常量池行为List<String> list = new ArrayList<>();// 10MB的PermSize在integer范围内足够产生OOM了int i = 0;while (true){list.add(String.valueOf(i).intern());}}
}

运行结果出现:运行时常量池溢出,并出现PermGen space,说明运行时常量池属于方法区(永久代)的一部分。

PS: 不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,

JDK1.8中移除了永久代,转移到MetaSpace元空间

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:

-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,
就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。

String.intern()返回引用的测试

代码如下:

public class RuntimeConstantPoolOOM{public static void main(String[] args){String str1 = new StringBuilder("计算机").append("软件").toString();System.out.println(str1.intern() == str1);String str2 = new StringBuilder("ja").append("va").toString();System.out.println(str2.intern() == str2);}
}
  • 在JDK1.6版本中,得到如下结果
false
false

在JDK1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用,而由StringBuilder 创建的字符串实例在Java堆上,所以必然不是同一个引用,将返回false

  • 在JDK1.7版本中,得到如下结果
true
false

在JDK1.7的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。

对str2比较返回的false,是因为"java"这个字符串在执行StringBuilder.toString()之前已经出现过,字符串常量池中已经有它的引用了,不符合“首次出现”的原则,而“计算机软件”这个字符串则是首次出现的,因此返回true

方法区用于存放class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。对于这些区域的测试,基本的思路是运行时产生大量的类去填满方法区,直到溢出。

5. 总结

程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作

转载于:https://my.oschina.net/loubobooo/blog/3045848

JVM最佳学习笔记一---Java内存区域与内存溢出异常相关推荐

  1. JVM最佳学习笔记---总览

    2019独角兽企业重金招聘Python工程师标准>>> 本笔记参照了周志明<深入理解Java虚拟机:JVM高级特性与最佳实践>第三版,读完之后受益匪浅,让我对Java虚拟 ...

  2. 【深入理解Java虚拟机学习笔记】第二章 Java 内存区域与内存溢出异常

    最近想好好复习一下java虚拟机,我想通过深读 [理解Java虚拟机 jvm 高级特性与最佳实践] (作者 周志明) 并且通过写一些博客总结来将该书读薄读透,这里文章内容仅仅是个人阅读后简短总结,加强 ...

  3. 学习笔记:Java虚拟机——JVM内存结构、垃圾回收、类加载与字节码技术

    学习视频来源:https://www.bilibili.com/video/BV1yE411Z7AP Java类加载机制与ClassLoader详解推荐文章:https://yichun.blog.c ...

  4. 学习笔记【Java 虚拟机④】内存模型

    若文章内容或图片失效,请留言反馈.部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 总目录 学习笔记[Java 虚拟机①]内存结构 学习笔记[Java 虚拟机②]垃圾回收 学习笔记[Java ...

  5. 菜鸟学习笔记:Java基础篇3(面向对象思想、程序执行过程内存分析、面向对象重要概念)

    菜鸟学习笔记:Java面向对象篇上 Java面向对象的思想 Java程序执行过程内存分析 Java垃圾回收机制 构造方法 方法重载(overload) static关键字 this关键字 Java面向 ...

  6. Java虚拟机(JVM)学习笔记(不定时更新)

    Java虚拟机(JVM)学习笔记 不少组织都曾开发过Java虚拟机: SUN公司曾经使用过3个虚拟机,Classic.Exact VM.Hotspot.     其中Hotspot虚拟机沿用至今,并已 ...

  7. java heap 内存_深入理解jvm之内存区域与内存溢出

    Java内存区域与内存溢出异常 运行时数据区域 程序计数器当前线程所执行的字节码的行号指示器 当前线程私有 不会出现OutOfMemoryError情况 java虚拟机栈线程私有,生命周期与线程相同 ...

  8. 深入理解JVM之Java内存区域与内存溢出异常

    读了<深入理解Java虚拟机-JVM高级特性与最佳实践>的第二章,明白了在虚拟机中内存是如何划分的,哪部分区域.什么样的代码和操作可能导致内存溢出异常.虽然Java有垃圾收集机制,但是内存 ...

  9. 学习笔记:Java 并发编程①_基础知识入门

    若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 视频下载: ...

最新文章

  1. linux之父密码,Linux之父十大名言
  2. 为什么Python没有main函数?
  3. 整数的最大值和最小值
  4. 数据网络卡顿怎么处理_监控网络卡顿怎么办
  5. miRNA实验与荧光素酶
  6. Spring Boot——集成Swagger2
  7. Pixhawk原生固件以往代码版本的下载
  8. Mybatis使用IN语句查询
  9. oracle load select,Oracle数据库的Load详解
  10. Python飞机大战+图片
  11. iris数据集(.csv .txt)免费下载
  12. 利用leaflet做一个飞机航线 可根据方向动态掉头
  13. 金山打字通83字/分
  14. WIN7截图工具灵活使用
  15. ai新视觉:一键解决模糊图片高清精准修复
  16. Programming Rust Fast, Safe Systems Development(译) 错误处理(第七章)
  17. nginx软件安装部署
  18. inprivate浏览是什么意思_打开浏览器无痕是什么意思
  19. colorkey唇釉是否安全_colorkey唇釉安全吗
  20. 中国地址英文翻译,英文网站注册

热门文章

  1. 如何读取电脑html信息,JavaScript 获取客户端计算机硬件及系统信息
  2. 华硕笔记本电池0%充不进电_笔记本电脑电池充不进电如何解决【解决方法】
  3. HDLBits 系列(34)Serial two's complememter(Mealy and Moore FSM)
  4. 【 C 】用链表实现堆栈
  5. ECEF rectangular coordinate system(ECEF直角坐标系)
  6. HTTP报文(待整理)
  7. Android:可变参数Viarable
  8. 微信小程序教学第二章(含视频):小程序中级实战教程之预备篇 - 封装网络请求及 mock 数据...
  9. HTML5上传图片,后台使用java
  10. SQL语句-exec执行