JVM内存区域解析(TLAB分配,逃逸分析)

JVM内存区域解析(TLAB分配,逃逸分析)

  • 前言

这是我写的第一篇介绍JVM的文章,有一些不对之处,希望大家可以留言互相交流,在交流探讨之中共同进步。
在这一篇文章中主要会尽量详细明白的介绍JVM的内存布局,以及在对象分配的时候流程和JDK1.6之后出现的逃逸分析

  • JVM的内存布局

这里是引用
这里用一张图首先让大家对JVM有个清晰地认知,今天我们主要介绍的是运行时数据区

程序计数器
就是一块比较小的内存空间,存储的是线程执行字节码的行号指示器,整个线程的分支,循环,异常等等都通过这个会进行下去,由于多线程是通过CPU分配的时间片来执行,所以每条线程都要有一个在时间片执行完重新分配时间片的时候,恢复之前状态的记录,所以就有了程序计数器,并且是线程所独有的,并且这里并不会出现OOM的情况

虚拟机栈
生命周期和线程同步,线程中每次执行方法都会同步生成一个栈帧,栈帧中存在着局部变量表,操作数栈,动态链接和方法返回地址,这里随着方法入栈出栈,对于栈帧中的这些东西会在之后介绍虚拟机执行的时候详细介绍这些,这里要注意两个问题,这里可能出现两个异常,一个是虚拟机栈的深度超过JVM限制的最大栈深度,就会抛出栈溢出的异常,另一个是虚拟机栈的的容量是动态扩展的,如果在扩展的时候申请内存失败,那就回抛出内存溢出的异常

本地方法栈
这里的和虚拟机栈的区别就是虚拟机栈的方法是适用于虚拟机执行java方法,但是本地方法栈却适用于本地方法,就像是我之前写的ConcurrentHashMap源码分析里面,1.8的ConcurrentHashMap使用的就是CAS和Node节点,那里面的CAS中的Unsafe方法就是本地方法,这里可能出现两个异常,一个是虚拟机栈的深度超过JVM限制的最大栈深度,就会抛出栈溢出的异常,另一个是虚拟机栈的的容量是动态扩展的,如果在扩展的时候申请内存失败,那就回抛出内存溢出的异常


Java堆是内存中最大的一块区域,主要的作用就是存放对象实例,被所有线程共享使用,堆中又分为Eden、Survivor1、Survivor2、Old 也可以认为是年轻代和老年代,可以说所有的对象都是在堆中分配的,这在JDK1.6之后出现了逃逸分析,栈上分配的优化之前可以认为是正确的,但是在1.6之后这样的说法很明显是一种不明确的说法,在堆中分配对象还是用了TLAB的方法,关于逃逸分析和TLAB在下面会单独讲一下,这里同样也会有异常的产生,也就是对象要在堆中分配的时候内存不足,这时会出现内存溢出的异常

方法区
也和堆一样,是所有线程共享的一块空间,为了区分也经常被称为非堆,这里面主要存储的就是类型信息,静态变量,常量,即时编译器编译的代码缓存,在JDK8之前大家还习惯称呼方法区为永久代,并将原来放在永久代的字符串常量池和静态变量移到了堆空间中,使用的是JVM的内存空间,但是在JDK8时候方法区改用了元空间实现,并使用的是本地内存,由于方法区回收的条件较为苛刻,一般这个区域的垃圾回收并不会令人满意,也可能会出现内存溢出的情况

运行时常量池
运行时常量池也是方法区的一部分,Class文件除了有类的版本信息,字段,方法,接口这些信息之外,还有常量池表,也是就俗称的Class常量池,里面含有大量的字面量和符号引用,在类加载完成之后会被加载到运行时常量池,主要是为了字节码使用动态链接的时候使用的
在这里插入图片描述

直接内存
直接内存并不是运行时数据区的一部分,但是也可能出现内存溢出的情况,在1.4的时候引入了NIO通道,可是使用Native函数分配堆外内存,使用堆中的DirectByteBuff来操作对外内存,直接内存是会被gc的具体大家可以看这一篇文章
JAVA堆外内存的简介和使用

  • TLAB分配

TLAB分配是一个比较重要的点,这里希望大家能够认真看一下,TLAB全程Thread Local Allocation Buffer线程私有的分配缓冲区,来提高对象分配的效率,这里又有几种情况,我们先不考虑栈上分配的情况
在这里插入图片描述
这里用一张图来介绍一下TLAB分配的情况,首先我需要判断是不是开启了TLAB没有的话就会直接走外分配,这里的分配策略就是看GC的算法了(最好对比的就是G1和别的),然后开启了就会从当前线程分配TLAB,如果分配失败就会判断是否大于了最大浪费空间(最大浪费空间是一个动态的值,就是为了增强GC的效率,因为退回Eden区的时候,剩余没使用的就是一个空隙,需要使用dummy object填满,这样GC就可以跳过标记回收,增强GC效率),大于的话就说明浪费的比较多,直接走外分配,小于的话就去Eden区重新申请一个

  • 逃逸分析

是在1.6之后出现的新的优化,旨在为了减少GC,因为有的作用域只存在于栈上,随着栈帧入栈出栈,这样分配在栈上显得更为合适
这里逃逸分析又分为两种
方法逃逸
也就是在一个方法内创建的局部变量,被其他方法引用,这样就可以认为是方法逃逸
线程逃逸
这个线程中的变量被其他线程访问到,这就可以认为是线程逃逸
如果这两种逃逸都不符合,就说明并没有发生逃逸,这就可以进行栈上分配,并不是在堆中进行对象分配

栈上分配
就是字面的意思,对于没有产生逃逸对象并不是在堆中进行分配,而是在栈上进行分配
标量替换
就是对于逃逸分析之后的确定不会被外部访问的对象,并且这个如果这个对象是聚合量并且可以被细分为标量(也就是java的基本类型,引用类型那种)那么JVM就不会创建这个对象,而是会使用若干标量来替代,这些标量在栈帧或寄存器上分配空间
同步消除
线程同步的代价是相当高的,同步的后果是降低并发性和性能。逃逸分析可以判断出某个对象是否始终只被一个线程访问,如果只被一个线程访问,那么对该对象的同步操作就可以转化成没有同步保护的操作,这样就能大大提高并发程度和性能。

-XX:+DoEscapeAnalysis开启逃逸分析(JDK 6u23以上默认开启)
-XX:-DoEscapeAnalysis 关闭逃逸分析
 
#标量替换基于分析逃逸基础之上,开启标量替换必须开启逃逸分析
-XX:+EliminateAllocations开启标量替换(jdk1.8默认开启,其它版本未测试)
-XX:-EliminateAllocations 关闭标量替换
 
#锁消除基于分析逃逸基础之上,开启锁消除必须开启逃逸分析
-XX:+EliminateLocks开启锁消除(jdk1.8默认开启,其它版本未测试)
-XX:-EliminateLocks 关闭锁消除
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值