谈谈java的内存模型

1. jvm

java中负责管理内存和垃圾回收等工作的东西:jvm我们称之为java虚拟机。简单来讲,就是jvm帮你管理你的内存分配与回收,不需要你去像c++中一样需要自己delete内存。

    需要注意的几点:
1. jvm只负责与内存相关的资源的管理,其他的不属于jvm的范畴,需要你自己去控制。
e.g.文件的读取操作中流的开启和关闭、数据库连接的释放等等,这些jvm不会管理,需要你自己显式地控制,否则会出现资源消耗的情况,最终导致系统崩溃。方法是:可以在finally块中显式地释放,无论如何finally块都会在内存资源被jvm回收之前获得执行,当然,这并不意味着可以明确gc的执行时间,gc是不能被程序员控制的!

2. 有了jvm的管理内存泄露的情况依然有可能会发生
很多初学者认为java自己管理gc,那么完全不必要担心内存泄露的情况发生,这是完全错误的认识。
我们看下面的一段模拟出栈的pop代码就发生了内存泄露:
public class Stack {
	
	Object[] stack;
	int top;

	public void pop(){
		top--;
	}
}
虽然top--之后我们不能再访问到已经被pop的元素,但是由于实际的对象仍然在stack数组中保存了引用,因此不会被gc回收,这样这段内存就处于这种状态:访问不到也不能回收,这就是内存泄露;一次两次是没有问题,但是当调用的次数很多的时候,内存会被一点一点得耗尽,直到系统崩溃。
正确的做法是:将stack数组的该引用指向null,让gc在随后回收实际的对象
public void pop(){
		stack[top] = null;
		top--;
	}


2. 堆和栈

jvm将java中的基本类型变量和对象的存储分为两个区域:堆区和栈区。
a. 栈区:
存放函数的临时变量,包括java的基本数据类型变量和指向对象的引用变量(这个比较难理解,说白了就是new对象的时候等号前面的那个变量)
b. 堆区:
存放java中的实际的对象(包括数组,数组也是一种对象,显然创建一个数组也是通过new出来的)。堆区对于程序员是不透明的,jvm不允许程序员直接访问堆区中的对象,而只能通过栈区中的引用对象通过引用的方法访问实际的对象,即从栈区的引用变量有一个指向堆区中的实际对象的“指针“。

jvm为什么要设立栈区和堆区之分?
这个问题我的理解是:
1. 为了效率(efficiency)。jvm的内存管理其实基于这样的一个事实:java程序的大部分对象或者变量都是短命的(即仅仅处于年轻代),而只有少量的对象或变量会进入持久代,还有极少数的对象生命周期更长,进入到了老年代。因此,设立栈区存放那些临时变量和基本数据类型,而在堆区中的对象则会进一步分成年轻代、持久代、老年代等,用不同的策略来实行高效的gc。
2. 为了安全。众所周知,c++中的指针式初学者的一个难题,很多有经验的码农遇到指针依然会出错。由于c++不是很熟,所以不能做过多的评价,不过记得空指针和野指针的问题应该是c++中的很容易出现的问题。在java中,通过引用变量的方式避免了类似的问题的发生,jvm拒绝程序员直接访问堆区中的对象,而必须通过栈区中的引用变量访问。

3. gc的机制

简单理解就是:引用标记算法,即gc计算堆中的对象是否有指向它的引用,如果有,那么就不会回收这个对象,否在就将对象放入到待回收区域,等待回收。如果在回收执行之前,对象又被重新引用了,那么它又会重新进入活动对象区,不会被回收了。
首先从stack和静态分配区的ref查找heap中对应的对象,如果没有ref就回收它
我们来看一个例子:
class X2{
    public X2 x;
    public static void main(String[] args){         
		X2 x2=new X2();        
		X2 x3=new X2();
        x2.x=x3;
        x3.x=x2;
        x2=new X2();
        x3=x2;
        doComplexStuff();
	}
}
after line 9 runs,how many objects are eligible for garbage collection?
A.0  B.1  C.2  D.3  E.4

在第8行之前,它的内存模型是这样的:

在第9行之后,它的内存模型变为:

于是之前的两块内存空间不再有ref,可以被gc了,所以答案应该是C:2个。

<think> 我们正在讨论Java内存模型(JMM)的相关信息。根据用户要求,我们需要参考之前提供的引用内容来构建回答。 注意:用户要求中提到了系统级指令,包括数学表达式的格式要求、中文回答、生成相关问题等。但当前问题关于JMM,暂时不涉及数学表达式,因此我们主要关注内容组织。 根据引用内容,我们可以总结JMM的几个关键点: 1. JMM是什么(定义和目的) 2. JMM的组成部分(主内存、工作内存) 3. JMM与硬件内存架构的关系 4. JMM如何解决并发问题(规则和机制) 回答结构建议: 1. JMM的定义和目的 2. JMM的核心结构(主内存和工作内存) 3. JMM与硬件内存的关系 4. JMM的内存交互操作(8种原子操作)和规则(happens-before等) 5. JMM如何保证并发安全(可见性、有序性、原子性) 注意:由于引用内容有限,我们主要基于引用内容,并补充必要知识(确保准确)来回答。 同时,按照要求,在回答末尾生成相关问题。 另外,在回答中引用参考的地方需要标注引用标识,如[^1]。注意:引用标识需要与之前提供的引用编号对应,但用户提供的引用中关于JMM的有多个(引用1-5都是关于JMM的),我们可以根据内容选择合适的引用标注。 由于引用1、2、3、4、5都涉及JMM,我们可以在相关的地方标注。 开始构建回答: </think> ### Java内存模型(JMM)深度解析 #### 1. **JMM的定义与核心目标** Java内存模型(JMM)是Java虚拟机规范定义的**抽象内存模型**,旨在解决两大核心问题: - **平台兼容性**:屏蔽不同硬件(CPU架构)和操作系统(内存管理)的访问差异[^1][^2] - **并发安全性**:规范多线程环境下变量的读写规则,确保一致性内存访问效果[^4] JMM本质上是一组**规则和规范**,而非物理存在。它定义了线程如何与主内存交互,以及线程私有工作内存的抽象概念[^3][^5]。 #### 2. **JMM核心结构** ``` +-----------------+ 读取/写入 +-----------------+ | 主内存 | <----------------> | 工作内存 | | (Main Memory) | 同步操作 | (Working Memory) | | - 共享变量 | | - 局部变量 | | - 静态变量 | | - 主内存副本 | +-----------------+ +-----------------+ ^ ^ ^ | | | | | +---------+---------+---------+ | | | +-------------+ +------------+ | +-----------------+-------------------------+ 线程1 线程2 线程3 ``` - **主内存**:存储所有共享变量(实例字段、静态字段),全局可见 - **工作内存**:线程私有空间,存储主内存变量的**副本**和局部变量[^5] - **交互协议**:通过8种原子操作实现主内存与工作内存的同步(`lock`/`unlock`/`read`/`load`/`use`/`assign`/`store`/`write`)[^4] #### 3. **JMM三大特性** 1. **原子性(Atomicity)** - 保证基本类型(除long/double)的读写操作原子性 - 通过`synchronized`和`Lock`扩展原子操作范围 2. **可见性(Visibility)** - **问题场景**:线程A修改共享变量,线程B无法立即感知 - **解决方案**: ```java volatile int counter = 0; // volatile强制刷新主内存 synchronized(lock) { counter++; } // 同步块自动刷新 ``` 3. **有序性(Ordering)** - **问题场景**:指令重排序导致执行顺序与代码不一致 - **控制手段**: - `volatile`禁止指令重排(内存屏障) - `synchronized`保证临界区串行执行 - `happens-before`规则(程序顺序规则、锁规则等)[^1] #### 4. **JMM与硬件架构关系** ``` +---------------------+ +---------------------+ | JMM抽象层 | | 物理硬件层 | | - 主内存 | | - RAM主内存 | | - 工作内存 | | - CPU寄存器 | +----------+----------+ | - CPU高速缓存(L1/L2)| | 映射关系 +----------+----------+ +---------------------------->| ``` - JMM的"主内存"对应物理内存+CPU缓存 - "工作内存"主要对应CPU寄存器和私有缓存[^3] - **关键差异**:JMM是逻辑划分,硬件架构是物理实现[^4] #### 5. **并发问题根源** 当违反JMM规则时会导致: 1. **脏读**:线程读取过时副本 2. **丢失更新**:多线程覆盖写操作 3. **内存不一致**:代码顺序与执行顺序错位 **解决方案示例**: ```java public class Counter { private volatile int value; // 保证可见性与有序性 public synchronized void increment() { // 保证原子性 value++; } } ``` #### 6. **JMM实际应用场景** 1. 双检锁单例模式(`volatile`防指令重排) 2. `ConcurrentHashMap`分段锁设计 3. `AtomicInteger`的CAS无锁实现 4. `ThreadLocal`线程私有存储 > JMM通过定义**内存屏障**(Memory Barrier)控制读写顺序,现代JVM在`volatile`和锁操作中自动插入屏障指令(如LoadLoad/StoreStore屏障)[^1][^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值