前言

最近开始看这本书,记得前段时间拿起这本书的时候,心情是相当沉重的!当时的剧本是这样的——

内景。家里 - 下午
我(画外):唉,有点无聊啊!(偶然撇过书架)这么多书得看到什么时候啊,要不要拿一本翻翻呢?但是在家里好像有点看不下去啊,是太安逸了吗?最近那本《图解 HTTP》也还没看完,感觉暂时有点不想看了。(走到书架前)还是挑几本优先级比较高的带到███下班的时候看吧。(沉思)嗯,这本带过去~

当我拿起《深入理解 Java 虚拟机》这本书的那一刻,心里咯噔一下——唉,PM10 浓度又上升了,地球环境越来越差了啊,万恶的地球人!

正文

一、运行时数据区域

Java 虚拟机在执行 Java 程序时,会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁时间。

1、程序计数器

  • 是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
  • 线程私有:为了线程切换后能恢复到正确的执行位置,因此每条线程都需要有一个独立的程序计数器。
  • 唯一一个不会出现 OutOfMemoryError 异常的区域。

2、Java 虚拟机栈

  • 虚拟机栈描述的是 Java 方法执行的内存模型:Java 方法在执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
  • 线程私有。
  • 会出现 StackOverflowError 和 OutOfMemoryError 异常。
    • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度,将抛出该异常。
    • OutOfMemoryError:虚拟机栈动态扩展时无法申请到足够的内存,将抛出该异常。

3、本地方法栈

  • 作用与虚拟机栈相似,只不过虚拟机栈为虚拟机执行 Java 方法(字节码)服务,而本地方法栈为虚拟机执行 Native 方法服务。
  • 线程私有。
  • 会出现 StackOverflowError 和 OutOfMemoryError 异常。

4、Java 堆

  • Java 虚拟机所管理的内存中最大的一块,用于存放对象实例。它是垃圾收集器管理的主要区域,也被称为"GC堆”。
  • 可细分为新生代和老年代,新生代又可细分为 Eden 空间、From Survivor 空间、To Survivor 空间。
  • 线程共享。
  • 会出现 OutOfMemoryError 异常。

5、方法区

  • 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。别名 Non-Heap(非堆)。
  • 也被称为“永久代”,因为 HotSpot 虚拟机使用永久代来实现方法区,但本质上两者并不等价。
    PS:JDK1.8 已经彻底移除了永久代,改用元空间实现方法区。元空间使用的是直接内存。
  • 线程共享。
  • 会出现 OutOfMemoryError 异常。

6、运行时常量池

  • 是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。
    PS:JKD1.7 已经从方法区移到了 Java 堆中。
  • 线程共享。
  • 会出现 OutOfMemoryError 异常。

7、直接内存

  • 不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域。但是这部分内存也被频繁使用。
  • 会出现 OutOfMemoryError 异常。

二、HotSpot 虚拟机对象探秘

1、对象的创建

类加载检查 -> 分配内存 -> 初始化零值 -> 设置对象头 -> 执行 init 方法

(1)类加载检查

虚拟机遇到 new 指令时,会先检查这个指令的参数能否在常量池中定位到一个类的符号引用,以及这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,就必须先执行相应的类加载过程。

(2)分配内存

对象所需内存的大小在类加载完成后便可确定,为对象分配内存空间等同于把一块确定大小的内存从 Java 堆中划分出来。

分配内存的两种方式:

  • 指针碰撞: Java 堆中内存规整时,将用过的内存放在一边,空闲的内存放在另一边,中间放一个指针作为分界点的指示器。分配内存时,只需把那个指针向空闲内存那边,移动一段与对象大小相等的距离即可。
  • 空闲列表: Java 堆中内存不规整时,虚拟机通过维护一个列表,记录哪些内存块是可用的。在分配时从列表中找出一块足够大的空间划分给对象实例,并更新列表上的记录。

Java 堆是否规整(是否有内存碎片),由所采用垃圾收集器的算法所决定。“标记-清除”算法会产生内存碎片,而“标记-整理”和复制算法则不会。

如何保证分配内存的线程安全:

  • CAS 同步机制:采用 CAS 配上失败重试的方式保证更新操作的原子性。
  • 本地线程分配缓冲(TLAB):每个线程在 Java 堆中预先分配一小块内存(TLAB),线程要分配内存时,先在 TLAB 上分配,TLAB 用完后再采用 CAS 同步机制进行分配。

(3)初始化零值

将分配到的内存空间初始化为零值(不包括对象头),保证对象的实例字段在 Java 代码中可以不赋初始值就直接使用。

(4)设置对象头

虚拟机需要对对象进行必要的设置,例如这个对象是哪个类的实例、如何找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等。这些信息存放在对象的对象头中。

(5)执行 init 方法

把对象按照程序员的意愿进行初始化。

2、对象的内存布局

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

(1)对象头

对象头包含两部分信息:

  • Mark Word:用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。
  • 类型指针:对象指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例。

(2)实例数据

对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。

(3)对齐填充

仅仅起着占位符的作用,不是必然存在的,也没有特别的含义。

由于 HotSpot 虚拟机的自动内存管理系统,要求对象起始地址必须是 8 字节的整倍数,换句话说,对象的大小必须是 8 字节的整倍数。而对象头部分正好是 8 字节的整倍数,因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

3、对象的访问定位

Java 程序需要通过栈上的 reference 数据来访问堆上的具体对象。目前主流的访问方式有句柄和直接指针两种。

(1)句柄

  • reference 中存储的是对象的句柄地址。
  • Java 堆中划分出一块内存作为句柄池,句柄中包含了对象实例数据与类型数据各自的具体地址信息。
  • 好处:reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,reference 本身不需要修改。

(2)直接指针

  • reference 中存储的直接就是对象的地址。
  • Java 堆对象的布局必须考虑如何放置访问类型数据的相关信息。
  • 好处:节省了一次指针定位的时间开销,速度更快。

三、OutOfMemoryError 异常

Java 虚拟机中,除了程序计数器外,其他几个运行时区域都有发生 OutOfMemoryError(OOM)异常的可能。

1、Java 堆溢出

异常堆栈信息:java.lang.OutOfMemoryError: Java heap space。

异常原因:内存泄露、内存溢出。

  • 内存泄露:存在 GC 无法回收的对象。
  • 内存溢出:堆中存活对象过多。

异常处理:

  • 通过工具查看泄露对象到 GC Roots 的引用链,从而定位出泄露代码的位置。
  • 调大堆参数(-Xmx、-Xms),例:-Xmx256m -Xms128m
  • 检查代码中是否存在对象生命周期过长的情况。

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

异常堆栈信息:java.lang.OutOfMemoryError: unable to create new native thread。

异常原因:创建线程过多。

  • 操作系统分配给每个进程的内存是有限制的,因此每个线程分配到的栈容量越大(栈是线程私有的),可创建的线程数量就越少,创建线程时就越容易把剩下的内存耗尽。

异常处理:

  • 减少线程数。
  • 更换 64 位虚拟机。
  • 减少最大堆容量(-Xmx)。
  • 减少栈容量(-Xss),例:-Xss128k

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

异常堆栈信息:java.lang.OutOfMemoryError: PermGen space。

异常原因:载入内存的类、常量过多。

异常处理:调大方法区容量(-XX:PermSize、-XX:MaxPermSize),例:-XX:PermSize=64m -XX:MaxPermSize=128m

4、本机直接内存溢出

异常堆栈信息:java.lang.OutOfMemoryError: Direct buffer memory。

异常原因:使用了 NIO 等用到直接内存的技术时就有可能出现。

异常处理:调大直接内存容量(-XX:MaxDirectMemorySize),例:-XX:MaxDirectMemorySize=512m

《深入理解 Java 虚拟机》读书笔记:Java 内存区域与内存溢出异常的更多相关文章

  1. 深入理解java虚拟机读书笔记--java内存区域和管理

    第二章:Java内存区域和内存溢出异常 2.2运行时数据区域 运行时数据区分为方法区,堆,虚拟机栈,本地方法栈,程序计数器 方法区和堆是线程共享的区域 虚拟机栈,本地方法栈,程序计数器是数据隔离的数据 ...

  2. 深入理解Java虚拟机读书笔记 -- Java内存区域

    Graal VM: Run Programs Faster Anywhere. 跨语言全栈虚拟机,可以作为"任何语言"的运行平台使用. Java内存结构 程序计数器:线程私有,较小 ...

  3. 深入理解Java虚拟机 -- 读书笔记(1):JVM运行时数据区域

    深入理解Java虚拟机 -- 读书笔记:JVM运行时数据区域 本文转载:http://blog.csdn.net/jubincn/article/details/8607790 本系列为<深入理 ...

  4. 深入理解java虚拟机---读书笔记

    第一章 走近java 1. java 技术体系: java 程序设计语言 各种硬件平台上的java虚拟机 class 文件格式 java API 类库 来自商业机构或者开源机构的第三方java类库 j ...

  5. 深入Java虚拟机读书笔记第五章Java虚拟机

    Java虚拟机 Java虚拟机之所以被称之为是虚拟的,就是因为它仅仅是由一个规范来定义的抽象计算机.因此,要运行某个Java程序,首先需要一个符合该规范的具体实现. Java虚拟机的生命周期 一个运行 ...

  6. 深入Java虚拟机读书笔记第一章Java体系结构介绍

    第1章 Java体系结构介绍 Java技术核心:Java虚拟机 Java:安全(先天防bug的设计.内存).健壮.平台无关.网络无关(底层结构上,对象序列化和RMI为分布式系统中各个部分共享对象提供了 ...

  7. 深入理解Java虚拟机读书笔记8----Java内存模型与线程

    八 Java内存模型与线程   1 Java内存模型     ---主要目标:定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节.     ---此处的变量和J ...

  8. 深入理解Java虚拟机读书笔记1----Java内存区域与HotSpot虚拟机对象

    一 Java内存区域与HotSpot虚拟机对象 1 Java技术体系.JDK.JRE?     Java技术体系包括:         · Java程序设计语言:         · 各种硬件平台上的 ...

  9. 深入理解java虚拟机读书笔记1--java内存区域

    Java在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途.创建和销毁的时间,有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,有些则是与线程一一对应,随 ...

  10. 【Todo】深入理解Java虚拟机 读书笔记

    有一个在线系列地址 <深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)> http://book.2cto.com/201306/25426.html 已经下载了这本书(60多M ...

随机推荐

  1. 百度网盘快速下载工具下载:Pandownload下载

    众所周知的百度网盘下载限速很厉害,所以就有了快速下载工具出现了.一般情况下,按照家里网速有多快,使用工具下载就有多快. 可以直接复制如下图进行下载: 另外,PanDownload[百度网盘下载工具]无 ...

  2. nginx 安装 ssl 证书

    nginx 安装 ssl 证书 关键词: pem 转 crt , 证书续期, nginx 部署 ssl 证书, 解决 SSL23_GET_SERVER_HELLO 错误. 之前免费申请的 1年的证书过 ...

  3. Rancher容器目录持久化

    最近一直在研究Rancher的持久化问题. 目前已找到两种方式实现Rancher里的容器文件实现持久化方法. 方法一: 配置映射(只适用单个文件,不适用于目录) 这个算不上是真正的持久化,只是相当于配 ...

  4. Git - ignore过滤文件

    Git - ignore 官网:https://git-scm.com/docs/gitignore 今天在初始化仓库的时候,考虑到如何过滤不需要的文件进入版本控制系统.所以去查阅了一番官方文档. 想 ...

  5. SQL Server 2019 中标量用户定义函数性能的改进

    在SQL Server中,我们通常使用用户定义的函数来编写SQL查询.UDF接受参数并将结果作为输出返回.我们可以在编程代码中使用这些UDF,并且可以快速编写查询.我们可以独立于任何其他编程代码来修改 ...

  6. java AES-256加解密解决方法

    看文件操作即可: 链接:https://pan.baidu.com/s/1dQ_-cZitxbG31JVmRi-trg 提取码:89p4 复制这段内容后打开百度网盘手机App,操作更方便哦

  7. 一定要记住的14个JVM内存配置参数

    jvm setting的参数确实比较多(Oracle官网Java HotSpot VM Options),但是作为一名java开发者,那几个最常用最基本的参数设置和意义一定要死记和理解.这里推荐一个网 ...

  8. Java 将 PPT 形状(表格、文本框、心形、图表等)保存成图片

    MS PowerPoint中的表格.文本框.心形.图表.图片等均可以称为形状,将这些形状保存成图片,便可分类储存,方便日后查找,再利用. 本文将介绍如何使用 Spire.Presentation fo ...

  9. 《算法图解》全本PDF下载附百度云链接

    作者使用Python和图画来解释算法,找了好久才找到PDF版本,末尾附百度云链接~ 作者[美]Aditya Bhargava 译者袁国忠 类别 出版 / 非虚构 出版社人民邮电出版社 / 2017-0 ...

  10. JAVA十六进制数据接收与传输

    一.十六进制转换工具类 主要包含十六进制字符串转ASCII,ASCII转十六进制字符串以及十六进制字符串转Byte数组等方法: /** * Created by wly on 2018/4/17. * ...