内容简介:对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。线程私有的包括:程序计数器、虚拟机栈、本地方法栈线程共享的:堆、方法区、直接内存
对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发 程序员 这样为内一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。
运行时数据区
线程私有的包括:程序计数器、虚拟机栈、本地方法栈
线程共享的:堆、方法区、直接内存
程序计数器
记录正在执行的虚拟机字节码指令的地址。由于是多线程,线程轮流切换,切换线程后为了能恢复到正常的执行位置,每个线程需要一个独立的程序计数器。如果执行的是本地(Naive)方法,计数器为空。此内存区域是唯一一个没有规定任何OutOfMemoryError情况的区域,它的生命周期随着线程创建而创建,随着线程结束而死亡。
Java虚拟机栈
Java 内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。
Java虚拟机栈也是线程私有的,生命周期与线程相同。描述的是Java方法执行的内存模型。每个Java方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用、动态链接、程序出口等信息。每一个方法从调用到执行完成的过程,对应一个栈帧在Java虚拟机栈中入栈和出栈的过程。
局部变量表存放了编译器可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double)、对象引用(reference类型,不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddressleixing (字节码指令地址)。局部变量表所需内存在编译期间完成分配,运行期间不会改变。
可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小: java -Xss 512M
可能抛出的异常情况:
- 若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候(栈帧过多),就抛出StackOverFlowError异常。
- 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。
本地方法栈
本地方法栈与 Java 虚拟机栈类似,虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。有的虚拟机如HotSpot虚拟机把二者合二为一。抛出的异常与上述一致。
本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
Java堆
Java堆是整个虚拟机所管理的最大内存区域,所有的对象创建都是在这个区域进行内存分配,是被所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
Java堆是垃圾收集器管理的主要区域(方法区也需要回收),因此又称为GC堆(Garbage Collected Heap)。现在收集器基本采用分代收集算法,可以将堆分为新生代和老年代。划分的好处是可以方便垃圾的准确回收。
Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。堆还可以动态增加其内存,当堆中无法申请到新内存创建实例,并且堆也无法再扩展时,将会抛出OutOfMemroyError。
可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
java -Xms1M -Xmx2M
方法区
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区也是所有线程共享。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。
方法区和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。
在HotSpot虚拟机中,把方法区当做永久代来进行GC,对起回收的目标主要是针对常量池的回收以及对类型的卸载,但是一般比较难实现。垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。在JDK1.8中,已经移除了永久代,用元空间来替代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。
运行时常量池
运行时常量池是方法区中的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)
既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
《Java 中几种常量池的区分》: blog.csdn.net/qq_26222859…
直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致OutOfMemoryError异常出现。
JDK1.4中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它可以直接使用Native函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。
本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Automate This
Christopher Steiner / Portfolio / 2013-8-9 / USD 25.95
"The rousing story of the last gasp of human agency and how today's best and brightest minds are endeavoring to put an end to it." It used to be that to diagnose an illness, interpret legal docume......一起来看看 《Automate This》 这本书的介绍吧!