JVM · Java虚拟机原理 · JVM上语言·框架· 生态系统

JVM 内存区域模型

2018-12-17  本文已影响17人  Aiibai
1.运行时内存区域的划分

java 虚拟机在执行 java 程序的过程中,会将它管理的内存区域划分为若干个不同的数据区域,这些区域有各自不同的用途,以及创建和销毁时间。

image.png

HotSpot 虚拟机并不区分虚拟机栈和本地方法栈,所以下面统称这两种内存区域叫 Java 栈

2.每个内存区域的作用以及服务的对象
3.可能抛出的异常
4.控制的参数

-方法区
-XX:PermSize 最小方法区大小
-XX:MaxPermSize 最大方法区大小

5.模拟异常
/**
 * -Xms10m
 * -Xmx10m
 * */
public class HeapOOM {
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while (true) {
            list.add(new OOMObject());
        }
    }

    static class OOMObject {

    }
}

运行结果

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 jvm.exception.HeapOOM.main(HeapOOM.java:13)

解决这种溢出的思路是:首先确定是内存泄露还是内存溢出导致的异常。可以通过堆转储快照进行分析,如果是内存泄露导致的,就需要检查泄露的对象为什么不能被垃圾回收,也即寻找泄露对象与 GC Roots 的可达路径。如果是内存溢出查看

/**
 * -Xss128k
 */
public class StackOOM {
    public static void main(String[] args) {
        while (true) {
            new Thread(() -> {
                while (true){
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //...
                }
            }).start();
        }
    }
}

执行结果:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:717)
    at jvm.exception.StackOverflow.main(StackOverflow.java:23)

StackOverflowError

/**
 * -Xss128k
 */
public class StackOverflow {
    public static void main(String[] args) {
        test();
    }
    public static void test(){
        test();
    }
}

执行结果:

Exception in thread "main" java.lang.StackOverflowError
    at jvm.exception.StackOverflow.test(StackOverflow.java:11)
    at jvm.exception.StackOverflow.test(StackOverflow.java:11)
    at jvm.exception.StackOverflow.test(StackOverflow.java:11)
    at jvm.exception.StackOverflow.test(StackOverflow.java:11)
....
/**
 * -XX:PermSize=10M  -XX:MaxPermSize=10M
 */
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        List<String> values = new ArrayList<>();

        int i=0;
        while (true){
            values.add(String.valueOf(i++).intern());
        }
    }
}

上面代码中的 intern 方法的作用是:判断是否存在需要的常量,如果存在就返回,如果不存在,则将当前值加入到常量池中。

执行结果:
发现设置了 -XX:PermSize和-XX:MaxPermSize 并不会导致方法区溢出。原来在 java8 中移除了这两个参数,原来的永久代(PermGen)概念也没有了,取而代之的是元空间(Metaspace)。不过它们也有一些区别:原先永生代中类的元信息会被放入本地内存(元数据区),将类的静态变量和内部字符串放入到java堆中。

新的元空间设置参数:

-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对改值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集。
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集。

总结起来,java8中对方法区的实现做了如下的变化:

那么,很明显上面的代码中的字符串常量是被放到了堆中了,增加参数-Xms10M和-Xmx10M,验证效果如下:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
Disconnected from the target VM, address: '127.0.0.1:53648', transport: 'socket'
    at java.nio.CharBuffer.wrap(CharBuffer.java:373)
    at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:265)
    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.newLine(PrintStream.java:545)
    at java.io.PrintStream.println(PrintStream.java:807)
    at jvm.exception.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:17)
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize1=10M; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10M; support was removed in 8.0

在上面的结果中我们不仅看到了 OutOfMemoryError ,还有一句 GC overhead limit exceeded ,这句是什么意思的?
GC overhead limt exceed 检查是 Hotspot VM 1.6 定义的一个策略,通过统计 GC 时间来预测是否要 OOM 了,提前抛出异常,防止 OOM 发生。Sun 官方对此的定义是:并行/并发回收器在GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存。用来避免内存过小造成应用不能正常工作。“

/**
 * //-XX:PermSize=128k  -XX:MaxPermSize=128k
 * -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
 */
public class JavaMethodAreaOOM {
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1));
            enhancer.create();
        }
    }

    static class OOMObject {

    }
}

执行结果:

Error occurred during initialization of VM
OutOfMemoryError: Metaspace
6.对象访问
Object obj = new Object();

上面这行代码涉及到的内存区域有:方法区、堆、栈。
Object obj 这部分的语义将反映到 Java 栈的本地变量表 中。
new Object() 这部分的语义将反映到 Java 堆 中。
另外,还需要类型数据(对象类型、父类、实现的接口、方法等),这部分数据储存在 方法区

obj 中的引用代表的含义在不同的虚拟机中有不同的实现,主流的有两种:句柄和指针。

这两种方式都有各自的优缺点
在对象被移动式,只要修改句柄中的对象对象地址即可,而 obj 中的句柄地址不用改变。但是直接指针方法的速度更快,它节省的一次指针定位的时间开销。

参考:
https://blog.csdn.net/laomo_bible/article/details/83067810
https://www.cnblogs.com/hucn/p/3572384.html

上一篇 下一篇

猜你喜欢

热点阅读