[转]从JVM角度看线程安全与垃圾收集
线程安全
Java内存模型中,程序(进程)拥有一块内存空间,可以被所有的线程共享,即MainMemory(主内存);而每个线程又有一块独立的内存空间,即WorkingMemory(工作内存)。普通情况下,当线程需要对某一共享变量进行修改时,通常会进行如下的过程:
1. 从主内存中拷贝变量的一份副本,并装载到工作内存中;
2. 在工作内存中执行代码,修改副本的值;
3. 用工作内存中的副本值更新主存中的相关变量值。
如下图:
所谓“线程安全”,即多个线程同时执行同一段代码时,不会出现不确定的或者与单线程条件下不一致的结果。通常,下列三种条件居其一的并发访问被JVM认为是线程安全的:
1. 有final关键字修饰且已被赋值;
2. 有volatile关键字修饰;
3. 有锁保护(synchronized、ReentrantLock等)。
第1点显而易见,不再赘述。
volatile关键字的作用是告知JVM:它所修饰的域的原子操作都不需要经过线程的工作内存,而直接在主内存中进行修改。这样就保证了线程从主内存中读取(read)它的值的时候,总是最新的。但是,Java中的运算极少是原子的,即便是像++ 这样的一元运算符或者+= 这样的二元运算符都不是原子的,因此volatile关键字修饰的域在多线程环境下依然可能会读写出“脏”数据:它只保证每一步原子操作的线程安全,但不保证整个操作过程的线程安全。也因此,volatile主要被用于变量只有原子操作的场合,如赋值、移位等。
锁,无论是显式(ReentrantLock)还是隐式(synchronized)的同步锁,或是信号量(Semaphore),抑或是阻塞队列(BlockingQueue),还是其它的同步措施(CyclicBarrier、CountDownLatch、wait¬ify等),它们的作用都是一样的,就是保证一个共享变量的副本进入到某个线程的工作内存之后,该共享变量就不再会被其它线程访问到,直到前述过程的第3步执行完成。
线程在有同步锁的情况下访问共享变量的过程如下:
1. 获取同步锁
2. 清空工作内存
3. 从主内存将拷贝变量副本,并装载到工作内存
4. 对副本执行代码
5. 用副本数据更新主内存中的相关变量
6. 释放同步锁
通常,没有获得同步锁的线程将被阻塞,直到它竞争到同步锁。这样,没有获得同步锁的线程不仅不能访问数据,甚至都不能继续运行,于是强迫性地保证了线程安全。也因此,线程安全代码的开销要大于不安全的代码,同步锁的开销也要大于volatile。
垃圾收集
上述线程运行的过程实际上是JVM的思维模型。JVM真正的逻辑内存模型如下图所示:
其中:
线程的工作内存位于JVM栈中。线程中的每个方法在运行的时候都会在栈中申请一个帧(frame),用来保存变量表、操作集、动态链接、出入口等信息。每个方法从调用到返回,就是它的帧在线程栈中从入栈到出栈的过程。
程序的主内存位于JVM堆。程序中的每一个实例或数据域都会被分配到堆中,并由所有线程共享(如果有权限的话)。线程从主内存中拷贝变量副本的过程,就是从堆中读取(read)该变量的数据,然后在自己的栈中创建(load)一个新的实例。可见,load之后,栈中的操作就与堆中的变量没有关系了。
线程PC用于寄存每个线程当前执行到的机器指令。本地方法栈用于调用JNI方法,并在方法调用结束后销毁。
Java的垃圾收集就是针对堆中的对象进行的。堆中按照对象的生命周期长短分为如下图的几个区域:
其中:
新生代(NewGeneration)分为两个区:Eden和Survivor;而Survivor区又分为了等大的两个区:S0和S1(或From和To)。新创建(new)的对象都会分配到Eden区中。当Eden区满时,会触发一次MinorGC,JVM会将Eden中存活的对象进行标记并拷贝到S0中,同时回收所有无效的对象。当S0满时,S0中存活的对象被拷贝到S1区,无效的对象被回收。当S1也满时,意味着整个新生代都满了,此时将触发一次MajorGC,新生代中部分存活的对象被标记后拷贝到老年代区中,无效的对象被回收,同时新生代中剩余的对象按一定的规则分配到Eden、S0、S1中。
老年代(OldGeneration)中保存的,是多次GC之后仍然存活的对象。在老年代区中,如果剩余的任一连续内存空间都不足以容纳一个对象时,需要对老年代区进行碎片压缩,此时程序将挂起。当老年代满时,将触发一次FullGC,它先对新生代发起一次MinorGC,再在老年代区标记清除无效的对象。
持久代(PermanentGeneration)也就是前图中堆内部的方法区。这里存放了程序中每个Class和ClassLoader以及一些常量的数据信息。其中又包含了常量池,用于保存常量数据、方法签名等在编译时就决定的不变量。持久代中通常极少、甚至不发生GC。
GC中的算法参见如下的链接:
http://www.cnblogs.com/aigongsi/archive/2012/04/06/2434771.html
http://www.cnblogs.com/aigongsi/archive/2012/04/13/2446166.html
转自:
http://blog.csdn.net/sadfishsc/article/details/10325879
[转]从JVM角度看线程安全与垃圾收集的更多相关文章
- 从JDK源码角度看线程池原理
"池"技术对我们来说是非常熟悉的一个概念,它的引入是为了在某些场景下提高系统某些关键节点性能,最典型的例子就是数据库连接池,JDBC是一种服务供应接口(SPI),具体的数据库连接实 ...
- 从JVM角度看Java多态
首先,明确一下,Java多态的三个必要条件: 1. 继承 2. 子类重写父类方法 3. 父类引用指向子类对象 然后看一个例子 package test.xing; class Father{ prot ...
- 从JDK源码角度看线程的阻塞和唤醒
目前在Java语言层面能实现阻塞唤醒的方式一共有三种:suspend与resume组合.wait与notify组合.park与unpark组合.其中suspend与resume因为存在无法解决的竟态问 ...
- 从jvm角度看懂类初始化、方法重写、重载。
类初始化 在讲类的初始化之前,我们先来大概了解一下类的声明周期.如下图 类的声明周期可以分为7个阶段,但今天我们只讲初始化阶段.我们我觉得出来使用和卸载阶段外,初始化阶段是最贴近我们平时学的,也是笔试 ...
- 从java字节码角度看线程安全性问题
先看代码: package com.roocon.thread.t3; public class Sequence { private int value; public int getNext(){ ...
- 从JVM角度看i++ 与++i
1.i++和++i的问题 反编译结果为 Code: 0: iconst_1 1: istore_1 2: iinc 1, 1 //这个个指令,把局部变量1,也就是i,增加1,这 ...
- 从JDK源码角度看Short
概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然 ...
- 从JDK源码角度看Byte
Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型 ...
- 从JDK源码角度看Object
Java的Object是所有其他类的父类,从继承的层次来看它就是最顶层根,所以它也是唯一一个没有父类的类.它包含了对象常用的一些方法,比如getClass.hashCode.equals.clone. ...
随机推荐
- mvc url 伪静态
WebConfig配置 <system.webServer> <validation validateIntegratedModeConfiguration="false& ...
- BaaS模式的开发思路
如今,有一种BaaS服务(后端即服务)的模式,即无需编程即可生成后端应用及相应API. Drupal这套CMS下,有一款插件Services,可以在线直接自定义各种格式如json.xmlrpc.web ...
- myeclipse中导入的js文件报错(出现红叉叉,提示语法错误)
为了做一个页面特效,导入了一个jquery文件,怎想,myeclipse竟然报错说是语法错误,但是这个js文件我是从官网上下载的,不应该出错才对,百度谷歌之后终于找到了解决办法: 选中报错的js文件, ...
- SOA (面向服务的体系结构)
面向服务的体系结构是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来.接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台.操作系统和编程语言 ...
- objective-c高德地图时时定位
这篇随笔是对上一遍servlet接口的实现. 一.项目集成高德地图 应为我这个项目使用了cocopods这个第三方库管理工具,所以只需要很简单的步骤,就能将高德地图集成到项目中,如果你没使用过这工具, ...
- dubbo管理控制台安装
官网提供的下载路径好像不能用了,我也是在网上找的包,现在提供下载路径:http://pan.baidu.com/s/1nvPHQvZ 下载下来了,把他解压到wabapps->ROOT里面,先把t ...
- go的markdown解析库和session库
最近学习go,就决定做一个博客来练练手,一下是用到的一些不错的库 markdown解析库 使用markdown来写博客文章,我用的是"github.com/russross/blackfri ...
- No.014:Longest Common Prefix
问题: Write a function to find the longest common prefix string amongst an array of strings. 官方难度: Eas ...
- 免费 PSD 素材:25个全新的界面设计资源
在这篇文章中,我们给大家收集了25套全新的 UI 设计素材.这些来自优秀设计师的 PSD 源文件素材让其它的设计师们在设计用户界面原型的时候能够非常便利. 网站用户界面,移动应用程序用户界面和对设计师 ...
- Immutable.js – JavaScript 不可变数据集合
不可变数据是指一旦创建就不能被修改的数据,使得应用开发更简单,允许使用函数式编程技术,比如惰性评估.Immutable JS 提供一个惰性 Sequence,允许高效的队列方法链,类似 map 和 f ...