JVM内存模型

Sun在2006年将Oracle JDK开源最终形成了Open JDK项目,两者在绝大部分的代码上都保持一致。JVM的内存模型是围绕着原子性(操作有且仅有一个结果)、可见性(racing thread读取变量的值永远是最新的)和有序性(指令的执行时有序并且符合happen-before原则的)这三个特性建立的,运行时数据区构成如下:

线程隔离区域:虚拟机栈(Java方法执行时的栈帧,存储本地变量和外部引用),本地方法栈(Native Java方法执行时的栈帧)和程序计数器(保存当前线程的下一条执行地址以及线程恢复地址)。

线程共享区域:方法区(存储被加载的Java类信息,final和static变量,JIT编译代码)和堆(存放所有对象实例)。

前者的空间耗用量在编译时期就是确定的,当然也可能出现stackOverFlowError错误;后者主要由于堆的存在,所以空间耗用量只有在运行时才能确定,可能出现outOfMemoryError错误,也是GC算法主要的作用对象,使用heap的时候如果每个线程都去申请分配空间,则空间分配器可能成为并发的瓶颈,因此JDK引入了线程私有分配缓冲区(Thread Local Allocation Buffer)的概念,线程在创建的时候就会一次性向heap申请一整块空间用于自身对象的创建,运行时如果这块空间不够则会动态申请更多的空间。

由于CPU的计算速度远远超过内存的读写速度,因此内存中的值一般需要提前预载到CPU高速缓存内等待使用;但这样的设计在多CPU架构下会引入高速缓存不一致的问题,也就是不同CPU操作的内存的值可能不是基于最新的值;解决这个问题先后出现了两种方案,一种是总线锁机制,也就是一个CPU在使用某个内存值的时候会阻塞其他CPU对这个内存值的使用,这样的设计极大降低了多CPU带来的性能提升;另一种是缓存一致性协议(Intel的MESI协议),核心思想是当CPU写数据时如果发现变量是多CPU共享变量,则会发送signal通知其他CPU他们持有的变量值已经过期,需要重新从内存中读取。

Volatile是JVM提供的最轻量级的同步机制,当一个变量被volatile修饰后,racing thread在读取volatile变量的时候会先将main内存的值同步到工作内存,然后再进行操作,同时会禁止JVM优化策略带来的指令重排(Instruction Reorder),因此能保证总是最新的值,同样更新变量的值也会立即写回main内存,以保证变量在多个线程之间的可见性;当前thread对普通变量的修改发生在Thread工作内存中,需要到达某个同步点才会写入main内存中,因此不能保证其他线程的可见性。但这并不意味着volatile能解决数据一致性问题,racing thread在修改volatile值的时候并不能保证操作的原子性,因为操作过程中有可能是基于过期的数据进行操作。

JVM内存模型只能保证最基本的读取和赋值具有原子性(32位系统下,64bit数据的读写和赋值有可能不能保证原子性),volatile可以保证单个变量值的可见性和有序性;如果需要更大范围的原子性/可见性/有序性,比如一系列的代码操作,则需要引入Lock或者Synchronized机制。一个利用volatile和synchronized保证单例的sample code,可以在保证多线程安全的前提下最大程度保证并行效率。

 class App1 {
private volatile static App1 instance = null; private App1() {
} public static App1 getInstance() {
if (instance == null) {
synchronized (App1.class) {
if (instance == null) {
instance = new App1();
}
}
}
return instance;
}
}

GC回收算法策略

GC算法如何判断对象是否可被回收,引用计数算法和可达性分析(Reachability Analysis)算法,前者通过计算对象是否至少被一个其他对象引用,无法解决循环引用的问题;后者通过一系列称为GC roots的对象作为起始点(如Object对象),从这些节点开始往下遍历,当一个对象没有被任何基于GC roots的引用链连接时,可以判定这些对象是可以被回收的,可以解决循环引用问题。GC roots对象包含:方法调用栈中引用的对象,方法区中类静态变量和常量变量引用的对象。

当前商业JVM的GC算法一般都会选择分代收集算法(Generational Collection)。由于总有一部分对象需要长期存在,一部分对象仅存在很短时间,所以首先将可用heap划分成两个部分Senior和Junior,Junior又划分成Survivor1和Survivor2;初始的时候仅在Survivor1上分配内存,一段时间后将Survivor1上存活的对象规整复制到Survivor2上,这时候使用Survivor2分配内存,然后一段时间后将Survivor2上存活的对象规整复制到Survivor1上,如此循环反复;经过几次循环之后将还存活的已经被复制过多次的对象复制到Senior上,表示长期保存。一段时间后对于Senior也可以经过标记,回收,规整等GC回收策略。

GC一般分成minor GC和full GC,前者代价较小,一般仅发生在Junior区,后者可能导致整个JVM停顿,是对Senior的规整。GC的执行时间点也会选择安全点(SafePoint),也就是选择即将发生如方法调用、循环执行、异常处理等重复指令或者长时间执行等待的时间点,GC同样提供安全区域(SafeRegion),也就是JVM判定在一定指令执行范围内不会有对象reference的变化。因此在构建服务的时候需要避免创建内存耗用大但生命周期短的对象(容易频繁触发GC),一台具有较好配置的物理机适合分割成多台虚拟机,这样可以最大程度减少单台虚拟机在full GC的时候对服务造成的影响。

JVM类加载策略

JVM类加载一般分为5个阶段:加载,验证,准备,解析,初始化。

加载阶段主要使用类加载器(Class Loader)通过一个类的全限定名来获取描述此类的二进制字节流,对于任意一个类,都需要加载它的Class Loader和类本身一起确立它在JVM中的唯一性,也就是说每一个Class Loader都拥有一个独立的类命名空间,即使同一个类,被不同Class Loader加载,JVM也认为是不同的类。绝大部分Java程序都会用到3种系统提供的类加载器:
启动类加载器(Bootstrap):加载$JAVA_HOME\lib
扩展类加载器(Extension):加载$JAVA_HOME\lib\ext
应用程序类加载器(Application):加载用户类路径上的类,getSystemClassLoader()可以获取

类加载器之间保持着一种称为双亲委派模型(Parents Delegation Model)的组合关系,也就是一个ClassLoader准备加载一个类的时候会优先给其父加载器发送一个加载请求,如此递归到启动类加载器,仅当父加载器指定的路径找不到对应的类,子加载器才会在自己负责的类路径上查找。这样设计最大的好处在于天然保持了一种带有优先级的类层次关系,也就是说越是核心系统提供的类越是优先使用,可以最大程度保证系统的稳定性,java.lang.ClassLoader.loadClass()中实现。当然JVM也允许用户自定义的ClassLoader,主要目的是系统提供的ClassLoader的class路径在系统启动的时候就是固定的,如果需要加载其他路径(网络class字节流)的class文件,则需要自定义ClassLoader。

验证阶段是为了确保class文件的字节流复合当前JVM的执行需求,包含文件格式验证,元数据验证,字节码验证和符号引用验证,主要验证是否满足method area的存储结构要求。准备阶段是为类static变量和全局变量在method area分配内存和设置初始值。解析阶段是JVM将常量池中的符号引用转换成直接引用。初始化阶段是为类成员变量设置初始化值,执行statick代码块,调用构造函数

常用的JDK自带工具
% jps:查看当前系统运行的JVM运行进程和对应的PID,仅显示各JVM内main()入口的主类。
% jstat -gc 95987 250 20:查看指定PID的JVM的GC收集情况,每250毫秒看一次,总计看20次
% jinfo 95987:查看并调整指定PID的JVM实例的运行参数。
% jmap 95987:生成指定PID的JVM的堆转存储快照(Heapdump)
% jhat 95987:将堆转存储快照生成HTML文件,配合jmap使用
% jstack 95987:生成JVM当前时刻的线程快照(Threaddump),也就是方法堆栈集合
% JConsole:是一款图形界面的JVM运行状态查看工具。
% VisualVM:

关于JVM内存模型,GC策略以及类加载器的思考的更多相关文章

  1. java jvm内存管理/gc策略/参数设置

    1. JVM内存管理:深入垃圾收集器与内存分配策略 http://www.iteye.com/topic/802638 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想 ...

  2. Jvm 内存模型 —— GC

    一.Jvm 原理 二.Jvm 运行时数据区( Run-Time Data Areas ) (主要是关于 non-stack 区域的详细划分) 从上图可以清楚地看到:程序计数器.Jvm 栈.本地方法栈 ...

  3. JVM内存模型以及HotSpot的GC策略

    概述 想要进一步掌握Java语言,必须要深入了解一下Java程序的运行环境.本文会对JVM的内存模型.Java内存自动管理机制.以及Oracle官方虚拟机HotSpot在GC方面的实现策略进行大概的梳 ...

  4. jvm-垃圾回收gc简介+jvm内存模型简介

    gc是jvm自动执行的,自动清除jvm内存垃圾,无须人为干涉,虽然方便了程序员的开发,但同时增加了开发人员对内存的不可控性. 1.jvm内存模型简介 jvm是在计算机系统上又虚拟出来的一个伪计算机系统 ...

  5. JVM内存模型与GC算法(简介)

    JVM内存模型如上图,需要声明一点,这是<Java虚拟机规范(Java SE 7版)>规定的内容,实际区域由各JVM自己实现,所以可能略有不同.以下对各区域进行简短说明. 1.1程序计数器 ...

  6. JVM性能调优(1) —— JVM内存模型和类加载运行机制

    一.JVM内存模型 运行一个 Java 应用程序,必须要先安装 JDK 或者 JRE 包.因为 Java 应用在编译后会变成字节码,通过字节码运行在 JVM 中,而 JVM 是 JRE 的核心组成部分 ...

  7. JVM的stack和heap,JVM内存模型,垃圾回收策略,分代收集,增量收集

    (转自:http://my.oschina.net/u/436879/blog/85478) 在JVM中,内存分为两个部分,Stack(栈)和Heap(堆),这里,我们从JVM的内存管理原理的角度来认 ...

  8. JVM内存模型以及垃圾收集策略解析

    http://xmuzyq.iteye.com/blog/599750 一 JVM内存模型 1.1 Java栈 Java栈是与每一个线程关联的,JVM在创建每一个线程的时候,会分配一定的栈空间给线程. ...

  9. JVM内存模型及垃圾收集策略解析

    一 JVM内存模型 1.1 Java栈 Java栈是与每一个线程关联的,JVM在创建每一个线程的时候,会分配一定的栈空间给线程.它主要用来存储线程执行过程中的局部变量,方法的返回值,以及方法调用上下文 ...

随机推荐

  1. iOS开发检测是否开启定位、是否允许消息推送等权限

    1.iOS开发检测是否开启定位: 需要导入: #import <CoreLocation/CoreLocation.h> 代码如下: + (void)openLocationService ...

  2. 如何在Template Codes 中能够加载所在的Project的Assembly,获取所有Type

    1.首先要获取Project对象 2.分析得到Project对象生成的bin路径,也就是$(TargetPath) 3.Assembly.LoadFromFile( binpath ) 4.asm.G ...

  3. (四)SpringBoot如何定义消息转换器

    一:添加fastjson依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>fastj ...

  4. servlet重定向到jsp后样式无法正常显示

    原因是在servlet中转发时css和图片的路径变成相对于这个servlet的相对路径而非相对于web项目的路径了. 解决办法:导入css样式和图片时把css写成动态绝对路径, 如用EL表达式表示: ...

  5. Jmeter之一个请求获取上一个请求的参数

    刚开始有这个需求,网上都是一些使用正则表达式的例子,苦于自己看不好正式的表达式,且响应结果稍微变一下,自己就不会写了,于是谷歌上各种搜,也阅读官网上文档,后来发现一个好的插件 Json path Ex ...

  6. Vue项目搭建流程 以及 目录结构构建

    Vue项目搭建流程 以及 目录结构构建 一个小的Vue项目, 基于微信浏览器的移动端, 做了这么多的练习项目, 这一次准备记录下构建的过程, 以方便以后的调高效率 环境准备 操作系统 我的 windo ...

  7. linux下使用svn创建版本库和权限管理

    linux上的svn服务端如何和本地的电脑客户端结合使用 Linux上安装SVN服务器: 第一步:检查是否已安装 # rpm -qa subversion 第二步: 通过yum命令安装svnserve ...

  8. Ubuntu 安装 node

    ubuntu安装node和npm的命令行命令: sudo apt install nodejs-legacy sudo apt install npm 最新版本安装方法 1.安装npm sudo ap ...

  9. 18.3.2从Class上获取信息(属性)

    package d18_3_1; import java.lang.reflect.Field; import java.util.Arrays; /** * 获取Class对应类所包含的属性的四个方 ...

  10. memcache的分布式配置

    public static class MemcacheHelper { private static MemcachedClient mc; static MemcacheHelper() { St ...