运行时数据区域
java 虚拟机在执行java 程序的时候会把它管理的内存划分为若干个不同的数据区域,如下:
程序计数器
java虚拟机栈
java虚拟机栈和程序计数器一样,也是线程私有的,它的生命周期与线程相同。 虚拟机栈描述的是java 的内存模型:每个方法在执行的同时,都会创建一个叫栈帧用户存储局部变量表,动态链接,方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。
局部变量表存放了编译期间可知的个各种基本数据类型(boolean,byte,short,character,int,float,long,dubbo),局部变量表所需的内存空间在编译期间完成分配,当一个方法进入时候,这个方法在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
java虚拟机规范中:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError;如果虚拟机栈可以动态扩展,但在在扩展的时候无法申请到足够的内存空间,则会抛出OutOfMemoryError异常
本地方法栈
本地方法栈和虚拟机栈发挥的作用是非常相似的,他们之间的区别不过是虚拟机栈执行的是java方法服务,本地方法栈执行的是虚拟机栈中使用到的native服务。
与虚拟机栈一样,本地方法栈也会抛出StackOverFlowError和OutOfMemoryError异常。
java堆
java堆是java虚拟机中所管理内存中最大的一块,java堆是被所有现存共享的,在虚拟机启动时候创建,此内存唯一的目的就是存放对象实例,几乎所有的对象实例都在这里分配。
从内存分配的角度来看,线程共享的java堆可能划分出多个线程私有的分配缓冲区,不过无论如何划分,都与存放内容无关,无论哪个区域,存储的都依然是对象实例,进一步划分的目的是为了更好的回收内存。
java堆可以是物理上不连续内存空间。目前主流的虚拟机都是按照可扩展内存实现的,如果在堆中没有内存完成实例的分配,并且堆也无法扩展时,将会抛出OutOfMemoryError异常。
方法区
方法去和java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编辑后的代码等数据。虽然java虚拟机规范中把该方法描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是要与java堆区分开来。
垃圾收集在这个区域比较少出现,但是并非数据进入了这个方法区就如永久代的名字一样“永久存在”,这个区域的内存回收主要目标是针对常量池的回收以及类型的卸载,这个区域的回收成绩比较令人满意,尤其是对类型的卸载,条件相当苛刻。
运行时常量池
运行时常量池是方法去的一部分,用于存放编译期间生产的各种字面量和符号引用。这部分在类加载后进入方法区的运行时常量池存放。
java语言并不要求常量一定是编译期间才能产生,也就是并非预置入Class文件中的常量池内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用的比较多的便是String的intern()方法。
当常量池无法再申请到内存的时候便会抛出OutOfMemoryError异常。
直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,但是这一部分内存也被频繁的使用。而且也可能导致OurOfMemoryError异常。
java1.4中新加入的NIO类,引入了以一种基于通道(Channel)与缓冲区的IO方式,通过Native函数库可以直接分配堆外的内存。
本机的内存分配不会受到java堆大小的分配,但是,既然是内存,肯定还是会受到本机总内存大小以及处理器寻址空间的限制。服务器管理员,在配置虚拟机参数时,会根据实际内存设置 -Xmx等参数,但是经常忽略直接内存,使得各个内存区域总和大于物理内存,从而导致动态扩展时候出现OutOfMemoryError异常。
HotSpot虚拟机对象探秘
对象创建
对象内存布局
对象的访问定位
两种对象访问方式各有优势,使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时候对象移动是非常普遍的行为)时候只会修改句柄中的实例数据地址,而reference本身不需要修改;直接使用指针访问的最大好处就是访问的速度更快,,节省了一次指针的定位时间开销。
Sun HotSpot采用的是第二种对象访问方式。
实战OutOfMemoryError异常
java堆溢出
将java堆大小设置为20MB,通过参数--XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时候Dump出当前的内存堆转储快照以便事后分析
import java.util.ArrayList;
import java.util.List;
/**
* Args: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
*/
public class JavaTest {
public static void main(String[] args) {
List<JavaTest> list=new ArrayList<JavaTest>();
while(true){
list.add(new JavaTest());
}
}
}
运行之后:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid2704.hprof ...
Heap dump file created [13107553 bytes in 0.073 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at JavaTest.main(JavaTest.java:12)
虚拟机栈和本地方法栈溢出
使用-Xss参数来减少栈内存容量。结果:抛出StackOverFlowError异常,异常出现时输出堆栈的深度相应缩小。
定义大量的本地变量,增大此方法帧中本地变量表的长度。结果:抛出StackOverFlowError异常,输出堆栈的深度相应缩小。
/** * 虚拟机栈和本地方法栈OOM测试
* Args: -Xss128k
*/
public class JavaTest {
private int stackLength = 1;
public static void main(String[] args) {
JavaTest jt=new JavaTest();
try {
jt.stackLeak();
} catch (Exception e) {
e.printStackTrace();
}
}
public void stackLeak(){
stackLength++;
stackLeak();
}
}
Exception in thread "main" java.lang.StackOverflowError
at JavaTest.stackLeak(JavaTest.java:18)
at JavaTest.stackLeak(JavaTest.java:19)
at JavaTest.stackLeak(JavaTest.java:19)创建线程导致内存溢出异常
/**
* 创建线程导致内存异常
* Args: -Xss1m -Xms2m -Xmx2m
*/
public class JavaTest {
public static void main(String[] args) {
JavaTest jt=new JavaTest();
try {
jt.stackLeakThread();
} catch (Exception e) {
e.printStackTrace();
}
}
public void dontStop(){
while(true){
}
}
public void stackLeakThread(){
while(true){
Thread thread=new Thread(new Runnable() {
public void run() {
dontStop();
}
});
thread.start();
}
}
}
运行结果
Exception in thread "main"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
方法区和运行时常量池溢出
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* -XX:PermSize=2M -XX:MaxPermSize=2M
*/
public class JavaTest {
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 arg1, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invoke(obj, args);
}
});
enhancer.create();
}
}
static class OOMObject {
}
}
本机直接内存溢出
直接内存可以通过 -XXMaxDirectMemorySize制定,如果不指定,默认是堆最大值,下面通过反射获取Unsafe实例,执行内存分配。
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class JavaTest {
private static final int _1MB=1024 * 1024;
public static void main(String[] args) throws Exception{
Field unsafeField=Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe=(Unsafe)unsafeField.get(null);
while(true){
unsafe.allocateMemory(_1MB);
}
}
}
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at JavaTest.main(JavaTest.java:19)
本文深入介绍了Java虚拟机(JVM)的内存管理机制,包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区等关键概念。同时,文章详细分析了HotSpot虚拟机的对象创建、内存布局和访问定位过程,并提供了多种导致OutOfMemoryError异常的实战案例。

被折叠的 条评论
为什么被折叠?



