Java虚拟机之内存结构

Java8内存结构

本文基于Jdk8

正文前先看一张图(来自美团2020年技术年货):

在这里插入图片描述

栈主要包括 程序计数器,本地方法栈(HotSpot和虚拟机栈合二为一)和虚拟机栈

程序计数器:

线程私有的一小块内存,可以看做当前线程执行的字节码行号指示器,为线程切换恢复到正确的执行位置,是Java虚拟机中唯一一个没有 OutofMemoryError 的地方

Java虚拟机栈 :
在这里插入图片描述

每个Java线程都有一个私有Java虚拟机栈,与该线程同时创建。 在虚拟机栈内,每个方法会生成一个栈帧,用于存储 局部变量表,操作数栈,动态链接,方法出口等信息。一个方法的调用到执行完成的过程,代表栈帧从入栈到出栈的过程

局部变量存放 八大基本类型和对象引用(reference,可能是一个指向起始地址的引用指针,也可能是指向一个代表对象的句柄)和returnAddress类型(指向字节码指令的地址)

64位长度的long和double类型的数据会占用2个局部变量空间,其他类型只占用一个。局部变量所需要的内存在编译期间已经分配完成,在方法运行期间不会改变局部变量表的大小

如果线程请求的栈深度大于虚拟机允许的深度,抛出StackOverflowError 异常
如果扩展时无法申请到足够的内存,抛出 OutofMemoryError 异常

本地方法栈

本地方法栈和虚拟机栈的作用相似,不过本地方法栈作用于Native方法,也会出现 StackOverflowError 和 OutofMemoryError 异常

Java堆

Java堆是所有线程共享的一块内存区域。几乎所有的对象实例都在堆上分配内存。但是JIT编译器的发展和逃逸分析技术成熟,栈上分配和标量替换优化技术会导致一些变化。
Java 堆 可以细分:新生代(Eden(TLAB)/From Survivor/To Survivor),老年代
堆内存不够时会出现 OutofMemoryError 异常

TLAB(Thread Local Allocation Buffer)
TLAB是一小块线程独享的内存区域,避免了对象分配时的竞争。TLAB占用的是eden区的空间。根据虚拟机参数 -XX:UseTLAB进行启用(默认启用)。
TLAB空间的内存非常小,缺省情况下仅占有整个Eden空间的1%,也可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。
TLAB通常很小,所以放不下大对象,大对象在Eden区分配内存

逃逸分析
逃逸分析是一种确定指针动态范围的方法——分析在程序的哪些地方可以访问到指针
JVM判断新创建的对象是否逃逸的依据有:
1、对象被赋值给堆中对象的字段和类的静态变量。因为对象呗放入堆中,其他线程可以访问,编译器无法再进行追踪对象使用情况
2、对象被传进了不确定的代码中去运行。因为编译器是不能事先完整知道这段代码会对对象做什么操作
确定对象不能逃逸时,则可以进行一些优化:
1.栈上分配:在new语句方法退出时,通过弹出当前方法的栈帧来自动回收分配的内存,就无需GC回收了,HotSpot采用标量替换代替了栈上分配
2.标量替换:标量指仅能存储一个值得变量,例如基本类型,相反可以存储多个值得变量为聚合量,编译器会将未逃逸的聚合量分解成多个标量
3.锁消除:不能逃逸,对该对象加锁,解锁就没有意义,因为线程不能获得该锁对象,次情况不多见

   public static void main(String[] args) {
        Test();
    }
    public static void Test(){
        Write write = new Write();
        Read read = new Read();
        read.setWrite(write);
     }
     class Write {
     }
     class Read {
        private Write write;
        public void setWrite(Write write) {
            this.write = write;
        }
      }

编译器可以通过逃逸分析Read()对象不会逃出Test()方法的调用,则Write()对象也不能,因此编译器可以安全的在栈上分配两个对象

   @AllArgsConstructor
    class Hxx {
        String sex;
        int age;
    }
    public void lzx() {
        Hxx hxx = new Hxx("n", 30);
        test(hxx.age, hxx.sex);
    }

进过逃逸分析Hxx()未能逃出lzx()的调用,因此可以将聚合量Hxx()替换成两个标量set和age

    @AllArgsConstructor
    class Hxx {
        String sex;
        int age;
    }
    public void lzx() {
        int age = 30;
        String sex = "n";
        test(age, sex);
    }

MeteSpace :

Metaspace用来取代Java8之前的方法区(永久代为HotSpot对方法区的实现)。方法区在启动时大小就已经确定,对一些动态类信息加载容易出现OOM异常,而且GC效率也低。
每一个类加载器的存储区域都称作一个元空间,所有的元空间合在一起就是我们一直说的元空间。 当一个类加载器被垃圾回收器标记为不再存活,其对应的元空间会被回收
Metaspace由两大部分:Klass Metaspace和NoKlass Metaspace

堆外内存 :

堆外内存占用的是本地内存

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值