Java内存区域简介

前言

为什么是简介呢?因为自己实在不敢用详解两字,不过还是希望用简短易懂的语言来描述,使大家能有一个初步的认识,这些内容主要是从《深入Java虚拟机》中同时加入自己的一些零碎知识总结而来的,所以还是希望大家可以多看书,然后总结为自己的知识体系。

线程和进程

为什么会提到这两个老生常谈的东西呢?因为Java的内存区域是分为共享和非共享的,那么直接就和线程和进程相关了,线程和进程的区别在这里我就不详细介绍了,不太了解的同学可以看看这篇文章进程与线程的一个简单解释,比较形象和生动。

单核可以多线程吗

我们从这个问题入手,看看Java虚拟机是如何实现多线程的。首先,这个问题的答案是肯定的(可以),多个线程会被CPU分配不同的时间,CPU快速的切换(一般在几十毫秒)不同的线程来实现多线程,我们把分配给线程的时间叫着时间片,不停的切换时间片就是上下文切换,在一秒时间里,上下文切换的次数可以达到数千次(2000次左右)。所以接下来第一块我们需要介绍到的,就是在切换中起着很大作用的程序计数器

运行时数据区

运行时数据区

程序计数器

程序计数器是一块很小的内存,它是线程私有的,它记录着当前线程Java方法虚拟机字节码指令的地址(如果为Native方法,计数器值为空),为什么需要记录这个东西呢?因为再上下文不断切换时,我们需要保存将被切换线程执行到哪里了,然后再恢复时,可以根据保存的信息进行恢复。在这个内存区域中,是不会出现OutOfMemoryError的,也是内存区域中唯一个。

Java虚拟机栈

Java虚拟机栈也是线程私有的,它的生命周期和线程相同。一个线程中有多个方法,每一个方法都会创建一个栈帧,这个栈帧里面保存了局部变量表、操作数栈、动态链接、方法出口等信息。很多个栈帧不断的在虚拟机栈中入栈和出栈,就是这个线程不断的执行,同时随着入栈和出栈,一个方法的调用也执行完成了。

局部变量表

一个方法有一个局部变量表,它存放了方法中各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,对象引用不是对象本身,而是指向对象起始地址的引用指针、一个代表对象的句柄或者其他对象的相关位置)、returnAddress类型(指向了一条字节码指令的地址)。虚拟机栈中局部变量表的大小在运行期间是不会变化的,进入一个方法时,这个大小就已经确定了。

异常

在虚拟机栈中,可能会发生两种异常:StackOverflowError和OutOfMemoryError。发生这两种异常的情况分别是:

1)线程所请求的栈深度大于虚拟机所允许的深度。

2)如果虚拟机扩展无法申请足够的内存。

这两个描述还是比较模糊的,那么实际情况中,什么情形下会产生这两种不同的异常呢?有一个统一的结论,大家可以实际去证明,那就是:在单个线程下,无论是由于栈帧太大还是栈容量太小(大小是可配置的),但内存无法分配时,都抛出StackOverflowError。在多线程中,线程越多,供每个线程瓜分的虚拟机栈空间越少,当把最后能被瓜分的内存耗尽时,就会抛出OutOfMemoryError

本地方法栈

本地方法栈和Java虚拟机栈作用是非常相似的,Java虚拟机栈是服务于Java方法(也就是字节码),而本地方法栈是服务于Native方法的。甚至有的虚拟机(如HotSpot,目前最新版本JDK使用的虚拟机)直接将这两部分合二为一,所以同样的,本地方法栈也会抛出StackOverflowError和OutOfMemoryError。

Java堆

Java堆一般来说是Java虚拟机所管理的内存中最大的一块。它是一块被线程共享的内存区域。他存放的就是对象的实例和数组,如上面提到局部变量中对象引用就可能指向这里。这一块的内容主要就是内存回收相关的东西,这里不再深入,后面单独对内存回收做介绍。无论这块内存有多大,还是会出现无法再扩展的情况,就会抛出OutOfMemoryError异常。

方法区

这个区域和堆一样,是线程共享的,它用来存储已被虚拟机加载的类信息(类名、访问修饰符等)、常量、静态变量、即时编译器编译后的代码等数据,因此它是堆得一个逻辑区域。不过大家容易被这个名字所误导,所以需要特别注意。

运行时常量区

此区域属于方法区的一部分,在编译时,会产生一些类的常量信息,就存放在此区域中,当然在运行时,也可以产生一些常量,如调用String的intern方法,就会将该常量放入常量池中。在方法区中,如果无法满足内存分配时,将抛出OutOfMemoryError异常。

延伸知识

刚才提到,使用String的intern方法,会把常量放入方法区的运行时常量区中,不过JDK 1.6和JDK 1.7有一些区别:

1)1.6intern方法会把首次遇到的字符串实例复制到永久代中,并返回实例的引用。

2)1.7不会复制实例,而是在常量池中记录首次出现的实例引用。

坚持原创分享,您的支持将鼓励我不断前行!