深入了解JVM笔记(1)—内存区域与内存溢出异常

标签: jvm 笔记 内存 | 发表时间:2013-04-06 12:55 | 作者:小学徒V
出处:http://www.cnblogs.com/

  在前面的几篇博文中,我们一起简单的了解jvm的基本知识,例如jvm对字符串的处理等等,或许大家看完后就把这当成一条准则来记住了,但是一些比较好奇的朋友有没有想过,这是为什么呢?他的原理是什么呢?下面就让我们开始一步一步的深入学习。

  在这篇博文中呢,我打算主要就讲Java内存区域与内存溢出异常吧。下面言归正传吧。

1.Java虚拟机运行时数据区

  在前面的几篇博文中,我们只是简单的把内存区域分为了堆和栈,但其实,这种分法是十分粗糙的,jvm在实际运行的时候,内存区域的划分绝对不是那么简简单单的就两块,我们一起看下面这个图就知道了。

从上图我们知道了,JVM虚拟机运行时数据区主要划分为:方法区、虚拟机栈、本地方法栈、堆、程序计数器。

1.1程序计数器

虽然在上图中,程序计数器这块占用的区域画的很大,但其实,在内存中,它只是较小的一块内存空间。

1)生命周期:线程私有的,与它所绑定的线程相同。

2)作用:当前线程所执行的字节码的行号指示器,简单的可以理解成,程序计数器记录着下一行要执行的代码行数,比如我们经常写的分支、循环、跳转都是通过改变该计数器的值来完成的。如果一个线程正在执行的是Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器则为空。

3)程序运行时该区域可能发生的异常:此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

1.2Java虚拟机栈

其实这里所说的栈,就是前面我们博文中粗糙分法堆栈中的栈了。精确的讲的话,其实是Java虚拟机栈中的局部变量表部分吧;同时这里是专门为虚拟机执行java方法服务的,为什么这么说呢?因为内存区域中还有本地方法栈,本地方法栈是只为虚拟机使用到的Native方法服务的。

1)生命周期:线程私有的,与它所绑定的线程相同。

2)作用:每个方法被执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。其中局部变量表存储的是以下类型:

①基本数据类型:boolean, byte, char, short, int, float, long, double;

②对象引用:reference类型,它不等于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用文章,也可能是指向一个对象句柄或其他与此对象有关的位置;

③returnAddress类型:指向了一条字节码指令的地址,简单的说,就是下一条要执行的代码的地址;

3)程序运行时该区域可能发生的异常:

线程请求栈深度 > 虚拟机允许深度,抛出StackOverflowError;eg:

1 package edu.outofmemoryerror.heap;
2
3 public class JavaStackSOF {
4 private int stackLength = 1;
5
6 public void stackLeak() {
7 stackLength++;
8 stackLeak();
9 }
10
11 /**
12 * -Xss128K
13 * @param args
14 */
15 public static void main(String[] args) {
16 JavaStackSOF sf = new JavaStackSOF();
17 sf.stackLeak();
18 }
19 }

 

运行结果:

注意在运行的时候注意要改变那些参数;如果是通过控制台命令来执行的,可以直接跟在Java命令之后书写,如果是通过Eclipse来执行的可以在下面页面添加上再来执行,如图:

该栈允许动态拓展,当无法申请到足够的空间时,抛出OutOfMemoryError异常;

1.3本地方法栈

本地方法栈跟虚拟机栈相似,他们的区别在前面已经提过了:本地方法栈是只为虚拟机使用到的Native方法服务的,而java虚拟机栈是专门为虚拟机执行java方法服务的。

1)生命周期:线程私有的,与它所绑定的线程相同。

2)作用:为使用到的Native方法服务。

3)程序运行时该区域可能发生的异常:跟虚拟机栈一样。

1.4Java堆(Java Heap)

对于很多应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。

1)生命周期:线程共享的,在虚拟机启动时就创建

2)作用:存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

3)程序运行时该区域可能发生的异常:堆中没有内存完成实例分配或者堆无法再拓展时,将会抛出OutOfMemoryError异常,eg:

1 package edu.outofmemoryerror.heap;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 public class HeapOOM {
7 static class OOMObject { }
8
9 /**
10 * -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
11 * @param args
12 */
13 public static void main(String[] args) {
14 List<OOMObject> list = new ArrayList<OOMObject>();
15 while (true) {
16 list.add(new OOMObject());
17 }
18 }
19 }

 

运行结果:

 

 

1.5方法区(Method Area)

方法区有一个别名叫做非堆(Non-Heap),对于习惯在HotSpot虚拟机上开发和部署程序的开发者来说,它也叫“永久代”。

1)生命周期:线程共享的,在虚拟机启动时就创建

2)作用:存储已被虚拟机加载的类信息、常量、静态变量,也就是编译器编译后的代码数据。

3)程序运行时该区域可能发生的异常:当方法区无法满足内存分配需求时,将会抛出OutOfMemoryError异常。比如java.lang.OutOfMemoryError,这个错误其实对于经常使用SSh框架进行开发的我们应该很熟悉,它就是由于方法区空间不足导致的内存溢出。

因为该区域中的垃圾收集行为是比较少出现的,所以对象相当于永久不被回收一般,所以在这个区域就叫做“永久代”了。

1.5.1运行时常量池

运行常量池是方法区的一部分。

1)生命周期:线程共享的,在虚拟机启动时就创建

2)作用:存放Class文件中类的版本、字段、方法、接口等描述信息外,还有一些信息是常量池,用于存放编译期间生产的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行是常量池中。

3)程序运行时该区域可能发生的异常:运行时是方法区的一部分,自然收到方法区内存的限制,当常量池无法在申请到内存是就会抛出OutOfMemoryError异常

看到这里,不知道大家会不会想起前面一篇博文中我们说的JVM对字符串的处理呢?

没错,在那篇博文中讲的Java字符串缓冲池,其实就是运行时常量池的一部分,而运行池是在方法区中的,方法区中的垃圾收集行为是几乎没有的,相当于不会被回收。这就是前面的JVM对字符串处理的原理了。

1.6直接内存

其实直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中的一部分,但是这部分也频繁的使用,也可能会导致OutOfMemoryError异常的出现。

2引用对象的访问

在之前我们有一篇博文讲了关于对象与内存管理的知识,但是那里只是比较简单的,下面我们更加深入了解一下,在Java语言中,对象访问是如何进行的?

其实对象访问并不像前面那篇博文中讲的那么简单只涉及到堆和栈,因为即使是最简单的访问,也会涉及到Java虚拟机栈,Java堆,方法区这三个重要的内存区域。之所以只讲堆和栈是因为开发人员在大多数情况下关注的最多的是堆和栈两个区域。

关于基本类型的访问,在前面那篇博文中我们已经讲了,下面,我们先提一下相关的概念:

1)对象类型数据:指的是该对象的对象类型,父类,实现的接口,方法等,其实就是前面说的,方法区存储的是加载的对象的信息。

2)对象实例数据:指的是该类实例化后的对象的实例变量的值。

接着,我们继续重点深入讲解下关于引用类型对象的两种访问方式:

1)使用句柄方式访问

使用这种对象的访问方式的优势是:存储的是稳定的句柄地址,在对象移动时只会改变句柄中的实例数据指针,而reference本身不用改变。

使用句柄池访问方式,Java堆中将会划分出一块内存来作文句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象实力数据和类型数据各自的具体地址信息。

其实从上图中,我们还能看出一个知识点,那就是:64位长度的long和double类型的数据会占用2个局部变量空间

2)使用直接指针访问方式

使用这种访问方式的优势是:访问速度快。

 

 

 [本文为原创,转载请注明出处: http://www.cnblogs.com/xiaoxuetu]

本文链接

相关 [jvm 笔记 内存] 推荐:

深入了解JVM笔记(1)—内存区域与内存溢出异常

- - 博客园_首页
  在前面的几篇博文中,我们一起简单的了解jvm的基本知识,例如jvm对字符串的处理等等,或许大家看完后就把这当成一条准则来记住了,但是一些比较好奇的朋友有没有想过,这是为什么呢. 下面就让我们开始一步一步的深入学习.   在这篇博文中呢,我打算主要就讲Java内存区域与内存溢出异常吧. 1.Java虚拟机运行时数据区.

JVM内存分配

- - 移动开发 - ITeye博客
计算机内存,它算是CPU与计算机打交道最频繁的区域,所有数据都是先经过硬盘至内存,然后由CPU再从内存中获取数据进行处理,又将数据保存到内存,通过分页或分片技术将内存中的数据再flush至硬盘. 那JVM的内存结构到底是如何呢. JVM做为一个运行在操作系统上,但又独立于os运行的平台,它的内存至少应该包括象寄存器、堆栈等区域.

JVM原理分析笔记

- - Java - 编程语言 - ITeye博客
1.Javac编译器的作用. 将符合Java语言规范的源代码转化成符合Java虚拟机规范的Java字节码. 2.编译器主要的几个处理阶段. 词法分析、语法分析、语义分析和代码生成,基于访问者模式来遍历语法树的过程. 二.ClassLoader. 将Class加载到JVM中,审查每个类应该由谁加载,将Class字节码重新解析成JVM统一要求的对象格式.

jvm内存映像分析

- - ITeye博客
     jdk自带的jmap就是java内存映像工具,可以用于上生成堆转储快照:. 在eclipse中启动一个java类,打开jdk安装目录下的C:\Program Files\Java\jdk1.6.0_11\bin目录,双击jconsole.exe,显示连接窗口:.  ,单击pid为6920的选项,点连接进入,可以看到jvm运行时的多种参数,.

JVM内存的调优

- - ITeye博客
默认的 java 虚拟机的大小比较小,在对大数据进行处理时 java 就会报错: java.lang.OutOfMemoryError. 设置 jvm 内存的方法,对于单独的 .class ,可以用下面的方法对 Test 运行时的 jvm 内存进行设置. -Xms 是设置内存初始化的大小. -Xmx 是设置最大能够使用内存的大小(最好不要超过物理内存大小).

MAT JVM内存分析

- - 开源软件 - ITeye博客
我们使用的是 Eclipse Memory Analyzer V0.8,Sun JDK 6. 和其他插件的安装非常类似,MAT 支持两种安装方式,一种是“单机版“的,也就是说用户不必安装 Eclipse IDE 环境,MAT 作为一个独立的 Eclipse RCP 应用运行;另一种是”集成版“的,也就是说 MAT 也可以作为 Eclipse IDE 的一部分,和现有的开发平台集成.

[译] HotSpot JVM 内存管理

- - IT瘾-dev
HotSpot JVM 内存管理. 更新时间:2018-03-28. 关于 JVM 内存管理或者说垃圾收集,大家可能看过很多的文章了,笔者准备给大家总结下. 这算是系列的第一篇,接下来一段时间会持续更新. 本文主要是翻译《 Memory Management in the Java HotSpot Virtual Machine》白皮书的前四章内容,这是 2006 的老文章了,当年发布这篇文章的还是 Sun Microsystems,以后应该会越来越少人记得这家曾经无比伟大的公司了.

【JVM】HotSpot JVM内存管理和GC策略总结

- - ITeye博客
JVM的相关知识是学习java高级特性必须要去深入学习的. 平时也有一些学习和实践,不过总结比较少. 今天有时间总结一下最基础的内存模型和GC策略的知识,在此记录一下. hotspot jvm内存模型. hotspot的内存模型很多地方都有类似总结,我也简单总结了一下,大概可以用下图表示:. 1.线程栈:线程创建是会为每个线程创建一个线程栈,线程栈里面会为每个方法调用创建一个栈帧.

java 内存移到堆外!!! Jvm gcih 淘宝优化JVM实践

- - CSDN博客互联网推荐文章
出自Jvm  GC-Invisible Heap. GC-Invisible Heap,简称GCIH,是一种将Java对象从Java堆内移动到堆外,并且可以在JVM间共享这些对象的技术. GCIH顾名思义就是GC访问不到的堆,它是对JVM内存管理机制的一个有益的补充. 在某些特殊的应用中有大量生命周期很长的对象,在应用运行的整个过程中它们都存在,不需要被GC回收.

JVM内存管理学习总结(一)

- - CSDN博客互联网推荐文章
I.JVM进程的生命周期. JVM实例的生命周期和java程序的生命周期保持一致,即一个新的程序启动则产生一个新的JVM进程实例,程序结束则JVM进程实例伴随着消失. 那么程序启动和程序终止就是JVM实例生命周期的两个边界,两个边界点可以这么理解:一个拥有程序入口(main函数)的class在执行main方法时,相应的JVM就被创建了(即JVM生命周期的起点),当由此main函数启动的所有非守护线程都终止时,JVM即退出(JVM实例生命周期的终点).