Java 内存模型(一)

打算花比较长的篇幅来描述下自己理解的JVM,尽量描述的清晰易懂一些,从简单慢慢到慢慢深入,一方面自己也复习一下,一方面也供大家参考,少走些弯路。鉴于本人水平有限,如有错误的地方,欢迎指出,感谢。

一段废话引出,大家都知道java有JVM,那JVM有个非常方便的自动内存管理机制,致使java开发人员不再需要为每个new操作去写配对的delete/free代码,而且不容易出现内存泄漏和内存溢出的问题(不容易并非不可能),不过真因为此一旦出现了内存泄漏或者溢出的问题,如果不了解JVM怎么样使用内存的,那么排查起来也会比较困难。

我们初始学习java时候很多人关注就是java的堆栈,这种分法比较粗糙,但是易与理解,实际上远远不止于此,但是今天我们就先按粗浅的方式去解释下,后面我们在逐步深入。按我的习惯,我们先看个例子。

1 public class BaseTest {
2     public static void main(String[] args) throws Exception{
3         ArrayList list=new ArrayList();
4         while(true){
5             list.add(new FinalBean());
6         }
7     }
8 }

上面这段代码最终的结果:

1 java.lang.OutOfMemoryError: GC overhead limit exceeded
2 Dumping heap to java_pid1288.hprof ...
3 Heap dump file created [34835053 bytes in 0.247 secs]
4 Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
5     at java.util.Arrays.copyOf(Arrays.java:3332)
6     at java.lang.String.concat(String.java:2032)
7     at com.cz.smart.center.FinalBean.<init>(FinalBean.java:13)
8     at com.cz.smart.center.BaseTest.main(BaseTest.java:14)

为什么会出现这种结果呢? 

我们通过创建实例对象 new FinalBean(),会开辟堆内存空间,往list列表里面加,导致实例对象没法回收,一直到无法申请到新的内存,导致爆内存溢出,那有没有考虑过,为什么实例对象的内存无法进行回收呢?

再看一个例子

public class BaseTest {
    int i = 0;
    public static void main(String[] args) throws Exception{
        BaseTest b = new BaseTest();
        b.addStackLength();
    }

    public void addStackLength(){
        i++;
System.out.println(i); addStackLength(); } }

上面这段代码最终的结果:

938
939
Exception in thread "main" java.lang.StackOverflowError
	at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)
	at sun.nio.cs.UTF_8.access$200(UTF_8.java:57)
	at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:636)
	at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)
	at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579)
	at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271)
	at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
	at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
	at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
	at java.io.PrintStream.write(PrintStream.java:526)
	at java.io.PrintStream.print(PrintStream.java:597)
	at java.io.PrintStream.println(PrintStream.java:736)

  

为什么这段代码会报这个错呢?StackOverflowError 栈溢出错误。

 递归调用,死循环,超过栈的深度,导致抛出异常,那这段代码再内存从怎么跑的呢?我画图解释下:

我们知道栈是线程独享的,每个线程都会有自己独立的栈,而每调用一个方法会产生一个栈针进行入栈。

1.在代码b.addStackLength 时候,会入栈addStackLength方法的栈针,栈针包括局部变量表,操作数栈,动态链接,方法出口等信息(这些后面会详细讲解)。

2.进入方法addStackLength的时候,先执行i++,然后输出i++信息,发现该方法又调用了addStackLength的方法。

3.于是又会入栈一个addStackLength方法的栈针,以此循环,由于是死循环,入栈的栈针超过了栈的深度,抛出StackOverflowError 错误。

注:如果是递归调用而没有死循环,那情况就是在最底层的方法执行完之后,最底层方法就会出栈,调转到上一个调用该方法的下一行,以此类推持续出栈,这就是栈的先入后出。

 后续一步步拆分堆和栈内容信息,未完待续。。。

猜你喜欢

转载自www.cnblogs.com/HA-Tinker/p/10687097.html