一.运行时数据区域
Java虚拟机将其管理的内存划分为不同的数据区域,他们各自有不同的用途,创建及销毁时间。
总体情况如下:
1.1程序计数器
每当我们创建一个线程时,便会相对应的使其拥有一个程序计数器。程序计数器的主要功能是作为当前线程的行号指示器。字节码指示器会因为程序计数器的值的不同而执行不同的代码。各个线程的程序计数器互不影响,独立储存。
在实际运行中,如果当前线程执行的是java方法,程序计数器记录的是正在执行的虚拟机字节码指令的地址,如果当前线程执行的是本地Native方法,程序计数器的值为空。
此区域是唯一没有规定OutOfMemoryError异常的区域!
1.2虚拟机栈
此区域会抛出两种异常:
1.StackOverflowError : 当对栈的访问深度超出栈本身的深度时抛出,即栈已空还在get。
2.OutOfMemoryError :虚拟机栈的大小是支持扩展的,当虚拟机栈需要扩展而无法分配内存时抛出此异常。
1.3本地方法栈
1.StackOverflowError : 当对栈的访问深度超出栈本身的深度时抛出,即栈已空还在get。
2.OutOfMemoryError :栈无法分配内存时抛出此异常。
1.4堆
堆是所有线程共享的用于存放所有对象实例(包含数组)的一片很大的内存区域。此区域在逻辑上处于连续的内存空间中,而可以不处于连续的物理空间中。
堆可以稍微细分为 1新生代(Eden空间,From Survivor空间,To Survivor空间) 2老年代。其中,可能还会存在一些线程私有的分配缓冲区(TLAB-----Thread Local Allocation Buffer),分配缓冲去在下文中会仔细介绍。
抛出OutOfMemorryError异常 :当堆中没有内存完成实例对象的分配,与此同时又没有内存供其扩展时抛出异常。
1.5方法区
方法区时所有线程共享的用于储存已被虚拟机加载的类信息,常量,静态变量等数据。方法区的一部分叫做运行时常量池,数据进入运行时常量池有两种途径:1.class文件中含有一部分信息叫常量池(存放编译器生成的字面量和符号引用),在类被加载后会放入运行时常量池。2非编译器也可以进入运行时常量池,比如String的intern()方法。在HotSpot虚拟机中,方法去更多的被叫做永久带,这是为了同堆一起进行垃圾收集。但是,这造成了很多的内存溢出问题。
二.以HotSpot虚拟机为例探索内存区域
2.1普通对象的创建
普通对象的创建过程分为5个步骤:
2.1.1.进行类加载检查
2.1.2分配内存
内存分配有两种方式
分配内存时会出现并发问题,A线程想在某一区域分配内存还未完成时,B线程也想使用此区域的内存。解决办法有两种。
2.1.3分配的内存空间初始化为0值
2.1.4进行必要设置
2.1.5按程序员意愿进行初始化
2.2内存布局
2.2.1对象头
2.2.2实例数据
2.2.3对齐填充
2.3访问定位
2.3.1句柄访问
2.3.2直接指针访问
三.内存溢出实例
3.1堆溢出
/**
* VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
* @author zzm
*/
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
}
3.2虚拟机栈溢出/本地方法栈溢出
/**
* VM Args:-Xss128k
* @author zzm
*/
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
不停创建线程并为其分配各自的虚拟机栈会抛出OutOfMemorryError异常,
别运行这段代码,会死机 !!!!
/**
* VM Args:-Xss2M (这时候不妨设大些)
* @author zzm
*/
public class JavaVMStackOOM {
private void dontStop() {
while (true) {
}
}
public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) throws Throwable {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
3.3方法区(包含运行时常量池)
/**
* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
* @author zzm
*/
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
// 使用List保持着常量池引用,避免Full GC回收常量池行为
List<String> list = new ArrayList<String>();
// 10MB的PermSize在integer范围内足够产生OOM了
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
也可以生成大量的动态类区填满方法区使其抛出异常。在当下的主流框架如Spring,Hibernate中会使用CGLib这类字节码技术对类进行增强,增强的类越多,就需要更大的方法区保证动态类生成的Class可以载入内存。
/**
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
* @author zzm
*/
public class JavaMethodAreaOOM {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
static class OOMObject {
}
}
最后,本片文章是我阅读《深入理解Java虚拟机-----JVM高级特性于最佳实践》总结而来,溢出异常的代码也是来自于书中的源码。初学java,出错实乃再平常不过之事,万望各位提出我的错误,在此谢过诸位了。