Java虚拟机所管理的内存包括以下几个运行时数据区域(运行时数据区域):
注:概念模型,它代表了所有虚拟机的统一外观,但各款具体的Java虚拟机并不一定要完全照着概念模型的定义进行设计,可能会通过一些更高效率的等价方式去实现它。
1.程序计数器:可以看作是当前线程所执行的字节码的行号指示器,是一块较小的内存空间。(线程私有的内存空间)
在Java虚拟机的概念模型里,字节码解释器的工作就是通过改变该计数器的值来获取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个指示器完成。
如果线程执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址
如果正在执行的是本地方法(Java代码),这个计数器的值应该为空(undefined)
这个区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。(原因应该是这个区域的设计保证了不会触发任何OutOfMemoryError情况:线程数量有限制、线程私有、大小固定、存储内容简单)
2.Java虚拟机栈:描述的是Java执行的线程内存模型,也是线程私有的,生命周期与线程相同。
线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每个方法被调用直至执行完毕的过程,就对应着一个栈桢在虚拟机栈中从入栈到出栈的过程。
局部变量表:它存放了编译期间可知的各种Java虚拟机基本数据类型、对象引用和returnAddress
基本数据类型:boolean、byte、char、short、int等等;
对象引用:reference类型,但并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置。
returnAddress:它存储着被调用方法执行完毕之后的该返回的位置,当被调用方法执行完毕之后,回到调用者继续执行接下来的代码,returnAddress储存的就是继续执行处的地址指针。
该内存区域规定的异常:
-
- StackOverflowError:线程请求的栈深度大于虚拟机允许的深度;
- OutOfMemoryError:Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存。
(HotSpot没有OOM异常,因为其没有动态扩展)
注1 句柄:JVM会通过句柄池(Handle Pool)来管理对象引用,句柄是一个中间层,它包含了对象实例的数据(如对象的字段值)和对象类型的地址(如对象的类信息、方法表等),这种设计中,对象引用实际上是指向句柄的指针,而句柄又包含了两个指针:
·一个是指向对象数据的指针(实际为存储对象字段值的地方)
· 一个是指向对象类型数据的指针
3.本地方法栈:与Java虚拟机栈的区别只有,虚拟机栈为虚拟机执行Java方法(也即字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务
《Java虚拟机规范》对本地方法栈中方法使用的语言、使用方式与数据结构没有强制规定。这里可以根据需要自由实现,如HotSpot直接将Java虚拟机栈和本地方法栈合二为一。
该内存区域规定的异常(与Java虚拟机栈一致):
-
- StackOverflowError:线程请求的栈深度大于虚拟机允许的深度;
- OutOfMemoryError:Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存。
4.Java堆:原则上物理上可以不连续,逻辑上连续即可。但许多虚拟机为了简单,会直接请求连续的物理空间。
对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。这里是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里”几乎“所有的对象实例都在这里分配。(随着技术发展也不那么绝对)
Java堆是垃圾收集器管理的内存区域,所以也成为”GC堆“。另外,Java虚拟机并未规定过分代模型,分代模型只是一种设计风格。当然,从内存分配角度来看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer , TLAB),可以提示对象分配时效率。
5.方法区:Method Area与Java堆一样,是各个线程共享的内存区域,用于储存已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
《Java虚拟机规范中》将其描述为堆的一个逻辑部分,但他有个别名:Non-Heap,就是为了跟堆区分开。
”永久代“概念:JDK8之后完全废弃此概念,转为使用本地内存实现方法区。
该内存区域规定的异常:
-
- OutOfMemoryError:方法区无法满足新的内存分配需求
6.运行时常量池:Runtime Constant Pool是方法区的一部分。
Class文件中除了有类的版本、字段、方法、接口等描述信息以外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
RCP规定的异常:
-
- OutOfMemoryError:方法区无法满足新的内存分配需求
7.直接内存:Direct Memory并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。但这里会被频繁使用并且也可能导致OutOfMemoryError。
在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用,Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样避免了在Java堆中和Native中来回复制数据,可以在一些场景中显著提升性能。
直接内存区域规定的异常:
-
- OutOfMemoryError:本机直接内存分配不会受到Java堆大小的限制,但是当忽略这一部分时就可能会导致各区域内存和大于物理内存限制。
实践:maven工程
需要在File--setting--构建、执行、部署--maven--正在导入:导入程序的虚拟机选项设置VM参数
public class JavaHeapOOM {
//Java堆内存益处异常
static class OOMObject {}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true) {
list.add(new OOMObject());
}
}
}
/*
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at org.example.TwoOOM.JavaHeapOOM.main(JavaHeapOOM.java:19)
*/
/*
* 栈溢出(使用-Xss参数减少栈内存容量)
* VM Args: -Xss128k
* @author: pinko
*/
public class StackOverflowError1 {
private int stackLength=1;
public void stackLeak(){
stackLength++;
stackLeak();
}
public static void main(String[] args) {
StackOverflowError1 oom=new StackOverflowError1();
try {
oom.stackLeak();
}catch (Throwable e){
System.out.println("栈深度:"+oom.stackLength);
throw e;
}
}
}
/*
栈深度:18992
Exception in thread "main" java.lang.StackOverflowError
at org.example.TwoOOM.StackOverflowError.stackLeak(StackOverflowError.java:7)
at org.example.TwoOOM.StackOverflowError.stackLeak(StackOverflowError.java:7)
at org.example.TwoOOM.StackOverflowError.stackLeak(StackOverflowError.java:7)
.......
*/
/*
* 栈溢出(丑陋的定义大量本地变量,以此增大方法帧中本地变量表的长度)
* @author: pinko
*/
public class StackOverflowError2 {
private static int stackLength=0;
public static void stackLeak(){
long unused1,unused2,unused3,unused4,unused5,unused6,unused7,unused8,
unused9,unused10,unused11,unused12,unused13,unused14,unused15,
unused16,unused17,unused18,unused19,unused20,unused21,unused22,
unused23,unused24,unused25,unused26,unused27,unused28,unused29,
unused30,unused31,unused32,unused33,unused34,unused35,unused36,
unused37,unused38,unused39,unused40,unused41,unused42,unused43,
unused44,unused45,unused46,unused47,unused48,unused49,unused50,
unused51,unused52,unused53,unused54,unused55,unused56,unused57,
unused58,unused59,unused60,unused61,unused62,unused63,unused64,
unused65,unused66,unused67,unused68,unused69,unused70,unused71,
unused72,unused73, unused74,unused75,unused76,unused77,unused78,
unused79,unused80,unused81,unused82,unused83,unused84,unused85,
unused86,unused87,unused88,unused89,unused90,unused91,unused92,
unused93,unused94,unused95,unused96,unused97,unused98,unused99,unused100;
stackLength++;
stackLeak();
unused1=unused2=unused3=unused4=unused5=unused6=unused7=unused8=unused9=unused10=unused11=unused12=unused13
=unused14=unused15= unused16=unused17=unused18=unused19=unused20=unused21=unused22=unused23=unused24
=unused25=unused26=unused27=unused28=unused29=unused30=unused31=unused32=unused33=unused34=unused35
=unused36=unused37=unused38=unused39=unused40=unused41=unused42=unused43=unused44=unused45=unused46
=unused47=unused48=unused49=unused50=unused51=unused52=unused53=unused54=unused55=unused56=unused57
=unused58=unused59=unused60=unused61=unused62=unused63=unused64=unused65=unused66=unused67=unused68
=unused69=unused70=unused71=unused72=unused73= unused74=unused75=unused76=unused77=unused78=unused79
=unused80=unused81=unused82=unused83=unused84=unused85=unused86=unused87=unused88=unused89=unused90
=unused91=unused92=unused93=unused94=unused95=unused96=unused97=unused98=unused99=unused100=1;
}
public static void main(String[] args) {
try{
stackLeak();
}catch (Error e){
System.out.println("栈深度:"+stackLength);
throw e;
}
}
}
/*
栈深度:4953
Exception in thread "main" java.lang.StackOverflowError
at org.example.TwoOOM.StackOverflowError2.stackLeak(StackOverflowError2.java:24)
at org.example.TwoOOM.StackOverflowError2.stackLeak(StackOverflowError2.java:24)
at org.example.TwoOOM.StackOverflowError2.stackLeak(StackOverflowError2.java:24)
*/
/*
* 运行时常量池导致的内存溢出异常
* VM Args: -XX:PermSize=6M -XX:MaxPermSize=6M
* @author: pinko
*/
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
//使用Set保持常量池引用,避免Full GC回收常量池行为
Set<String> set =new HashSet<String>();
//short范围内足以让6MB的PermSize产生OOM
short i=0;
while(true){
set.add(String.valueOf(i++).intern());
}
}
}
public class StringPointInternReturnReference {
public static void main(String[] args) {
String str1=new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern()==str1);
String str2=new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern()==str2);
}
}
/*
* JDK6 :false
* false
* JDK7及以上 : true
* false
*
*
*/
//借助CGLib开源项目:http://cglib.sourceforge.net
public class JavaMethodAreaOOM {
public static void main(String[] args) {
while(true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(JavaMethodAreaOOM.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj,args);
}
});
enhancer.create();
}
}
static class OOMObject{}
}
/*
* 直接内存溢出(越过DirectByteBuffer的限制)
* VM Options: -Xms20m -XX:MaxDirectMemorySize=10m
* @author: pinko
*/
public class DirectMemoryOOM {
private static final int _1M = 1024 * 1024;
public static void main(String[] args) throws IllegalAccessException {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe=(Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1M);
}
}
}
/*
Exception in thread "main" java.lang.OutOfMemoryError: Unable to allocate 1048576 bytes
at java.base/jdk.internal.misc.Unsafe.allocateMemory(Unsafe.java:632)
at jdk.unsupported/sun.misc.Unsafe.allocateMemory(Unsafe.java:462)
at org.example.TwoOOM.DirectMemoryOOM.main(DirectMemoryOOM.java:19)
*/