JVM内存模型测试--Thread



 今天在SUN的jdk上测试了Thread栈占用内存的情况,帮助进一步了解JVM的内存模型。配置如下:

WIN7+4G 

 

SUN JDK

D:\maven\smart\smart>java -version
java version "1.6.0_21"
Java(TM) SE Runtime Environment (build 1.6.0_21-b06)
Java HotSpot(TM) Client VM (build 17.0-b16, mixed mode, sharing)

 

初始内存情况如下:


    
 

 测试类代码如下:

 

package org.smart.threadtest;

public class ThreadStackTest extends Thread {
	public ThreadStackTest(long num) {
		super("[ThreadStackTest] Thread:" + num);
	};

	public void run() {
		String threadName = Thread.currentThread().getName();
		// System.out.println(threadName + " start.");
		try {
			Thread.sleep(10000);
		} catch (Exception e) {
			System.out.println("Exception from " + threadName + ".run");
		}
	}

	public static void main(String[] args) {
		ThreadStackTest t2 = null;
		for (int i = 0; i < 10000; i++) {
			System.out.println("Thread " + i + " start...");
			t2 = new ThreadStackTest(i);
			t2.start();
		}

	}
}

 

 

 测试配置为:每个线程分配10240k就是10M的Stack,heap分配1024M,命令行如下:

D:\Java\jdk1.6.0_20\bin\java -Djava.compiler=NONE -verbose:gc -XX:+HeapDumpOnOutOfMemoryError 
	-XX:HeapDumpPath=d:/temp/java.phd   -Xms1024m -Xmx1024m -XX:+PrintGCDetails 
	-XX:+PrintHeapAtGC -XX:ThreadStackSize=10240  -cp D:\maven\smart\smart\target\test-classes\ 
	org.smart.threadtest.ThreadStackTest

 

 输出结果:

  

Thread 72 start...
Thread 73 start...
Thread 74 start...
Thread 75 start...
Thread 76 start...
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
        at java.lang.Thread.start0(Native Method)
        at java.lang.Thread.start(Thread.java:597)
        at org.smart.threadtest.ThreadStackTest.main(ThreadStackTest.java:22)
Heap
 def new generation   total 314560K, used 14417K [0x03b50000, 0x190a0000, 0x190a0000)
  eden space 279616K,   5% used [0x03b50000, 0x0493ba48, 0x14c60000)
  from space 34944K,   0% used [0x16e80000, 0x16ea8d18, 0x190a0000)
  to   space 34944K,   0% used [0x14c60000, 0x14c60000, 0x16e80000)
 tenured generation   total 699072K, used 0K [0x190a0000, 0x43b50000, 0x43b50000)
   the space 699072K,   0% used [0x190a0000, 0x190a0000, 0x190a0200, 0x43b50000)
 compacting perm gen  total 12288K, used 2088K [0x43b50000, 0x44750000, 0x47b50000)
   the space 12288K,  16% used [0x43b50000, 0x43d5a110, 0x43d5a200, 0x44750000)
No shared spaces configured.

 

从输出可以看出,启动了76个线程之后,内存溢出,有两个比较奇怪的地方:

1、没有产生heapdump文件(注:如果是heap内存满可以产生,测试过,配置没有错)

2、在WIN7资源监视器中显示内存基本没有波动,也就是说这些内存只是被jvm标记,没有实际使用,包括heap--我猜

 

76×10M + 1024M = 1784M 大概为 1800M左右,和操作系统空闲差200M

 

修改测试配置,将heap大小调整到124M,命令如下:

 

D:\Java\jdk1.6.0_20\bin\java -Djava.compiler=NONE -verbose:gc -XX:+HeapDumpOnOutOfMemoryError 
	-XX:HeapDumpPath=d:/temp/java.phd   -Xms124m -Xmx124m -XX:+PrintGCDetails 
	-XX:+PrintHeapAtGC -XX:ThreadStackSize=10240  -cp D:\maven\smart\smart\target\test-classes\ 
	org.smart.threadtest.ThreadStackTest

 输出结果:

Thread 161 start...
Thread 162 start...
Thread 163 start...
Thread 164 start...
Thread 165 start...
Thread 166 start...
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
        at java.lang.Thread.start0(Native Method)
        at java.lang.Thread.start(Thread.java:597)
        at org.smart.threadtest.ThreadStackTest.main(ThreadStackTest.java:23)
Heap
 def new generation   total 38080K, used 23783K [0x2bee0000, 0x2e830000, 0x2e830000)
  eden space 33856K,  69% used [0x2bee0000, 0x2d5ef4b8, 0x2dff0000)
  from space 4224K,   4% used [0x2e410000, 0x2e43a860, 0x2e830000)
  to   space 4224K,   0% used [0x2dff0000, 0x2dff0000, 0x2e410000)
 tenured generation   total 84672K, used 0K [0x2e830000, 0x33ae0000, 0x33ae0000)
   the space 84672K,   0% used [0x2e830000, 0x2e830000, 0x2e830200, 0x33ae0000)
 compacting perm gen  total 12288K, used 369K [0x33ae0000, 0x346e0000, 0x37ae0000)
   the space 12288K,   3% used [0x33ae0000, 0x33b3c710, 0x33b3c800, 0x346e0000)
    ro space 10240K,  54% used [0x37ae0000, 0x3805b760, 0x3805b800, 0x384e0000)
    rw space 12288K,  55% used [0x384e0000, 0x38b7f798, 0x38b7f800, 0x390e0000)

 

结果显示启动了166个线程。

166×10M + 124M  = 1784M 大概为1800M,结果和上面的对比一下,可以知道TreadStack确实占用的是操作系统内存,和heapsize本身无关。

 

 

IBM JDK:

java version "1.6.0"
Java(TM) SE Runtime Environment (build pwi3260sr8fp1ifix-20100924_01(SR8 FP1+IZ81201+IZ80998+IZ83034+IZ83273))
IBM J9 VM (build 2.4, JRE 1.6.0 IBM J9 2.4 Windows 7 x86-32 jvmwi3260sr8ifx-20100923_65174 (JIT enabled, AOT enabled)
J9VM - 20100923_065174
JIT  - r9_20100401_15339ifx6
GC   - 20100308_AA)
JCL  - 20100728_01

 

测试类还是沿用上面测试SUN JDK的类:ThreadStackTest

设置环境变量:

IBM_HEAPDUMP_OUTOFMEMORY=TRUE

IBM_HEAPDUMPDIR=<directory>

IBM_JAVACOREDIR=<directory>

IBM_JAVADUMP_OUTOFMEMORY=TRUE

 

测试配置也相同:每个线程分配10240k就是10M的Stack,heap分配1024M,命令行如下:

 

D:\Java\ibm_jdk16.0\bin\java -Djava.compiler=NONE -verbose:gc,sizes -Xms1024m -Xmx1024m -Xss10240K -Xiss10240K  
-cp D:\maven\smart\smart\target\test-classes\ org.smart.threadtest.ThreadStackTest

 

输出如下:

<?xml version="1.0" ?>

<verbosegc version="20100308_AA">

  -Xmca32K        RAM 类分段增量
  -Xmco128K       ROM 类分段增量
  -Xmns0K         初始新空间大小
  -Xmnx0K         最大新空间大小
  -Xms1G          初始内存大小
  -Xmos1G         初始旧空间大小
  -Xmox1G         最大旧空间大小
  -Xmx1G          最大内存
  -Xmr16K         已记住的集合大小
  -Xmso256K       操作系统线程堆栈大小
  -Xiss10M        Java 线程堆栈初始大小
  -Xssi16K        Java 线程堆栈增量
  -Xss10M         Java 线程堆栈最大大小
<initialized>
  <attribute name="gcPolicy" value="-Xgcpolicy:optthruput" />
  <attribute name="maxHeapSize" value="0x40000000" />
  <attribute name="initialHeapSize" value="0x40000000" />
  <attribute name="compressedRefs" value="false" />
  <attribute name="pageSize" value="0x1000" />
  <attribute name="requestedPageSize" value="0x1000" />
</initialized>

Thread 0 start...
Thread 1 start...
Thread 2 start...
......
......
Thread 81 start...
Thread 82 start...
<sys id="1" timestamp="Jan 26 14:35:08 2011" intervalms="0.000">
  <time exclusiveaccessms="0.050" meanexclusiveaccessms="0.050" threads="0" lastthreadtid="0x41F92300" />
  <refs soft="6" weak="4" phantom="0" dynamicSoftReferenceThreshold="32" maxSoftReferenceThreshold="32" />
  <tenured freebytes="1072352000" totalbytes="1073741824" percent="99" >
    <soa freebytes="1018665216" totalbytes="1020055040" percent="99" />
    <loa freebytes="53686784" totalbytes="53686784" percent="100" />
  </tenured>
  <gc type="global" id="1" totalid="1" intervalms="0.000">
    <classunloading classloaders="0" classes="0" timevmquiescems="0.000" timetakenms="0.087" />
    <finalization objectsqueued="2" />
    <timesms mark="13.684" sweep="4.105" compact="0.000" total="17.950" />
    <tenured freebytes="1072855776" totalbytes="1073741824" percent="99" >
      <soa freebytes="1019168992" totalbytes="1020055040" percent="99" />
      <loa freebytes="53686784" totalbytes="53686784" percent="100" />
    </tenured>
  </gc>
  <tenured freebytes="1072855776" totalbytes="1073741824" percent="99" >
    <soa freebytes="1019168992" totalbytes="1020055040" percent="99" />
    <loa freebytes="53686784" totalbytes="53686784" percent="100" />
  </tenured>
  <refs soft="1" weak="4" phantom="0" dynamicSoftReferenceThreshold="31" maxSoftReferenceThreshold="32" />
  <time totalms="18.043" />
</sys>

JVMDUMP006I 正在处理转储事件“systhrow”,详细信息“java/lang/OutOfMemoryError”- 请稍候。
JVMDUMP032I JVM 使用“D:\IBM SOFT\IBM JVM\jca401\Snap.20110126.143508.15788.0001.trc”来请求 Snap 转储以响应事件
JVMDUMP010I Snap 转储已写入 D:\IBM SOFT\IBM JVM\jca401\Snap.20110126.143508.15788.0001.trc
JVMDUMP032I JVM 使用“d:\temp\heapdump.20110126.143508.15788.0002.phd”来请求 Heap 转储以响应事件
JVMDUMP010I Heap 转储已写入 d:\temp\heapdump.20110126.143508.15788.0002.phd
JVMDUMP032I JVM 使用“d:\temp\javacore.20110126.143508.15788.0003.txt”来请求 Java 转储以响应事件
JVMDUMP010I Java 转储已写入 d:\temp\javacore.20110126.143508.15788.0003.txt
JVMDUMP013I 已处理转储事件“systhrow”,详细信息:“java/lang/OutOfMemoryError”。
Exception in thread "main" java.lang.OutOfMemoryError: 未能分配 JNIEnv
        at java.lang.Thread.startImpl(Native Method)
        at java.lang.Thread.start(Thread.java:887)
        at org.smart.threadtest.ThreadStackTest.main(ThreadStackTest.java:23)

 

 

82×10M + 1024M = 1844M,IBM JDK能够产生heapdump和core文件,这点比SUN JDK强,core文件内容如下:

 

 

 

同样将heapsize调整为124M测试,命令如下:

 

D:\Java\ibm_jdk16.0\bin\java -Djava.compiler=NONE -verbose:gc,sizes -Xms124m -Xmx124m -Xss10240K -Xiss10240K  
-cp D:\maven\smart\smart\target\test-classes\ org.smart.threadtest.ThreadStackTest

 

输出如下:

<?xml version="1.0" ?>

<verbosegc version="20100308_AA">

  -Xmca32K        RAM 类分段增量
  -Xmco128K       ROM 类分段增量
  -Xmns0K         初始新空间大小
  -Xmnx0K         最大新空间大小
  -Xms124M        初始内存大小
  -Xmos124M       初始旧空间大小
  -Xmox124M       最大旧空间大小
  -Xmx124M        最大内存
  -Xmr16K         已记住的集合大小
  -Xmso256K       操作系统线程堆栈大小
  -Xiss10M        Java 线程堆栈初始大小
  -Xssi16K        Java 线程堆栈增量
  -Xss10M         Java 线程堆栈最大大小
<initialized>
  <attribute name="gcPolicy" value="-Xgcpolicy:optthruput" />
  <attribute name="maxHeapSize" value="0x7c00000" />
  <attribute name="initialHeapSize" value="0x7c00000" />
  <attribute name="compressedRefs" value="false" />
  <attribute name="pageSize" value="0x1000" />
  <attribute name="requestedPageSize" value="0x1000" />
</initialized>

Thread 0 start...
Thread 1 start...
......
......
Thread 169 start...
Thread 170 start...
<sys id="1" timestamp="Jan 26 14:42:31 2011" intervalms="0.000">
  <time exclusiveaccessms="0.079" meanexclusiveaccessms="0.079" threads="0" lastthreadtid="0x0030EC00" />
  <refs soft="6" weak="4" phantom="0" dynamicSoftReferenceThreshold="32" maxSoftReferenceThreshold="32" />
  <tenured freebytes="128516864" totalbytes="130023424" percent="98" >
    <soa freebytes="122016000" totalbytes="123522560" percent="98" />
    <loa freebytes="6500864" totalbytes="6500864" percent="100" />
  </tenured>
  <gc type="global" id="1" totalid="1" intervalms="0.000">
    <classunloading classloaders="0" classes="0" timevmquiescems="0.000" timetakenms="0.061" />
    <finalization objectsqueued="2" />
    <timesms mark="2.994" sweep="0.623" compact="0.000" total="3.768" />
    <tenured freebytes="129057424" totalbytes="130023424" percent="99" >
      <soa freebytes="122556560" totalbytes="123522560" percent="99" />
      <loa freebytes="6500864" totalbytes="6500864" percent="100" />
    </tenured>
  </gc>
  <tenured freebytes="129057424" totalbytes="130023424" percent="99" >
    <soa freebytes="122556560" totalbytes="123522560" percent="99" />
    <loa freebytes="6500864" totalbytes="6500864" percent="100" />
  </tenured>
  <refs soft="1" weak="4" phantom="0" dynamicSoftReferenceThreshold="31" maxSoftReferenceThreshold="32" />
  <time totalms="3.899" />
</sys>

JVMDUMP006I 正在处理转储事件“systhrow”,详细信息“java/lang/OutOfMemoryError”- 请稍候。
JVMDUMP032I JVM 使用“D:\IBM SOFT\IBM JVM\jca401\Snap.20110126.144231.10464.0001.trc”来请求 Snap 转储以响应事件
JVMDUMP010I Snap 转储已写入 D:\IBM SOFT\IBM JVM\jca401\Snap.20110126.144231.10464.0001.trc
JVMDUMP032I JVM 使用“d:\temp\heapdump.20110126.144231.10464.0002.phd”来请求 Heap 转储以响应事件
JVMDUMP010I Heap 转储已写入 d:\temp\heapdump.20110126.144231.10464.0002.phd
JVMDUMP032I JVM 使用“d:\temp\javacore.20110126.144231.10464.0003.txt”来请求 Java 转储以响应事件
JVMDUMP010I Java 转储已写入 d:\temp\javacore.20110126.144231.10464.0003.txt
JVMDUMP013I 已处理转储事件“systhrow”,详细信息:“java/lang/OutOfMemoryError”。
Exception in thread "main" java.lang.OutOfMemoryError: 未能分配 JNIEnv
        at java.lang.Thread.startImpl(Native Method)
        at java.lang.Thread.start(Thread.java:887)
        at org.smart.threadtest.ThreadStackTest.main(ThreadStackTest.java:23)
</verbosegc>

 

 

170×10M + 124M = 1824M  ,与上面内存数量基本一致。

 

IBM JDK表现与SUN的JDK基本一致,都是占用操作系统内存。

  

 

<think>嗯,用户让我详细介绍Java内存模型(JMM),我得先回忆一下相关知识。Java内存模型主要跟多线程编程有关,对吧?用户之前已经问过多线程与并发的问题,现在可能想深入理解底层的原理。他们可能在学习多线程时遇到了内存可见性或者同步的问题,所以需要了解JMM如何解决这些问题。 首先,我需要确定JMM的定义和作用。JMM是Java虚拟机规范的一部分,用来定义线程如何与主内存交互,确保在不同平台上的内存访问一致性。然后要讲清楚JMM的三大特性:原子性、可见性、有序性。用户可能对这些概念有点模糊,需要用例子说明。 接下来是主内存和工作内存的概念。主内存是所有线程共享的,而每个线程有自己的工作内存,存储变量的副本。这里需要解释线程如何从主内存读取数据到工作内存,以及如何写回,这可能涉及到volatile关键字的作用。 然后要讲happens-before原则,这是JMM的核心规则,用来确定操作之间的可见性。需要列出几个重要的happens-before规则,比如程序顺序规则、volatile变量规则、锁规则等,并举例说明它们如何保证可见性。 内存屏障也是一个关键点,但用户可能对底层实现不太熟悉,所以需要简化解释,说明它是如何禁止指令重排序的。volatile的实现原理需要详细说明,包括读写操作的内存屏障插入位置,以及如何保证可见性和有序性。 锁的同步机制和JMM的关系也很重要,要说明synchronized和Lock如何保证原子性和可见性,可能和happens-before规则中的锁规则联系起来。原子类比如AtomicInteger,它们的实现基于CAS,这里需要提到CAS的操作和底层CPU指令,比如cmpxchg。 最后,实际应用中的问题,比如单例模式的双重检查锁问题,需要结合volatile来避免指令重排序导致的错误。这部分通过例子来说明JMM的重要性,以及如何正确应用知识来解决实际问题。 用户可能的疑问点在于指令重排序和内存屏障的具体机制,是否需要深入底层?但考虑到用户可能是中级开发者,应该保持解释在适度的技术层面,避免过于复杂的细节。同时,需要检查是否有遗漏的重要概念,比如final字段的内存语义,或者JMM与硬件内存模型的关系,但可能这些内容对于概述来说太深入,可以适当提到但不展开。 总结一下,结构应该是先定义JMM,再讲核心概念,接着是happens-before规则,然后内存屏障和volatile实现,最后实际应用案例。这样用户能由浅入深地理解JMM的作用和重要性,同时结合实际问题来巩固知识。</think>以下是Java内存模型(JMM, Java Memory Model)的系统解析,结合其设计原理、核心规则和实际影响展开说明: --- ### 一、JMM的定义与目标 **核心作用**:规范多线程环境下,线程如何通过内存交互实现**可见性**、**有序性**和**原子性**,屏蔽不同硬件/操作系统内存访问差异,为开发者提供一致的内存语义保证。 --- ### 二、核心概念 #### 1. **主内存与工作内存** - **主内存(Main Memory)**:所有线程共享的内存区域,存储变量原始值 - **工作内存(Working Memory)**:每个线程私有的内存副本,存储线程操作所需变量的拷贝 $$ \text{线程操作流程}:\text{主内存} \rightleftharpoons \text{工作内存} \rightarrow \text{CPU寄存器} $$ #### 2. **内存间交互操作** - **8种原子操作**(Java虚拟机规范定义): ```markdown - lock/unlock(锁定主内存变量) - read/load(主内存 → 工作内存) - use/assign(工作内存使用/赋值) - store/write(工作内存 → 主内存) ``` --- ### 三、关键特性保障 #### 1. **原子性(Atomicity)** - JMM直接保证的原子操作:`read`/`load`/`use`/`assign`/`store`/`write` - **扩展原子性**:通过`synchronized`/`Lock`/`原子类`实现代码块级原子操作 #### 2. **可见性(Visibility)** - **实现机制**: - `volatile`变量:强制读写直接穿透缓存到主内存 - `synchronized`:解锁前将变量同步回主内存 - `final`字段:构造器完成后的可见性保证 #### 3. **有序性(Ordering)** - **指令重排序问题**:编译器/处理器优化可能打乱代码执行顺序 - **控制手段**: - `volatile`禁止重排序(内存屏障) - `happens-before`规则约束 --- ### 四、happens-before规则 #### 1. **定义** 若操作A **happens-before** 操作B,则A的结果对B可见,且A的执行顺序排在B之前。 #### 2. **核心规则列表** ```markdown - **程序顺序规则**:单线程中的顺序执行 - **volatile规则**:volatile写happens-before后续读 - **锁规则**:解锁happens-before后续加锁 - **线程启动规则**:Thread.start()前的操作happens-before新线程所有操作 - **传递性**:若A→B且B→C,则A→C ``` #### 3. **示例说明** ```java // volatile写操作happens-before读操作 volatile boolean flag = false; // 线程1 flag = true; // 写操作(happens-before后续读) // 线程2 if (flag) { // 读操作能看到线程1的修改 System.out.println("Flag is true"); } ``` --- ### 五、内存屏障(Memory Barrier) #### 1. **作用** 禁止特定类型的指令重排序,强制刷新内存数据 #### 2. **JVM实现类型** | 屏障类型 | 作用 | |--------------------|-----------------------------| | LoadLoad屏障 | 禁止读操作重排序 | | StoreStore屏障 | 禁止写操作重排序 | | LoadStore屏障 | 禁止读后写重排序 | | StoreLoad屏障 | 禁止写后读重排序(开销最大) | #### 3. **volatile实现原理** ```markdown - **写操作**:插入StoreStore + StoreLoad屏障 - **读操作**:插入LoadLoad + LoadStore屏障 ``` --- ### 六、JMM与并发编程 #### 1. **双重检查锁定(DCL)问题** ```java // 错误实现(未加volatile) class Singleton { private static Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); // 可能发生指令重排序 } } } return instance; } } // 正确实现:添加volatile修饰符 private static volatile Singleton instance; ``` #### 2. **final字段的特殊处理** - 构造函数内对final字段的写入,happens-before任何其他线程读取该对象引用 --- ### 七、JMM与硬件内存模型 1. **关系映射** - JMM抽象层:定义程序员可见的保证 - 硬件实现差异:x86(强内存模型) vs. ARM(弱内存模型- JVM通过插入内存屏障适配不同硬件 2. **缓存一致性协议** - MESI协议:CPU缓存行状态管理(Modified/Exclusive/Shared/Invalid) --- ### 八、诊断工具 1. **JMM验证工具**:JCStress(测试并发行为) 2. **内存分析工具**:Eclipse Memory Analyzer 3. **调试命令**:`jconsole`查看线程内存状态 --- 通过理解JMM,开发者可以更精准地编写线程安全代码,避免因内存可见性、指令重排序等导致的隐蔽并发问题。实际开发中应优先使用`java.util.concurrent`包提供的高层抽象工具,而非直接操作底层内存模型
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值