当前位置: 首页 > 科技观察

拜托,再问Java内存模型的时候就不要跟我说栈方法区了,,

时间:2023-03-17 18:19:09 科技观察

最近面试了很多Java中高级开发者,问了很多次关于Java内存模型。问完之后,很多人上来开始回答:Java内存模型由几部分组成,堆,本地方法栈,虚拟机栈,方法区……不想每次都打断他们的话,虽然我知道这是另一个误解我问题的朋友。其实我想问的Java内存模型是和并发编程有关的。而考生给我的答案叫JVM内存结构,完全不一样。很多时候,有些人在我不打断的情况下,慢慢地讲了GC相关的知识。既然如此,只能硬着头皮继续问一些JVM相关的知识。不过,我的本意其实是想看看他对Java并发了解多少。经常,在我继续问他们回答的“Java内存模型”的一些知识后,我会友善地提醒你,我想问的Java内存模型不是他回答的……有时候,我会提醒你further一句话:和并发编程有关,和主内存和线程工作内存有关。..那么,这篇文章就简单说一下如何回答这个关于Java内存模型的面试题。PS:本文不是狗屁,也不是有意“为生词吐槽”。我只是认为JMM在Java语言规范中有明确的定义。不信你随便找几本介绍JVM和并发编程的经典书籍,一点歧义都没有。作者坚持:我不能因为别人真的不懂就装傻!1、人们为什么会误解?首先我们来分析一下,为什么很多人,甚至大部分人,都会答错问题?我认为有以下几个原因:1.Java内存模型,这个词听起来太像内存分布的知识了。听起来跟并发编程没什么关系。2.网上很多资料都是错误的。不信你去网上搜索一下“Java内存模型”,你会发现很多人都以内存模型为名介绍JVM内存结构的知识。在这里说一句,我尝试去谷歌搜索“Java内存模型”,首页显示的结果如下:?首页前5篇文章中,有1篇错误,介绍的是JVM内存结构。幸运的是,首页前五篇文章中有两篇是我写的。至少我的这两篇文章没有误导!!3.还有一种情况,虽然见的不多,但是也有。也就是很多面试官自己认为内存模型就是介绍堆、栈、方法区的知识。结果,有时面试官不知道该如何回答。那么,什么是Java内存模型呢?我该如何回答这个面试问题?2.什么是内存模型?Java内存模型的来龙去脉我在《再有人问你Java内存模型是什么,就把这篇文章发给他》已经详细介绍过了,这里再复习一遍。Java内存模型翻译自英文JavaMemoryModel(JMM)。事实上,JMM并不像JVM内存结构那么真实。他只是一个抽象的概念。Java内存模型的相关知识在JSR-133:JavaMemoryModelandThreadSpecification中有描述。JMM与多线程有关。它描述了一组规则或规范,这些规则或规范定义了当一个线程写入共享变量时,它对另一个线程可见。Java内存模型(JavaMemoryModel,JMM)是一种符合内存模型规范,屏蔽各种硬件和操作系统的访问差异,保证Java程序在各种平台上获得一致的内存访问效果的模型。机制和规范。目的是解决多线程通过共享内存进行通信时存在的原子性、可见性(缓存一致性)和排序问题。那么,我们先来说说什么是所谓的内存模型规范,这里所说的原子性、可见性、有序性是指什么?原子线程是CPU调度的基本单元。CPU有时间片的概念,会根据不同的调度算法进行线程调度。所以在多线程场景下,会出现原子性问题。因为当一个线程在进行读、修改、写操作时,读、修改完成后,时间片就用完了,会被要求让出CPU,等待重新调度。在这种情况下,读取和写入不是原子操作。即存在原子性问题。缓存一致性在多核CPU、多线程场景下,每个核心至少有一个一级缓存。如果进程中有多个线程访问某个共享内存,而这多个线程分别在不同的核上执行,每个核都会在自己的caehe中预留一块共享内存缓冲区。由于多核可以并行,多个线程可能同时写入自己的缓存,各个缓存之间的数据可能不一样。在CPU和主存之间加入缓存,在多线程场景下可能会造成缓存一致性问题,即在多核CPU中,在每个核自己的缓存中,相同数据的缓存内容可能不一致。除了时间片的引入,由于处理器优化和指令重排,CPU也可能乱序执行输入的代码。比如load->add->save可能优化成load->save->add。这就是秩序的问题。多CPU多级缓存带来的一致性问题,CPU时间片机制带来的原子性问题,处理器优化和指令重排带来的顺序问题,都是硬件不断升级带来的。那么,有没有什么机制可以很好的解决上述问题呢?最简单直接的办法就是取消处理器和处理器优化技术,取消CPU缓存,让CPU直接与主存交互。但是这样做可以保证多线程下的并发问题。不过,这样会噎着,有点浪费粮食。因此,为了保证在并发编程中能够满足原子性、可见性和有序性。有一个重要的概念,就是——内存模型。为了保证共享内存的正确性(可见性、顺序性和原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。这些规则用来规范对内存的读写操作,以保证指令执行的正确性。它与处理器有关,与缓存有关,与并发性有关,与编译器有关。解决了CPU多级缓存、处理器优化、指令重排等带来的内存访问问题,保证了并发场景下的一致性、原子性和顺序性。针对以上问题,不同的操作系统有不同的解决方案,而Java语言定义了一套属于Java语言的内存模型规范,即Java内存模型,以屏蔽底层差异。Java内存模型规定所有的变量都存放在主存中,每个线程都有自己的工作内存。线程的工作内存存储线程中使用的变量的主内存副本的副本。所有操作都必须在工作内存中进行,不能直接读写主内存。不同的线程不能直接访问对方工作内存中的变量,线程间变量的传递需要自己的工作内存和主存之间进行数据同步。JMM作用于工作内存和主内存之间的数据同步过程。他规定了如何做数据同步,什么时候做数据同步。?3。Java内存模型的实现了解Java多线程的朋友都知道,Java中提供了一系列与并发处理相关的关键字,如volatile、synchronized、final、concurrenpackages等,其实这些都是提供给程序员的一些关键字Java内存模型封装了底层实现。在开发多线程代码时,我们可以直接使用synchronized等关键字来控制并发,而无需关心底层编译器优化、缓存一致性等问题。因此,Java内存模型除了定义了一套规范外,还提供了一系列原语,将底层实现封装起来,供开发者直接使用。本文不打算一一介绍所有关键字的用法,因为关于每个关键字的用法网上有很多资料。读者可自学。这篇文章要介绍的另一个重要的事情是我们前面提到并发编程必须解决原子性、有序性和一致性的问题。让我们看一下Java中用于确保它的方法。原子性在Java中,为了保证原子性,提供了两条高级字节码指令monitorenter和monitorexit。在synchronized的实现原理一文中介绍了Java中这两个字节码对应的关键字是synchronized。因此在Java中可以使用synchronized来保证方法和代码块内的操作是原子的。VisibilityJava内存模型是通过在变量修改后将新值同步回主存,并在读取变量前从主存中刷新变量值来实现的,它依赖于主存作为传输介质。Java中的volatile关键字提供了一个功能,就是被它修饰的变量在被修改后可以立即同步到主存,被它修饰的变量在每次使用前从主存中刷新。因此可以使用volatile来保证多线程运行时变量的可见性。除了volatile,Java中的synchronized和final这两个关键字也可以实现可见性。只是实现方法不同,这里就不展开了。排序在Java中,可以使用synchronized和volatile来保证多线程之间的操作顺序。实现方式不同:volatile关键字禁止指令重排。synchronized关键字确保同一时间只允许一个线程运行。好了,这里简单介绍一下Java并发编程中可以用来解决原子性、可见性和顺序性的关键字。读者可能已经发现,synchronized关键字似乎是最好的,它可以同时满足以上三个特点,这其实也是很多人滥用synchronized的原因。但是,synchronized对性能的影响更大。虽然编译器提供了很多锁优化技术,但不建议过度使用。4.如何回答面试我介绍了一些Java内存模型相关的基础知识,只是基础,不是全部,因为任何知识点还是可以扩展的,比如volatile是如何实现可见性的?synchronized是如何实现Orderly的?然而,当面试官问你:你能简单介绍一下你理解的内存模型吗?首先跟面试官确认一下:你说的内存模型指的是JMM,跟并发编程相关的是哪一个?得到肯定回答后,开始介绍(如果没有,那你可能要回答堆,栈,方法区……囧……这里也可以结合上下文理解面试官想问的问题.What):Java内存模型实际上是一种机制和规范,保证Java程序在各种平台上的内存访问都能获得一致的效果。目的是解决多线程通过共享内存进行通信时存在的原子性、可见性(缓存一致性)和排序问题。此外,Java内存模型还提供了一系列原语,将底层实现封装起来,供开发者直接使用。比如我们常用的一些关键字:synchronized、volatile、concurrent包。回答到这里就可以了,然后面试官可能会继续提问,然后根据他的问题继续回答。所以,当有人再问你Java内存模型的时候,不要直接回答栈,方法区甚至GC,先想想面试官的问题。