快速认识Java内存区域划分

来源:互联网 发布:中国亚投行失败了知乎 编辑:程序博客网 时间:2024/05/17 22:56

快速认识Java内存区域划分

Java内存区域

Java 内存区域中比较重要也是经常被提到的几部分是:程序计数器,栈(Stack),堆(Heap)和方法区,它们都定义在被称作运行时数据区(Runing Data Area)的区域中。

其中程序计数器与栈(Stack)是随线程启动而生,线程结束而灭的,也就属于线程私有。而堆(Heap)和方法区是由JVM启动时创建切被所有线程共享的。

程序计数器

我们知道所有程序代码在最底层都是要被转化为机器指令才能够被电脑识别并执行。当前程序不在是执行1+1或简单的打印字符串(实际上执行打印字符串相对于1+1已经是比较复杂的功能了,包含多个指令),而是要完成更加复杂的功能时,其所包含的指令也就更多,那么为了按逻辑顺序执行这些指令,就需要一个计数器来帮助一条条提取指令或指出下一条指令的位置。这里说的是CPU中的程序计数器的概念,换成JVM 程序计数器,其所承担的任务是基本一样的,只需要将指令换成字节码行号即可。

JVM的字节码解释器通过改变程序计数器的当前值,来提取下一条需要执行字节码的行号(既被编译为二进制的java代码)。所有的完成分支,循环,跳转,异常处理,线程恢复等的功能都是依赖它来完成的。

另外,由于Java的多线程特性(通过轮流切换并分配CPU的使用权,CPU在同一时间也只会执行一个线程中指令),为了在线程切换后能够恢复到正确位置,所以每个线程都有自己的独立的 程序计数器,这就是所谓的线程私有。

栈(Stack)

需要特别指出的是,这里说的栈(Stack)是指虚拟机栈 (在jvm规范中,栈分为虚拟机栈也就是执行Java 方法时用到的,还有本地方法栈,两者区别在与本地方法栈为执行Native方法服务,Sun公司的jvm 称为HotSpot,它将虚拟机栈与本地方法栈合二唯一了)。

相比程序计数器,栈(Stack)的出镜率更高,也离开发人员更近。在栈(Stack)中包含了用于Java方法执行时所需要的局部变量表,操作数,动态链接以及方法出口等信息。由于操作数,我们常说的栈其实只是其中的局部变量表(至少我这个级别的coder是这样),这主要是因为操作数,动态链接以及方法出口等与我们关系并不大,这也就造成了局部变量表成为栈(Stack)的代名词。

之所以局部变量表被我们常常提到,是因为他的作用。局部变量表是用于存放编译期间就已知的基本数据类型以及对象引用(地址指针或对象句柄)。

快速认识Java内存区域划分

以上代码中的 int a 与 User user 都是存放在局部变量表中。其中 a 是基本数据类型,它指向了一个值为1的内存空间,该内存空间的大小有类型 int 限定。user 则存放的是 new User() 这个对象在堆(Heap)中的内存位置。另外,以上代码之所以要声明一个方法体,是为了强调局部变量概念,如果 a 和 use 是全局变量,那么它们则在堆内存中属于对象,而如果他们是加上关键static的静态变量或者加上final的常量,则会在方法区的常量池中属于类。

栈是线程私有的,随线程结束而空间被回收。栈的大小是有限制的,当出现线程请求深度大于栈的最大深度则会出现StackOverFlow异常;而当Stack需要动态扩展而内存没有足够空间时则会出现OutOfMemoryError异常。

堆(Heap)

堆的唯一作用就是存放所有对象实例与数组的。它是线程共享的,是Jvm所有管理的内存中最大的一块,也是垃圾收集器GC管理的主要区域。

为了GC更好的进行内存回收,大部分GC使用了分代收集算法,它将堆划分了新生代与老年代。其中新生代又被划分为Eden空间、From Survivor空间和To Survivor空间(Eden是伊甸园,Survivor是幸存者,新创建的对象会优先在Eden中,当第一次GC清理时未被清理的对象则会移动到Survivor空间。这部分会在将来讨论GC时细说)。

堆的空间大小也是有限制的。我们可以通过参数-Xmx与-Xms来制定最大与最小空间。当超出-Xmx所设置的量时,则出现OutOfMemoryError:heapSpace 异常;

方法区

方法区用于存储虚拟机启动时加载的类信息,常量和静态变量。与堆一样,方法区也是线程共享的。与其他版本的JVM不同,SUN 公司的Hotspot 虚拟机将方法区*划分为永久代(Permanent Generation)。

大多数JAVA开发人员,尤其是会使用很多三方JAR的WEB开发人员都应该见过OutOfMemoryError:PermGen space 这个异常信息。出现原因便是方法区发生了内存溢出。虽然GC在方法区也会进行回收,但其目的是卸载类和回收常量池,大部分情况下,回收效果都不是很好。方法区的溢出在使用如Spring或Hibernate(这些框架会在运行时通过CGLib字节码技术来增强原有类),或者有大量JSP文件,又或者OSGI等会有大量动态生成的类被加载到方法区的应用中更容易出现。 在不能精简加载的jar和应用本身包含的类或常量,静态变量的情况下,可通过参数-XX:PermSize和-XX:MaxPermSize参数来设置方法区大小,来减少OutOfMemoryError:PermGen space异常的出现。