JVM 虚拟机 内存区域理解 以及关键字 new简介

JVM 虚拟机内存分布

不啰嗦 , 直接上图 (自己画的 , 有不准的别太在意细节!)

在这里插入图片描述

各区域作用及内存溢出

java堆

gc的主要作用区域,从分带收集算法,大家都知道java堆分新生代和老年代,默认比例为 1:2,即新生代为堆的1/3大小;
新生代又分为 : Eden(伊甸园)-FromSurvivor-ToSurvivor其默认比例是8:1:1;
总体新生代&老年代 的划分比例是 Eden : FromSurvivor : ToSurvivor : old = 8:1:1:20
当无限制的new对象,并且所有对象都是无法回收的,会造成堆内存溢出的情况:

/**
 * 为了更快的抛出异常,建议配置虚拟机参数,分别设置堆最小和最大值,并打出抛出错误时的内存快照:
 * -Xms20m(堆最小值)  -Xmx20m(堆最大值) -XX:HeapDumpOnOutOfMemoryError
 */
List<Object> ol = new ArrayList<Object>();
while (true) {
    ol.add(new Object());
}
方法区

方法区是属于堆类内存的一个逻辑分区,但通常都叫它非堆(Non-Heap);
有些时候也称之为永久代,但其实方法去并不是真正的永久代,只是有些虚拟机使用了永久代的内存管理方式实现了方法区而已;
方法区用于存放 : 被虚拟机加载的类信息、常量(final)、静态变量(static)、即时编译器编译的代码数据,以及运行时常量池数据;
其中运行时常量池包含 : 字面量和符号引用;
如果无限的创建不同值的String对象,就可能造成方法区内存溢出,原因是运行时常量池溢出 :

/**
 * -XX:PermSize=10M -XX:MaxPermSize=10M
 */
int i = 0;
while (true) {
    list.add(String.valueOf(i++).intern());
}

通过代理模式无限的生成某个类的代理类,代理类信息及代理类的方法等也会造成方法区内存溢出 :

public static void main(String[] args) {
        while (true) {
            Enhancer e = new Enhancer();
            e.setSuperclass(SupperClass.class);
            e.setUseCache(false);
            e.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method m, Object[] args, MethodProxy p) throws Throwable {
                    System.err.println("CGlib : DO");
                    try {
                        return p.invokeSuper(obj, args);
                    } finally {
                        System.err.println("CGlib : finally");
                    }
                }
            });
            ((SupperClass) e.create()).superDo();
        }
    }

    class SupperClass {
        public void superDo() {
            System.err.println("SUPER : DO");
        }
    }
虚拟机栈

虚拟机栈是线程私有的内存空间,通过 -Xss128K 虚拟机参数来设定每个线程的栈大小;
在线程每执行一个方法的时候,都会开辟一个栈帧,用于存放方法局部变量表、操作帧数、动态链接、方法出口等信息;
当栈帧深度超出虚拟机允许的深度时,就会抛出StackOverflowError;
当栈无法动态扩展,无法申请足够内存时,就会抛出OutOfMemoryError;(现在大部分虚拟机都是可以动态扩展的,很难造出这个异常)
最常见的栈深度异常,无限递归:

static int i = 0;
    public static void main(String[] args) {
        try {
            superDo();
        } catch (Throwable e) {
            System.err.println(e.getClass().getName() + " : " + i);
           
        }
    }
    public static void superDo(){
        i++;
        superDo();
    }
// 执行结果 : java.lang.StackOverflowError : 38321 数字是随机的
本地方法栈

本地方法栈和虚拟机栈类似,只不过是执行本地方法时为本地方法开辟的栈空间;
也是通过Xss来设置栈大小
创建线程就是一个本地方法,我们可以无限创建线程来制造本地方法栈溢出:

 static int i = 0;
    private static void unstop() {
        while (true) {
        }
    }
    public static void createThread() {
        while (true) {
            i++;
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    unstop();
                }
            });
            t.start();
        }
    }
    public static void main(String[] args) {
        try {
            createThread();
        } catch (Throwable e) {
            System.err.println(i);
            throw e;
        }
    }
// 由于本人电脑比较差,跑这段代码直接宕机了,执行结果没看到
// 在《深入理解Java虚拟机》一书中这样写:
// Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

关键字 new

当虚拟机接收到一个new指令,首先虚拟机会区常量池中定位这个类的符号引用,如果没有定位到就会执行对应的类加载过程,然后从堆中划分出一块符合这个对象大小的空间来;
分配空间时可能堆内存块不是连续的,虚拟机会自行维护一个空闲列表,从空闲列表中获取空闲空间来给对象分配空间;
在虚拟机获得了该对象的分配空间后,开始设置这个对象的初始化信息,所有属性先初始成0值,再设置对应的ObjectHeader(记录对象的元数据信息、哈希码、GC分代年龄,锁信息,对象的所属类信息等);
最后才是按照程序员的意愿,给对象初始化值;
这样,一个对象才真正的创建出来;
由于分配空间时并发的情况,可能会因为两个线程同时申请一个内存空间的问题,还会有一个本地线程缓冲区的概念,来保证每个线程都再自己的缓冲区进行内存分配,通过 -XX:+/-UseTLAB 来设定使用缓冲区(Thread Local Allocation Buffer);

参考文献 《深入理解Java虚拟机》 ---------- 周志明,机械工业出版社

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值