对象的创建、访问定位与异常演示

来源:互联网 发布:想学软件编程 编辑:程序博客网 时间:2024/05/21 00:52

创建JAVA对象

在虚拟机中,对象的创建过程如下:当虚拟机遇到一条new命令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符合引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。在类加载检查通过后,接下来虚拟机会为新生对象分配内存,内存分配完成还要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。

在虚拟机中访问Java对象

建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式也取决于虚拟机实现而定的。目前主流的访问方式有使用句柄和直接操作指针两种。

1.如果使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。如下图:


2.如果使用直接指针访问,那么Java堆对象的布局就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象的地址,如下图:


       这两种对象访问方式各有优势,使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。

       使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。

演示:OutOfMemoryError异常

1.Java堆溢出

Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。

package com.jvm;import java.util.ArrayList;import java.util.List;/** * VM Args: -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError * @author lpf * */public class HeapOOM {static class OOMObject{}public static void main(String[] args){List<OOMObject> list = new ArrayList<OOMObject>();while(true){list.add(new OOMObject());}}}
在代码运行之前,设置Java堆的大小为20MB,不可扩展(将堆的最小值-Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展),通过参数-XX:HeapDummpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析。

运行结果:

java.lang.OutOfMemoryError: Java heap spaceDumping heap to java_pid9880.hprof ...Heap dump file created [27971702 bytes in 0.134 secs]Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat 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 com.jvm.HeapOOM.main(HeapOOM.java:15)
2.虚拟机栈和本地方法栈溢出

由于在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此,对于HotSpot来说,虽然-Xoss参数(设置本地方法栈大小)存在,但是实际上是无效的,栈容量只由-Xss参数设定。关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了两种异常:

~~如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。

~~如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

实验1:使用单线程测试导致StackOverflowError异常

package com.jvm;/* * VM Args: -Xss128k */public class JavaVMStackSOF {private int stackLength = 1;public void stackLeak(){stackLength++;stackLeak();}public static void main(String[] args){JavaVMStackSOF oom = new JavaVMStackSOF();try{oom.stackLeak();}catch(Throwable e){System.out.println("stack length:"+oom.stackLength);throw e;}}}
运行结果:

stack length:994Exception in thread "main" java.lang.StackOverflowErrorat com.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)at com.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)at com.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)at com.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)at com.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)at com.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)at com.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)at com.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)at com.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)
实验2:使用多线程测试导致内存溢出异常

package com.jvm;/* * VM Args: -Xss2M */public class JavaVMStackOOM {private void dontStop(){while(true){}}public void stackLeakByThread(){while(true){Thread thread = new Thread(new Runnable(){@Overridepublic void run(){dontStop();}});thread.start();}}public static void main(String[] args) throws Throwable{JavaVMStackOOM oom = new JavaVMStackOOM();oom.stackLeakByThread();}}
运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

        在单个线程下,当内存无法分配的时候,虚拟机抛出StackOverflowError异常;多线程时,虚拟机会抛出OutOfMemoryError异常。因为操作系统分配给每个进程的内存是有限制的,譬如32位的Windows限制2GB。虚拟机提供了参数来控制Java堆和方法区的这两部分内存的最大值。剩余的内存为2GB(操作系统限制)减去Xmx(最大堆容量),再减去MaxPermSize(最大方法区容量),程序计数器消耗内存很小,可以忽略掉。如果虚拟机进程本身耗费的内存不计算在内,剩下的内存就由虚拟机栈和本地方法栈“瓜分”了。每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽。



原创粉丝点击