天天看点

[jjzhu学java]深入理解JVM笔记之内存管理机制深入理解JVM笔记之内存管理机制

<a href="#%e6%b7%b1%e5%85%a5%e7%90%86%e8%a7%a3jvm%e7%ac%94%e8%ae%b0%e4%b9%8b%e5%86%85%e5%ad%98%e7%ae%a1%e7%90%86%e6%9c%ba%e5%88%b6">深入理解jvm笔记之内存管理机制</a>

<a href="#%e8%bf%90%e8%a1%8c%e6%97%b6%e6%95%b0%e6%8d%ae%e5%8c%ba%e5%9f%9f">运行时数据区域</a>

<a href="#%e7%a8%8b%e5%ba%8f%e8%ae%a1%e6%95%b0%e5%99%a8">程序计数器</a>

<a href="#jvm%e6%a0%88">jvm栈</a>

<a href="#%e6%9c%ac%e5%9c%b0%e6%96%b9%e6%b3%95%e6%a0%88">本地方法栈</a>

<a href="#java%e5%a0%86">java堆</a>

<a href="#%e6%96%b9%e6%b3%95%e5%8c%ba">方法区</a>

<a href="#%e8%bf%90%e8%a1%8c%e6%97%b6%e5%b8%b8%e9%87%8f%e6%b1%a0">运行时常量池</a>

<a href="#%e7%9b%b4%e6%8e%a5%e5%86%85%e5%ad%98">直接内存</a>

<a href="#%e5%af%b9%e8%b1%a1%e8%ae%bf%e9%97%ae">对象访问</a>

<a href="#outofmemoryerror%e5%bc%82%e5%b8%b8">outofmemoryerror异常</a>

<a href="#java%e5%a0%86%e6%ba%a2%e5%87%ba%e7%a4%ba%e4%be%8b">java堆溢出示例</a>

<a href="#jvm%e6%a0%88%e5%92%8c%e6%9c%ac%e5%9c%b0%e6%96%b9%e6%b3%95%e6%a0%88%e6%ba%a2%e5%87%ba">jvm栈和本地方法栈溢出</a>

<a href="#%e8%bf%90%e8%a1%8c%e6%97%b6%e5%b8%b8%e9%87%8f%e6%b1%a0%e6%ba%a2%e5%87%ba">运行时常量池溢出</a>

<a href="#%e6%9c%ac%e6%9c%ba%e7%9b%b4%e6%8e%a5%e5%86%85%e5%ad%98%e6%ba%a2%e5%87%ba">本机直接内存溢出</a>

每个线程都有一个程序计数器(pc),是当前线程所执行的字节码的行号指示器,通过改变程序计数器的值来选取下一条指令。各线程之间的计数器互不影响,是线程私有的内存。

如果线程执行的是一个java方法,则计数器记录的为正在执行的字节码指令的地址,如果执行的是natvie方法,这计数器的值为空(undifined)。

程序计算器所在的内存区域是唯一一个jvm规范中没有规定oomerror的内存区域

jvm栈也是每个线程所私有的内存区域,随着线程的创建而分配,线程的消亡而回收。jvm栈是描述java方法执行的内存模型,每个java方法在执行的时候,都会创建一个栈帧(stack frame),其作用是用来保存java方法中的局部变量、操作栈、方法出口等信息。每一个java方法从被调用到执行完毕的过程,都伴随着对应的栈帧在jvm栈中的进栈和入栈。

jvm规范中,该区域中有两个异常状况:

-stackoverflowerror:线程请求的栈深度超过了jvm栈所允许的栈深度

-outofmemoryerror:如果jvm栈是动态可扩展的,在无法申请到足够内存时,抛出该异常

本地方栈(native method stack)是描述虚拟机所用到的本地方法的内存模型,与jvm栈类似,也会抛出stackoverflowerror和outofmemoryerror

java堆是java虚拟机所管理的最大的内存区域,我们所说的垃圾回收,就是对该区域的内存进行回收,所以也叫gc堆。java堆是所有线程所公用的内存区域,程序中的对象、数组都存放在该区域中。

从内存回收的角度来看,由于现在的收集器所采用的都是分代收集算法,所以java堆可以在进一步细分为:新生代和老年代,在新生代中,又可以划分为eden、from survivor、to survivor等空间。从内存分配的角度来看,java堆可能划分为多个线程私有的分配缓存区。

java堆可以是物理上不连续但逻辑上要连续的内存空间,可以通过指定-xmx(最大)、-xms(最小)、-xmn(新生代)等vm参数来设置java堆得大小。若有新对象申请内存空间但是java堆没有足够的内存分配时,会报oomerror。

方法区(method area)也是各线程共享的内存区域,用于存储被虚拟机加载的类信息、常量、静态常量、编译后的代码等数据看,其还有一个别名叫non-heap(非堆),与java堆加以区分。在hotspot虚拟机上,也可叫做“永久代”(permanent generation),本质上不等价(hotspot将gc收集扩展到了该区域)。

若要对该区域进行gc,主要是回收常量池以及对类型的卸载。该区域在内存满后也会抛出oomerror异常。

运行时常量池(runtime constant pool)是方法区内的一部分,class文件中除了类的版本、字段、方法、接口等描述信息,还常量池表,用于存放编译期间生成的葛总常量和符号引,这部分内容在类加载后存放运行时常量池这种(class如何加载会在后面章节提及)。运行时常量池具备动态性,java语言不单单在编译期间会产生常量,在运行期间也可能将新的常量放入运行时常量池中,如开发人员用了string.intern()方法,之后的异常测试就是用该方法引起常量池抛出oomerror。

直接内存(direct memory)不是虚拟机运行时数据区域的一部分,也不是jvm规范中定义的内存区域,但其也频繁被使用。自jdk1.4后,引入了基于通道(channel)与缓冲区的i/o方式,它可以使用native函数库直接分配堆外内存,并通过java堆中的directbytebuffer对象操作该块内存。避免了java堆和native堆之间的数据复制,提高了性能。

本机直接内存不受java堆大小限制,但是受本机总内存大小和处理器的寻址空间限制。在动态扩展时也会抛出oomerror异常。

之前对虚拟机运行时数据区域进行了描述,现在通过实例来验证oom异常发生的情况,可以进一步了解jvm各内存区域的存储内容,及发生异常的情况。

测试的java版本为:

java version “1.7.0_80” java(tm) se runtime environment (build 1.7.0_80-b15) java hotspot(tm) 64-bit server vm (build 24.80-b11, mixed mode)

之前已经提到,对象实例都存储在java堆中,所以可以不断的创建对象且保证对象不被gc就可以让java堆溢出

编写如下代码清单:

并配置运行时的vm参数:

[jjzhu学java]深入理解JVM笔记之内存管理机制深入理解JVM笔记之内存管理机制

这里-xx:useparnewgc是指定用parnew收集器,默认的是

参数解释: -xms:20m:java堆最小20m

-xmx:20m:java堆最大20m(避免动态扩展)

-xx:+heapdumponoutofmemoryerror:开启该选项,可以让虚拟机在抛出outofmemoryerror时dump出当前内存堆转存快照

运行后,就会有如下输出:

之前有提过,hotspot虚拟机并没有区分jvm和本地方法栈。在hotspot虚拟机中可以通过制定-xss参数来指定栈的大小。

编写如下测试代码:

运行后就会报stackoverflowerror异常:

string.intern()方法可以向运行时常量池中添加内,所以要达到运行时常量池溢出,执行用本地方法intern()像池中不断添加字符串即可,可以同过-xx:permsize -xx:maxpermsize限制方法区大小。

本机直接内存可以通过 -xx:maxdirectmemorysize指定内存大小,若不指定,则默认与java堆得最大值一样。