Java内存区域

对于 Java 程序员来讲,在虚拟机自动内存管理机制下,再也不须要像C/C++程序开发程序员这样为内一个 new 操做去写对应的 delete/free 操做,不容易出现内存泄漏和内存溢出问题。正是由于 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,若是不了解虚拟机是怎样使用内存的,那么排查错误将会是一个很是艰巨的任务。java

运行时数据区

线程私有的包括:程序计数器、虚拟机栈、本地方法栈程序员

线程共享的:堆、方法区、直接内存算法

程序计数器

记录正在执行的虚拟机字节码指令的地址。因为是多线程,线程轮流切换,切换线程后为了能恢复到正常的执行位置,每一个线程须要一个独立的程序计数器。若是执行的是本地(Naive)方法,计数器为空。此内存区域是惟一一个没有规定任何OutOfMemoryError状况的区域,它的生命周期随着线程建立而建立,随着线程结束而死亡。sql

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)中开辟了一块区域存放运行时常量池。

直接内存

直接内存并非虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,可是这部份内存也被频繁地使用。并且也可能致使OutOfMemoryError异常出现。

JDK1.4中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它能够直接使用Native函数库直接分配堆外内存,而后经过一个存储在 Java 堆中的 DirectByteBuffer 对象做为这块内存的引用进行操做。这样就能在一些场景中显著提升性能,由于避免了在 Java 堆和 Native 堆之间来回复制数据。

本机直接内存的分配不会收到 Java 堆的限制,可是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

欢迎工做一到五年的Java工程师朋友们加入Java架构开发: 855835163 群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用本身每一分每一秒的时间来学习提高本身,不要再用"没有时间“来掩饰本身思想上的懒惰!趁年轻,使劲拼,给将来的本身一个交代!