JVM内存管理 参考资料:《深入理解Java虚拟机》——周志明 著;
Java内存区域
Java和C/C++的内存管理是不同的。
Java将内存管理全权交给了JVM处理,而C/C++则是由程序员自己控制,虽然这样比较麻烦,但其内存管理相比于Java来说是更透明的,程序员更清楚的知道自己对内存做了啥。
而大多数入门Java的书籍对内存管理只以一笔带过,叫新手不用花心思去了解它,当然这种内存管理机制确实比较方便,但却更难控制。
1. JVM数据区 JDK1.6
数据区中的红色区域为线程共享的区域
数据区中的黑色区域为线程私有的区域
1.1线程私有区域:虚拟机栈、本地方法栈、程序计数器
先要说明一下什么是本地方法(Native Method):
在Java中其实允许执行非Java编程语言编写的程序,这种非java方法叫作Native Method,调用Native Method将由JVM负责控制。
Java方法就是由Java编程语言编写的方法,而Native方法,也叫本地方法,一般是指是由JVM调用的本地中由C语言编写的方法,具体的Java引用C的方法这里不再讨论。
1.1.1程序计数器:
一块较小的内存区域,用于指示线程中执行的字节码的行号,并完成执行位置的各种跳转,因为线程切换后需要重新指示线程的执行位置,每条线程之间不能相互影响,故该内存区域为线程私有。
1.1.2虚拟机栈:
描述了Java方法执行的内存模型,也有叫它Java栈的说法(?)。
方法执行时将创建一个栈帧用于存储方法内的局部变量表、动态链接、方法出口等信息。
调用一个方法代表着一个栈帧再虚拟机栈中入栈到出栈的过程。
一般所说的栈,就是指的虚拟机栈。
抛出异常情况:
StackOverflowError:方法请求的栈深度超过了虚拟机栈提供的最大深度。
OutOfMemoryError:虚拟机栈在动态扩展以满足方法请求的栈深度时,请求不到足够的内存,多发生于系统内存不足。
1.1.3本地方法栈:
控制了本地方法的执行,并不一定是C语言编写的方法,也可能是其他语言,具体的实现依照使用的虚拟机而定。
在HotSpot虚拟机中本地方法栈和虚拟机栈合二为一。
抛出异常情况:
与虚拟机栈相同。
1.2线程公共区域:方法区、堆
1.2.1 堆
所有的对象实例和数组在此分配内存,但也不是绝对的。
堆是垃圾收集管理的主要区域,为了更好的实现内存分配和垃圾收集,堆可以被细分为更小的部分,具体算法由所使用的JVM实现。
异常情况:
OutOfMemoryError:无法再为实例请求到内存时抛出
1.2.2 方法区
用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译的代码等数据。垃圾收集在此区域出现比较少,但并不意味着数据进入了此区域就不会被回收了。
原理图:
intern():
package ryo; publicclass Test { publicstaticvoid main(String[] args) { //通过这种方式创建的字符串将被直接放入运行时常量池 Strings1 = "abc"; //当运行时常量池中已经存在了一个"abc"之后,s2的引用将直接指向已存在的"abc" Strings2 = "abc"; //通过new关键字创建的String对象将被存储在堆中 Strings3 = new String("abc"); Strings4 = "a"; Strings5 = "bc"; //这里的两个字符串相加会被编译为: //String s6 = newStringBuilder(s4).append(s5).toString();实际上这里的s6已经是一个new出来的对象了 Strings6 = s4+s5; //同为常量池中的"abc"引用故相等 System.out.println("s1 == s2 "+(s1 == s2)); //s3不在常量池中 s1则是常量池的引用故不等 System.out.println("s1 == s3 "+(s1 == s3)); //s3不在常量池中 s2则是常量池的引用故不等 System.out.println("s2 == s3 "+(s2 == s3)); //s6是一个对象不在常量池中故不等 System.out.println("s2 == s6 "+(s2 == s6)); //"a"也是一个运行时常量和s4一样指向运行时常量池中"a"的引用 System.out.println("a == s4 "+("a" == s4)); //s3 和 s6都不在常量池,但堆中每个实例是独立的存储地址,故不等 System.out.println("s3 == s6 "+(s3 == s6)); //s6.intern()将把"abc"的引用放入常量池,返回引用,若常量池中已有"abc"则返回该"abc"的引用 //这里把常量池中的"abc"引用赋值给s6,故s6和s2都是常量池中的"abc"引用,相等 s6 = s6.intern(); System.out.println("s2 == s6 "+(s2 == s6)); } }
运行结果:
s1 == s2 true
s1 == s3 false
s2 == s3 false
s2 == s6 false
a == s4 true
s3 == s6 false
s2 == s6 true
异常情况:
OutOfMemoryError:方法区无法满足内存分配的需求时抛出。
1.2.3直接内存
直接内存并不是JVM运行时数据区的一部分,常使用这块内存的地方的一个例子就是NIO。
NIO直接对独立于堆之外的直接内存进行读写,在一定量的高并发下相比于BIO有显著的性能提升。
直接内存的使用不受java堆的限制,但仍然受到本机的物理内存和处理器最大寻址的限制。
占用过高内存可能会导致数据区无法申请到需要的内存,从而引发OutOfMemoryError异常。