最近想好好复习一下java虚拟机,我想通过深读 【理解Java虚拟机 jvm 高级特性与最佳实践】 (作者 周志明) 并且通过写一些博客总结来将该书读薄读透,这里文章内容仅仅是个人阅读后简短总结,加强学习深度的同时方便进行知识的回顾之用。如涉及版权还望周大神看到后告知一下小弟,我会第一时间将文章下线,在此强烈推荐大家买纸质图书 【理解Java虚拟机 jvm 高级特性与最佳实践】 (作者 周志明) 进行阅读,学习java虚拟机必备。努力学习只为遇到更好的你!

第2章 Java 内存区域与内存溢出异常

后面的每章介绍我都会先上引言:

Java 与 C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面 的人却想出来

该章主要介绍了3块内容
运行时数据区域HotSpot 虚拟机对象探秘实战:OutOfMemoryError 异常

1 运行时数据区域

运行是数据区域有5块 分别为 程序计数器 Java 虚拟机栈 本地方法栈 Java 堆 方法区 各个内存示例如下图:

1.1 程序计数器(Program Counter Register)

程序计数器 是记录程序下一条运行指令,运行指令包括循环,跳转等。 它是线程私有的 每个线程都有一个独立的程序计数器。由于其内存空间小 是唯一一个没有 OutOfMemoryError的区域。

在Java 虚拟机中多线程是通过线程轮流切换给处理器执行的 因此我们可以通过程序计数器来保证线程互不影响。如果运行的是java 方法程序计数器会记录下一条运行要执行的指令 如果是Native方法则 计数器的值则为空。

1.2 Java 虚拟机栈(Java Virtual Machine Stacks)

Java 虚拟机栈 是用来记录java 方法执行过程的内存区域,它是线程私有的,生命周期和线程相同。
当我们方法在创建时会创建一个栈帧用于存储局部变量,动态链接,方法出口等信息。方法的执行就是栈帧在Java 虚拟机栈中的入栈和出栈。
Java 虚拟机栈中线程请求栈的深度大于虚拟机允许的深度将抛出StackOverflowError 如果扩展无法申请到足够的内存抛出 OutOfMemoryError异常。

1.3 本地方法栈(Native Method Stack)

本地方法栈和Java 虚拟机栈作用类似 区别是 Java 虚拟机栈执行java方法 本地方法栈执行Native方法。本地方法栈也会抛出StackOverflowErrorOutOfMemoryError异常。

我们的Sun HotSopt 直接将本地方法栈和Java 虚拟机栈 合二为一。

1.4 Java 堆(Java Heap)

Java 堆的作用是 存放对象实例。它是线程共享也是垃圾收集器管理主要区域。也被成为GC 堆。该内存在虚拟机启动时进行创建。如果堆中没有内存存放我们创建的实例,并且无法进行扩展时 会抛出 OutOfMemoryError 异常

1.5 方法区(Method Area)

方法区作用是用来存储已加载类的信息,常量, 静态变量, 即时编译器编译后的代码等 ,它是线程共享的内存区域。为了与Java堆区分开也被称之为 Non-Heap 非堆。
垃圾收集器主要对方法区中的类型卸载 和常量池的内存回收 。方法区无法满足内存分配时将抛出 OutOfMemoryError 异常

垃圾收集子该区域的操作比较少 很多程序员也称之为永久代,在 JDK1.7 已经将字符串常量从永久代中移除。

1.5.1 运行时常量池(Runtime Constant Pool)

运行时常量池 是方法区的一部分 该区域用于存放编译器生成的各种字面量和符号引用。在类加载后存放到运行时常量池中。
当常量池无法在申请到内存时抛出OutOfMemoryError 异常

1.6 直接内存(Direct Memory)

JDK1.4 引入 NIO 通过通道与缓存的I/O方式 它可以使用Native 函数库直接分配的堆外内存 而这个存储就是我们直接内存。
它不是java 虚拟机中规定的内存,但是容易被我们忽视。直接内存受我们的本机总内存的影响 如果java虚拟器内存过大 导致我们的直接内存无法扩展是会抛出 OutOfMemoryError 异常

2 HotSpot 虚拟机对象探秘

虚拟机对象创建主要从三方面进行介绍 对象的创建 对象的内存布局 对象的访问定位

2.1 对象的创建

书中进行了大量的文字描述,我们就不再进行粘贴复制。通过一个流程图让大家大致了解一下对象在虚拟中创建的过程。

2.2 对象的内存布局

在HotSpot 虚拟机中 对象在内存中存储可以分为3块 对象头 实例数据 对齐填充

2.2.1 对象头

对象头有2部分数据
1. 存储对象自身运行是数据
这部分包括 哈希码 GC分代年龄 锁状态标志 线程持有的锁 等
2. 类型指针
对象指向元数据的指针。通过这个来指定对象是那个类型的实例。

2.2.2 实例数据

实例数据 是对象真正存储的有效信息。就是代码中定义各种类型字段的内容。

2.2.3 对齐填充

对齐填充并不是必然存在,由于对象起始地址必须是8字节的整倍数,当这个地址大于8整倍数时 就无法对齐,需要通过对其填充来补全。

2.3 对象的访问定位

java 程序通过栈上的引用数据来操作堆上的对象 怎么去访问对象具体的位置分为2中方式:
句柄访问直接指针 操作方式如下面2个图进行展示:

句柄访问 我们的引用都放在句柄池中 好处是当我们的对象被移动 只改句柄实例中的指针。 而引用本身不用修改。

直接指针 好处访问数度快 不用维护指针定位开销。

3 实战:OutOfMemoryError 异常

这一块主要通过正在java 代码演示如何产生 内存异常的操作,书中使用Eclipse 进行演示和介绍。强烈推荐各位将示例代码在Eclipse真实操作一遍 在结合上面概念介绍。你肯定会对Java 内存管理有深层次的理解。

3.1 Java 堆溢出

演示代码:

package cn.zhuoqianmingyue.heap;import java.util.ArrayList;
import java.util.List;public class HeapOOM {static class OOMObject{}public static void main(String[] args) {List<OOMObject> list = new ArrayList<OOMObject>();while(true) {list.add(new OOMObject());//强引用GC不能释放空间}}
}


通过-Xmx 设置最大堆内存


安装 Elipse Memory Analyzer 插件,具体操作按下图方式进行



安装完成后重启Eclipse

通过 -XX:+HeapDumpOnOutOfMemoryError 可以让虚拟机在内存溢出时Dum当前内存存储的快照用于我们内存溢出分析


执行完成后

刷新一下我们的项目在Eclispe中即可看到我们的Dump 文件如下图:


通过 Memory Analyzer可打开该文件,具体操作请根据图例一步步执行。





在上图中我们可以根据GCRoot 引用链分析 我们可以确定当前对象是否必须存活,通过调节-Xms
与 -Xmx 与机器物理内存比对看是否可以调大。或者从代码的角度检查某些对象的声明周期过长的情况 尝试减少程序运行期内存消耗。
就拿我们上面的代码如果我们代码必须需要这样处理 那么我们可以将list 根据数量进行清理一下就不会有内存溢出的情况具体代码如下:

package cn.zhuoqianmingyue.heap;import java.util.ArrayList;
import java.util.List;public class HeapOOM {static class OOMObject{}public static void main(String[] args) {List<OOMObject> list = new ArrayList<OOMObject>();while(true) {list.add(new OOMObject());//强引用GC不能释放空间if(list.size() == 100) {list.clear();}}}
}

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

在HotSopt虚拟机中由于不区分 虚拟机栈和本地方法栈所以我们设置 -Xoss 参数(本地方法栈)实际上是无效的。

3.2.1 StackOverflowError 异常

如果线程请求的栈深度大于虚拟机所允许的最大的深度 将抛出 StackOverflowError 异常
测试代码

package cn.zhuoqianmingyue.stack;public class JavaMVStackSOF {private int stackLength = 1;public void stackLeak() {stackLength++;stackLeak();}public static void main(String[] args) {JavaMVStackSOF oom = new JavaMVStackSOF();try {oom.stackLeak();} catch (Throwable e) {System.out.println("stack length :"+oom.stackLength);e.printStackTrace();}}
}

通过 -Xss设置虚拟机栈的大小。


我们将虚拟机栈的大小提升为2M 结果如下:
栈的深度明显提升了很多。

3.2.2 OutOfMemoryError 异常

如果虚拟机在扩展栈时无法申请到足够的内存空间 则抛出 OutOfMemoryError 异常。
单线程下无论是栈帧太大还是栈内存太小都会抛出StackOverflowError 异常 我们可以通过多线程的方式来进行 OutOfMemoryError 异常展示

如果你看到下面的代码切记不要在自己Windows电脑上运行 会造成系统的假死。本人亲身体验
重要的事情说三遍: 不要在自己Windows电脑上运行 不要在自己Windows电脑上运行 不要在自己Windows电脑上运行,我没有Mac 所以不知道 Mac 运行不知到是一个什么效果。

package cn.zhuoqianmingyue.stack;public class JavaVMStackOOM {private void dontStop() {while(true) {}}public void stackLeakByThread() {while(true) {Thread threa = new Thread(new Runnable() {@Overridepublic void run() {dontStop();}});threa.start();}}public static void main(String[] args) {JavaVMStackOOM oom = new JavaVMStackOOM();oom.stackLeakByThread();}
}

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

String.intern() 是一个Native方法 它的作用是 如果字符串常量池中已经包含一个等于此String对象的字符串 则返回代表池中这个字符串String对象 否则将该对象加入到常量池中并返回该对象的引用。

需要注意的是下面代码演示只在JDK1.6及以前的版本中生效 在JDK1.7 和更高的版本中不会生效
在JDK1.7 中已经将String 常量池溢出持久代了。

package cn.zhuoqianmingyue.runtimeconstant;import java.util.ArrayList;
import java.util.List;public class RuntimeConstant {public static void main(String[] args) {List<String> list = new ArrayList<String>();int i = 0;while(true) {list.add(String.valueOf(i++).intern());}}
}


下面是原书中提供的另一个有意思的代码:


这里都是false的原因是 Jdk1.6中 intern() 方法会把首次遇到字符串示例复制到永久代中 返回的是永久带中的示例 而StringBuilder 创建在java堆上所以是false
Jdk1.7 中第一次次返回true 是因为intern() 实现不会在复制实例 返回的引用和StringBuilder 的实例是同一个。 第二次为false 是因为 StringBuilder.toString() 之前已经出现过 字符串常量池已经有它的引用。 不符合首次出现的原则 因此返回false;

虽然我们不能用String.intern() 方法在JDK 1.7+ 上进行演示 但是我们可以通过CGLib 直接操作字节码运行是产生大量的动态类来演示永久带内存溢出 具体代码如下:

package cn.zhuoqianmingyue.runtimeconstant;import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/*** -XX:PermSize=10M -XX:MaxPermSize=10M* @author Administrator**/
public class JavaMethodAreaOOM {public static void main(String[] args) {while(true) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy arg3) throws Throwable {return arg3.invoke(obj, args);}});enhancer.create();}}static class OOMObject{}
}

设置永久代的大小通过下面的参数:
-XX:PermSize=10M -XX:MaxPermSize=10M

执行程序的时候出现下图中异常:

引入asm 的jar包即可

下图是测试结果:

3.4 本机直接内存溢出

本机直接内存 DirectMemory 通过设置 -XX:MaxDirectMemorySize 指定 如不制定和Java 堆最大值(-Xmx)一样。
下面的代码是越过啦 DirectByteBuffer类 直接通过反射获取Unsafe实例进行内存分配。


package cn.zhuoqianmingyue.directmemory;import java.lang.reflect.Field;import sun.misc.Unsafe;public class DirectMemoryOOM {private static final int _1MB = 1024*1024;public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {Field unsafeField= Unsafe.class.getDeclaredFields()[0];unsafeField.setAccessible(true);Unsafe unsafe = (Unsafe)unsafeField.get(null);while(true) {unsafe.allocateMemory(_1MB);}}
}


需要注意的是DirectMemory 导致的内存溢出 没有Heap Dump文件不会看见明显的异常 如果OOM
之后Dump文件很小就要考虑是不是使用了NIO .

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

  1. java语言定义一个具备栈功能的类_Java学习笔记 第二章 Java语言基础

    第二章 JAVA语言基础 一.关键字 1.关键字的定义和特点 定义:被Java语言赋予了特殊含义的单词 特点:关键字中所有的字母都为小写 2.用于定义数据类型的关键字 c;ass  interface ...

  2. 《Effective Java》学习笔记 第二章 创建和销毁对象

    第二章 创建和销毁对象 何时以及如何创建对象,何时以及如何避免创建对象,如何确保他们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作. 1 考虑用静态工厂方法代替构造器 一般在某处获取一 ...

  3. Java虚拟机学习笔记(一)--运行时数据区域

    强烈推荐一个大神的人工智能的教程:http://www.captainbed.net/zhanghan 前言 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分成若干个不同的数据区域. 程 ...

  4. 深入理解 C 指针阅读笔记 -- 第二章

    Chapter2.h #ifndef __CHAPTER_2_ #define __CHAPTER_2_/*<深入理解C指针>学习笔记 -- 第二章*//*内存泄露的两种形式1.忘记回收内 ...

  5. 《Go语言圣经》学习笔记 第二章 程序结构

    Go语言圣经学习笔记 第二章 程序结构 目录 命名 声明 变量 赋值 类型 包和文件 作用域 注:学习<Go语言圣经>笔记,PDF点击下载,建议看书. Go语言小白学习笔记,几乎是书上的内 ...

  6. Java 虚拟机学习笔记 | 类加载过程和对象的创建流程

    前言 创建对象是 Java 语言绕不开的话题,那么对象是如何创建出来的呢?我们今天就来聊一聊.对象创建第一步就是检查类是否加载,而类的加载又牵扯到类的加载过程.如果单说对象的创建而绕开类的加载过程,感 ...

  7. JavaSE入门0基础笔记 第二章Java基础语法

    JavaSE入门0基础笔记 第二章Java基础语法 1.运算符 1.1算术运算符 1.1.1运算符和表达式 1.1.2 算术运算符 1.1.3字符的"+"操作 1.1.4 字符串中 ...

  8. 小吴的《机器学习 周志华》学习笔记 第二章 模型评估与选择

    小吴的<机器学习 周志华>学习笔记 第二章 模型评估与选择 上一周我们介绍了第一章的基础概念,这一次将带来第二章的前三节.后面的2.4 比较检验与2.5 偏差与方差,涉及概率论与数理统计概 ...

  9. 小吴的《机器学习 周志华》学习笔记 第二章 2.4 比较检验、2.5 偏差与方差

    小吴的<机器学习 周志华>学习笔记 第二章 2.4 比较检验. 2.5 偏差与方差 2.4 比较检验 上一周提到了实验的评价方法和性能量度,步骤简单可以看成:先使用某种实验评估方法测得学习 ...

  10. PhalAPI学习笔记 ——— 第二章接口服务请求

    PhalAPI学习笔记 --- 第二章接口服务请求 前言 接口服务请求 接口服务请求案例 自定义接口路由 开启匹配路由 配置路由规则 nginx apache 服务请求 结束语 前言 公司业务需要转学 ...

最新文章

  1. Redis之数据结构底层实现
  2. Java中如何实现代理机制(JDK、CGLIB)
  3. 流言终结者——C语言内存管理
  4. 底层框架_百度PaddleCV硬核升级 与飞桨底层框架领先能力全面结合实现软硬一体...
  5. Silverlight实现强壮、可复用的拖放行为
  6. Mesos+Marathon docker 集群管理
  7. 数据分析 一文搞懂什么是RFM模型
  8. 未来自动阅读是什么?
  9. MATLAB学习笔记:求偏导
  10. 【ADNI】基本概念整理
  11. 改cpp[1] Vscode Hex Editor,在vscode中查看内存
  12. 虚拟专用网和IPv6路由配置【Cisco】
  13. hibernate-validate如何校验controller+service+分组校验+自定义注解实现校验规则等
  14. png格式图片 转 icns格式图标
  15. VB.NET 文本框获得焦点
  16. Excel数据透视表经典教程六《报表布局》
  17. 连就连,你我相约定百年。谁若97岁死,奈何桥上等三年!
  18. 程序员养花几个实用小技巧
  19. Vue刷新页面方式详解
  20. 【技术向】vbs实现远程控制和传输文件

热门文章

  1. python创建sqlite数据库表_python数据库sqlite基础(一)-------数据库创建,表的建立,数据录入,数据查询...
  2. 楼层平面放线及标高实测记录_建筑工程定位放线技术交底施工单位测量人员必备...
  3. OpenShift 4 - 在控制台中安装使用 Web Terminal
  4. 使用Jenkins在Azure Web App上进行ASP.NET Core应用程序的持续集成和部署(CI/CD)–第1天
  5. C#:生成哈希字符串
  6. 微软 WPF 框架源码现已托管至 GitHub
  7. python 两点曲线_python机器学习分类模型评估
  8. python与数据库完整项目_python入门:操作数据库项目实例分享
  9. oracle在指定列后添加列,oracle添加列到指定位置
  10. python mongodb 异步_Python异步读写Mongodb(motor+asyncio)