站内很多人都问我,所谓线程的“工作内存”到底是个什么东西?有的人认为是线程的栈,其实这种理解是不正确的。看看JLS(java语言规范)对线程工作内存的描述,线程的working memory只是cpu的寄存器和高速缓存的抽象描述。
可能 很多人都觉得莫名其妙,说JVM的内存模型,怎么会扯到cpu上去呢?在此,我认为很有必要阐述下,免得很多人看得不明不白的。先抛开java虚拟机不谈,我们都知道,现在的计算机,cpu在计算的时候,并不总是从内存读取数据,它的数据读取顺序优先级是:寄存器-高速缓存-内存。线程耗费的是CPU,线程计算的时候,原始的数据来自内存,在计算过程中,有些数据可能被频繁读取,这些数据被存储在寄存器和高速缓存中,当线程计算完后,这些缓存的数据在适当的时候应该写回内存。当个多个线程同时读写某个内存数据时,就会产生多线程并发问题,涉及到三个特性:原子性,有序性,可见性。在《线程安全总结》这篇文章中,为了理解方便,我把原子性和有序性统一叫做“多线程执行有序性”。支持多线程的平台都会面临这种问题,运行在多线程平台上支持多线程的语言应该提供解决该问题的方案。
那么,我们看看JVM,JVM是一个虚拟的计算机,它也会面临多线程并发问题,java程序运行在java虚拟机平台上,java程序员不可能直接去控制底层线程对寄存器高速缓存内存之间的同步,那么java从语法层面,应该给开发人员提供一种解决方案,这个方案就是诸如 synchronized, volatile,锁机制(如同步块,就绪队列,阻塞队列)等等。这些方案只是语法层面的,但我们要从本质上去理解它,不能仅仅知道一个 synchronized 可以保证同步就完了。 在这里我说的是jvm的内存模型,是动态的,面向多线程并发的,沿袭JSL的“working memory”的说法,只是不想牵扯到太多底层细节,因为 《线程安全总结》这篇文章意在说明怎样从语法层面去理解java的线程同步,知道各个关键字的使用场景。
今天有人问我,那java的线程不是有栈吗?难道栈不是工作内存吗?工作内存这四个字得放到具体的场景中描述,方能体现它具体的意义,在描述JVM的线程同步时,工作内存指的是寄存器和告诉缓存的抽象描述,具体请自行参阅JLS。上面讲的都是动态的内存模型,甚至已经超越了JVM的范围,那么JVM的内存静态存储是怎么划分的?今天还有人问我,jvm的内存模型不是有eden区吗?也不见你提起。我跟他说,这是两个角度去看的,甚至是两个不同的范围,动态的线程同步的内存模型,涵盖了cpu,寄存器,高速缓存,内存;JVM的静态内存储模型只是一种对内存的物理划分而已,它只局限在内存,而且只局限在JVM的内存。那些什么线程栈,eden区都仅仅在JVM内存。
说说JVM的线程栈和有个朋友反复跟我纠结的eden区吧。JVM的内存,被划分了很多的区域:
1.程序计数器
每一个Java线程都有一个程序计数器来用于保存程序执行到当前方法的哪一个指令。
2.线程栈
线程的每个方法被执行的时候,都会同时创建一个帧(Frame)用于存储本地变量表、操作栈、动态链接、方法出入口等信息。每一个方法的调用至完成,就意味着一个帧在VM栈中的入栈至出栈的过程。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果VM栈可以动态扩展(VM Spec中允许固定长度的VM栈),当扩展时无法申请到足够内存则抛出OutOfMemoryError异常。
3.本地方法栈
4.堆
每个线程的栈都是该线程私有的,堆则是所有线程共享的。当我们new一个对象时,该对象就被分配到了堆中。但是堆,并不是一个简单的概念,堆区又划分了很多区域,为什么堆划分成这么多区域,这是为了JVM的内存垃圾收集,似乎越扯越远了,扯到垃圾收集了,现在的jvm的gc都是按代收集,堆区大致被分为三大块:新生代,旧生代,持久代(虚拟的);新生代又分为eden区,s0区,s1区。新建一个对象时,基本小的对象,生命周期短的对象都会放在新生代的eden区中,eden区满时,有一个小范围的gc(minor gc),整个新生代满时,会有一个大范围的gc(major gc),将新生代里的部分对象转到旧生代里。
5.方法区
其实就是永久代(Permanent Generation),方法区中存放了每个Class的结构信息,包括常量池、字段描述、方法描述等等。VM Space描述中对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存,也可以选择固定大小或者可扩展外,甚至可以选择不实现垃圾收集。相对来说,垃圾收集行为在这个区域是相对比较少发生的,但并不是某些描述那样永久代不会发生GC(至 少对当前主流的商业JVM实现来说是如此),这里的GC主要是对常量池的回收和对类的卸载,虽然回收的“成绩”一般也比较差强人意,尤其是类卸载,条件相当苛刻。
6.常量池
Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量表(constant_pool table),用于存放编译期已可知的常量,这部分内容将在类加载后进入方法区(永久代)存放。但是Java语言并不要求常量一定只有编译期预置入Class的常量表的内容才能进入方法区常量池,运行期间也可将新内容放入常量池(最典型的String.intern()方法)。
关于垃圾收集,在此不多说,流到垃圾收集那一章再详细说吧。关于java的同步,其实还有基于CPU原语的比较并交换的非阻塞算法(CAS),不过这个在java的并发包里已经实现了很多,因此关于这点,就留到java并发包那一章介绍吧。后面我会专门写一篇文章,JVM内存与垃圾收集。
分享到:
相关推荐
以下是对Java线程安全的深入总结: ### 一、线程安全的定义 线程安全是指当多个线程访问同一块代码时,如果每个线程都能得到预期的结果,且不产生数据不一致或同步问题,那么这块代码就被称为线程安全的。Java中的...
### 线程安全总结 #### 概述 线程安全是Java多线程编程中的一个重要概念,确保在多线程环境下程序能够正确地运行,避免数据错误和资源竞争等问题。本文档旨在从基础概念出发,详细介绍导致线程不安全的原因、如何...
标题“java线程安全总结.pdf”指向了文档的主要内容:这是关于Java编程语言中的线程安全问题的总结性资料。线程安全是并发编程中的一个核心概念,它与Java多线程技术紧密相关。文档的描述信息非常简洁,只是重复了...
java线程安全总结 帮助你们更好的理解什么是线程 什么是网络安全
但是需要注意,虽然这个方法可以保证基本的线程安全,但迭代仍然是非线程安全的,即不能在遍历过程中修改Map。 2. 使用ConcurrentHashMap:Java从1.5版本开始引入了ConcurrentHashMap,它是线程安全且高并发性能的...
用思维导图将Java线程安全性相关基本概念联系起来
总结来说,实现一个线程安全的C++日志库需要深入理解多线程编程和文件I/O,同时考虑性能、可扩展性和易用性。`Log.cpp`和`Log.h`中的代码可能包含了上述所有要素,通过合理的封装和设计,确保在复杂多线程环境下,...
Java线程安全是多线程编程中的一个核心概念,尤其在服务器端开发中,理解并掌握线程安全至关重要。线程安全是指当多个线程访问一个对象时,如果这个对象的状态始终保持一致,那么我们就说这个对象是线程安全的。在...
Java线程安全是多线程编程中的一个核心概念,...总结起来,Java线程安全是一个复杂而又重要的主题,涉及到多线程编程中的各种机制和策略。理解并掌握这些知识点,可以帮助开发者编写出高效、稳定、线程安全的Java程序。
线程本地存储,如 `ThreadLocal` 类,提供了线程安全的数据结构,使得每个线程拥有自己的副本,避免了数据竞争问题。 #### 四、Java线程:线程状态的转换 - **线程的状态** Java线程的状态包括新建 (`NEW`)、...
总结来说,线程安全是多线程编程的基础,而原子操作和读写锁是实现线程安全的重要工具。易语言提供了丰富的支持,使得开发者能够方便地在自己的程序中实现线程安全,从而构建高效且稳定的多线程应用程序。通过合理...
#### 二、非线程安全的单例实现方式 在单例模式的实现中,通常有两种基本的方法:饿汉式和懒汉式。 1. **饿汉式单例**: ```java public final class EagerSingleton { private static EagerSingleton singObj =...
总结来说,理解和掌握线程安全对于开发高效、稳定的多线程C#应用程序至关重要。通过使用正确的同步机制,我们可以确保在多线程环境中安全地写入txt日志,避免数据竞争和错误。同时,设计良好的日志系统有助于调试和...
总结来说,线程安全问题是多线程编程中不可避免的一环。由于多线程能够显著提高程序的效率和响应速度,使得其在服务器端、桌面应用和嵌入式系统等领域被广泛采用。然而,只有深入理解线程安全的概念,并且在设计和...
- `java.util.concurrent.atomic` 包提供了原子类,用于在不需要锁的情况下进行线程安全的操作。 #### 二十、Java线程:新特征-障碍器 - `CyclicBarrier` 和 `Phaser` 类允许一组线程相互等待,直到所有线程到达一...
#### 二、线程安全的Vector详解 ##### Vector概述 `Vector` 是 Java 集合框架中最早提供的一种动态数组实现,它可以像数组一样存储元素,同时也支持动态扩展。相比于 `ArrayList`,`Vector` 的所有方法都默认是同步...