JAVA | Java对象的内存分配过程是如何保证线程安全的?
专注于Java领域优质技术,欢迎关注
作者 l Hollis 来源 l Hollis(ID:hollischuang)
JVM内存结构,是很重要的知识,相信每一个静心准备过面试的程序员都可以清楚的把堆、栈、方法区等介绍的比较清楚。
上图,是一张在作者根据《Java虚拟机规范(Java SE 8)》中描述的JVM运行时内存区域结构画的。
很多人都知道Java对象是在堆内存中分配空间的(JIT优化除外),也知道内存分配过程中是线程安全的,那么虚拟机到底是如何保证线程安全的呢?本文就来简单介绍一下。
1 Java对象的内存分配
我们知道,Java是一门面向对象的语言,我们在Java中使用的对象都需要被创建出来,在Java中,创建一个对象的方法有很多种,如使用new、使用反射、使用Clone方法等,但是无论如何,对象在创建过程中,都需要进行内存分配。
拿最常见的new关键字举例,当我们使用new创建对象后代码开始运行后,虚拟机执行到这条new指令的时候,会先检查要new的对象对应的类是否已被加载,如果没有被加载则先进行类加载。
在类加载检查通过之后,就需要给对象进行内存分配了,分配的内存主要用来存放对象的实例变量。
在进行内存分配时,需要根据对象中的实例变量情况等信息确定需要分配的空间大小,然后从Java堆中划分出这样一块区域(假设没有JIT优化)。
根据JVM使用的垃圾回收器的类型,因其回收算法不同,会导致堆中内存分配情况不同。如标记-清楚算法回收后的内存中会有大量不连续的内存碎片,在给新的对象分配的时候,就需要通过"空闲列表"来确定一块空闲区域。(这部分不是本文重点,读者可以自行学习一下。)
无论那种方式,最终都需要确定出一块内存区域,用于给新建对象分配内存。我们知道,对象的内存分配过程中,主要是对象的引用指向这个内存区域,然后进行初始化操作。
那么问题就来了:
在并发场景中,如何内存分配过程的线程安全性?如果两个线程先后把对象引用指向了同一个内存区域,怎么办。
2 TLAB
一般有两种解决方案:
- 1、对分配内存空间的动作做同步处理,采用CAS机制,配合失败重试的方式保证更新操作的线程安全性。
- 2、每个线程在Java堆中预先分配一小块内存,然后再给对象分配内存的时候,直接在自己这块"私有"内存中分配,当这部分区域用完之后,再分配新的"私有"内存。
方案1在每次分配时都需要进行同步控制,这种是比较低效的。
方案2是HotSpot虚拟机中采用的,这种方案被称之为TLAB分配,即Thread Local Allocation Buffer。这部分Buffer是从堆中划分出来的,但是是本地线程独享的。
这里值得注意的是,我们说TLAB时线程独享的,但是只是在“分配”这个动作上是线程独占的,至于在读取、垃圾回收等动作上都是线程共享的。而且在使用上也没有什么区别。
另外,TLAB仅作用于新生代的Eden Space,对象被创建的时候首先放到这个区域,但是新生代分配不了内存的大对象会直接进入老年代。因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
所以,虽然对象刚开始可能通过TLAB分配内存,存放在Eden区,但是还是会被垃圾回收或者被移到Survivor Space、Old Gen等。
不知道大家有没有想过,我们使用了TLAB之后,在TLAB上给对象分配内存时线程独享的了,这就没有冲突了,但是,TLAB这块内存自身从堆中划分出来的过程也可能存在内存安全问题啊。
所以,在对于TLAB的分配过程,还是需要进行同步控制的。但是这种开销相比于每次为单个对象划分内存时候对进行同步控制的要低的多。
虚拟机是否使用TLAB是可以选择的,可以通过设置-XX:+/-UseTLAB参数来指定。
3 总结
为了保证Java对象的内存分配的安全性,同时提升效率,每个线程在Java堆中可以预先分配一小块内存,这部分内存称之为TLAB(Thread Local Allocation Buffer),这块内存的分配时线程独占的,读取、使用、回收是线程共享的。
可以通过设置-XX:+/-UseTLAB参数来指定是否开启TLAB分配。
参考资料:
《深入理解Java虚拟机》
https://www.cnblogs.com/straybirds/p/8529924.html https://www.zhihu.com/question/56538259
JAVA | Java对象的内存分配过程是如何保证线程安全的?的更多相关文章
- 灵魂拷问:Java对象的内存分配过程是如何保证线程安全的?(阿里面试)
JVM内存结构,是很重要的知识,相信每一个静心准备过面试的程序员都可以清楚的把堆.栈.方法区等介绍的比较清楚. 上图,是一张在作者根据<Java虚拟机规范(Java SE 8)>中描述的J ...
- Java基础-对象的内存分配与初始化(一定要明白的干货)
首先,什么是类的加载?类的加载由类加载器执行.该步骤将查找字节码(classpath指定目录),并从这些字节码中创建一个Class对象.Java虚拟机为每种类型管理一个独一无二的Class对象.也就是 ...
- Java中对象的内存分配机制
一.内存划分 Java把内存划分为两种,一种是栈内存,另一种是堆内存. 1.栈内存 在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配.当在一段代码块定义一个变量时,Java就在栈 ...
- 源码分析:Java对象的内存分配
Java对象的分配,根据其过程,将其分为快速分配和慢速分配两种形式,其中快速分配使用无锁的指针碰撞技术在新生代的Eden区上进行分配,而慢速分配根据堆的实现方式.GC的实现方式.代的实现方式不同而具有 ...
- Java中的成员初始化顺序和内存分配过程
Java中的成员初始化顺序和内存分配过程 原帖是这样描述的: http://java.dzone.com/articles/java-object-initialization?utm_source= ...
- Java虚拟机7:内存分配原则
前言 对象的内存分配,往大的方向上讲,就是在堆上分配,少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节决定于当前使用的是哪种垃圾收集器组合,当然还有虚拟机中与内存相关的参数 ...
- java程序运行时内存分配详解
java程序运行时内存分配详解 这篇文章主要介绍了java程序运行时内存分配详解 ,需要的朋友可以参考下 一. 基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个 ...
- Java虚拟机11:内存分配原则
前言 JVM的自动内存管理要自动化的解决两个问题:对象分配内存以及回收分配给对象的内存.对象的内存分配一般是指在堆上分配,少数情况下也可能会直接分配在老年代上,对象主要分配在新生代的Eden 区上,如 ...
- 《深入理解 java 虚拟机》学习 -- 内存分配
<深入理解 java 虚拟机>学习 -- 内存分配 1. Minor GC 和 Full GC 区别 概念: 新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java ...
随机推荐
- Odoo CRM模块
转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10825983.html 一:理解CRM CRM:客户关系管理,是指企业用CRM技术来管理与客户之间的关系. ...
- 浅谈原子操作、volatile、CPU执行顺序
浅谈原子操作.volatile.CPU执行顺序 在计算机发展的鸿蒙年代,程序都是顺序执行,编译器也只是简单地翻译指令,随着硬件和软件的飞速增长,原来的工具和硬件渐渐地力不从心,也逐渐涌现出各路大神在原 ...
- XXX接口自动化测试方案
XXX接口自动化测试方案 1.引言 1.1 文档版本 版本 作者 审批 备注 V1.0 XXXX 创建测试方案文档 1.2 项目情况 项目名称 XXX 项目版本 V1.0 项目经理 XX 测试人员 X ...
- seaborn(1)---画关联图
将 Seaborn 提供的样式声明代码 sns.set() 放置在绘图前,就可以设置图像的样式 sns., color_codes=False, rc=None) context= 参数控制着默认的画 ...
- 说说openjdk及G1回收器日志内容详细分析
谈谈openjdk: 在正式往下学习JVM之前,这里谈谈openjdk这个网站,这个在学习java并发时也用过它来分析过锁的底层实现,如:https://www.cnblogs.com/webor20 ...
- js动画--链式运动
前面几节我们只是讲述了一种运动,这节课我将讲述链式运动:就以一个动作接着一个动作完成. 对于这个实现,我们只需要改变一下就可以实现了,设置一个回调函数. var timer; window.onloa ...
- 安全组与网络ACL
通过配置网络ACL和安全组策略,保障VPC内的弹性云服务器安全使用. 安全组对弹性云服务器进行防护:设置不同安全组访问规则实现系统访问控制 网络ACL对子网进行防护:可实现网络区域访问控制
- KVM管理工具
Ovirt:功能强大,RHEV的开源版本 WebVirtMgr:virt-manager的WEB模式的替代品 ConVirt:分为开源版.商业版 Openstack:开源框架,复杂程度较高
- centos7离线部署Patroni
实验环境Centos7.7.1908 x86_64 这里说明下为什么需要安装gcc readline-devel zlib-devel这三个包,因为编译安装postgres需要用到 一.首先安装gcc ...
- scala中可以执行外部命令Process
后续用到在总结 Process(s"hadoop fs -rm -r ${path}").!!