`
吴英贵
  • 浏览: 7391 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

JVM相关知识小结

 
阅读更多

JVM相关知识小结

前段时间本机运行的Java程序,经常出现OutOfMemoryError异常。由于种种原因,往往
是重启一下tomcat便草草了事。近来项目不是很忙,学习了一下Java虚拟机相关的资料,发现里面的
东西还真不少。比如刚才提到的异常信息,很多人一看便很可能内存溢出。但是内存溢出可能是栈
内存异常、方法区或本地方法区内存异常、也可能是堆内存移除。如果我们对相关的知识不熟悉
,仍然不能解决问题。
看完《深入理解Java虚拟机》,很多知识点结合到自己的程序有一种知其然知其所以然
的醒悟。学习的乐趣莫过于此。总结了一下,便有了此文。
运行时数据区域
1、程序计数器(Program Counter Register):是一块较小的内存空间,它可以看作是当前线程所执
行的字节码的行号指示器。
每一个Java线程都有一个程序计数器来用于保存程序执行到当前方法的哪一个指令。各线程之间计
数器互不影响,独立存储。
2、Java虚拟机栈(JavaVirtual Machine Stacks):描述的是Java方法执行的内存模型。每个方法在执行
的同时都会创建一个帧用于存储局部变量、操作数栈、动态链接、方法出口
等信息。每一个方法从调用到执行完成的过程,就对应着一个帧在栈中入栈到出栈的过程。
如果线程请求的栈深度大于虚拟机允许的深度,将抛出StackOverflowError异常。
如果栈可以动态扩展,扩展时无法申请到足够的内存,将抛出OutOfMemoryError异常。
3、本地方法栈(Native Method Stack):与栈作用类似,只不过栈为虚拟机执行Java方法服务,本地
方法栈则为虚拟机使用到的Native方法服务。
4、Java堆(Java Heap):被所有线程共享的一块内存区域,在虚拟机启动的时候创建。用于存放对
象实例,几乎所有的对象实例都在这里分配内存。Java堆可以细分为新生代和老年代;
再细致一点有Eden空间、From Survivor空间、To Survivor空间等。
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
5、方法区(Method Area):和Java堆一样,是各个线程共享的内存区域,用于存储虚拟机加载的类
信息、常量、静态变量、即时编译器编译后的代码等数据。
HotSpot虚拟机在Java堆中对象分配、布局、访问
1、对象创建过程:new->类加载->分配内存->对象头设置->初始化。
(1)指针碰撞:如果Java堆中内存是绝对规整的,所有用过的内存放一边,空闲的内存放一边,中
间放着一个指针作为分界点的指示器。分配内存就是把指针指向空闲空间那边挪动一段与对象相等
的距离。
(2)空闲列表:如果Java堆中已使用内存和空闲内存相互交错,虚拟机就必须维护一个列表,记录
上哪些内存块是可用的,分配内存就是从列表中找到一块足够大的空间划分给对象实例,并更新列
表上的记录。
2、内存布局:分为对象头、实例数据、对齐填充。
(1)对象头:包括两部分信息,a、用于存储对象自身的运行时数据,如哈希码,GC分代年龄、锁
状态标志等。b、类型指针,即对象指向它的类元数据指针,虚拟机通过这个指针来确定这个对象是
那个类的实例。
(2)实例数据:在程序代码中所定义的各种类型的字段内容。
(3)对齐填充:没有特别的含义,仅起着占位符的作用。对象大小必须是8字节的倍数,当实例数
据部分没有对齐时,就需要通过对齐填充来补全。
3、如何访问对象
(1)句柄访问:Java堆中会划分出一块内存来作为句柄池,reference中存储的就是对象的地址,句
柄中中包含的实例数据和类型数据各自的具体地址信息。
(2)直接指针:reference中存储的直接就是对象地址。

垃圾收集
垃圾收集的机制可以从以下三个问题来看:哪些内存需要回收?如何回收?什么时候回收?
1、区分垃圾对象与存活对象
(1)引用计数法:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引
用失效时,计数器就减1;计数器为0的对象就是不可能在被使用的。
(2)根搜索算法:通过一系列称为“GC Roots”的对象作为起始点,从这个节点开始向下搜索,搜
索所走过的路径称为引用链,当一个对象到GCRoots没有任何引用链相连,则对象是不可用的。
可以作为GC Roots的对象包括:虚拟机栈中引用的对象;方法区中的类静态属性引用的对象;方法
区中常量引用的对象;本地方法栈中JNI引用的对象。
2、垃圾收集算法
(1)标记-清除算法:分为“标记”和“清除”两个阶段,先标记出所有需要回收的对象,在标记
完成后统一回收所有的标记对象。
(2)复制算法:将内存划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了
,就将存活着的对象复制到另外一块上面,然后把已使用过的内存空间一次清理掉。
(3)标记-整理算法:标记过程与“标记-清除”一样,然后让所有存活的对象都向一端移动,然后
直接清理掉端边界以外的内存。
(4)分代收集算法:把Java对分为新生代和老年代。在新生代中,每次垃圾收集时都发现有大批对
象可以回收,所以选用复制算法。老年代中因为对象存活率高、使用“标记-清楚”或者“标记-清
理”算法进行回。
3、触发主GC(Garbage Collector)的条件
(1)应用程序空闲时,即没有应用线程在运行时,GC会执行。因为GC在优先级最低的线程中进行

(2)Java堆内存不足时,GC会执行。
内存分配策略
1、对象优先在新生代Eden区中分配。当Enen区没有足够空间进行分配时,虚拟机将发起一次Minor
GC。
2、大对象直接进入老年代。
3、长期存活的对象将进入老年代。
4、如果在Survivor空间中相同年龄所有对象大小总和大于Survivor空间的一半,年龄大于或等于该年
龄的对象就直接进入老年代。
类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成
可以被虚拟机直接使用的Java类型。这就是虚拟机的类加载机制。
类加载的过程
1、加载:(1)通过一个类的全限定名来获取定义此类的二进制字节流;(2)字节流所代表的静态
存储结构转化为方法区的运行时数据结构;(3)在内存中生成一个代表这个类的java.lang.Class对象
,作为方法区这个类的各种数据访问入口。
2、验证:确保Class文件的字节流中包含的信息符合虚拟机的要求,并且不会危害虚拟机自身的安全

(1)文件格式验证:验证字节流是否符合Class文件格式规范,并能被当前版本的虚拟机处理。
(2)元数据验证:对字节码描述的信息进行语义分析,以保证其描述的新符合Java语言规范的要求

(3)字节码验证:通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。
(4)符号引用验证:对类自身以外的信息进行匹配性校验。该验证发生在解析阶段。
3、准备:为类变量分配内存并设置类变量初始值,这些变量所使用的内存都在方法区中进行分配。
这里进行内存分配的仅是类变量(被static修饰的变量),而不包括实例变量。
4、解析:虚拟机将常量池内的符号引用转化为直接引用的时候。
符号引用:用一组符号来描述所引用的目标。
直接引用:直接执行目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
5、初始化:初始化阶段是执行类构造器方法的过程。什么时候初始化类?
(1)new、读取或设置一个类的静态字段、调用类的静态方法。
(2)对类进行反射调用的时候。
(3)初始化一个类,其父类还没有进行初始化,则需要先触发其父类的初始化。
(4)虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会线初始化这个主类。
类加载器的原理
从Java虚拟机角度看,只存在两种类加载器,一种是启动类加载器(BootstrapClassLoader);另一种
独立于虚拟机外部,并且全部都继承自抽象类java.lang.ClassLoader。
从Java开发人员角度看,系统提供以下3种类加载器。

类加载器的原理
从Java虚拟机角度看,只存在两种类加载器,一种是启动类加载器(BootstrapClassLoader);另一种
独立于虚拟机外部,并且全部都继承自抽象类java.lang.ClassLoader。
从Java开发人员角度看,系统提供以下3种类加载器。
1、启动类加载器(Boot ClassLoader):将存放在JAVA_HOME\lib目录中的,或者被-
Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。
2、扩展类加载器(Extension ClassLoader):负责加载JAVA_HOME\lib\ext目录的,或者被
java.ext.dirs系统变量所指定的路径中的所有类库。
3、应用程序加载器(Application ClassLoader):负责加载用户类路径(ClassPath)上所指定的类库

双亲委派模型
除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。
双亲委派模型工作过程
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派
给父类加载器去完成,每一个层次的加载器都是如此。因此所有的加载请求最终都应该传送到顶层
的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子类才会尝试自己去加
载。
字节码执行引擎
运行时栈帧结构
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。
1、局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
2、操作数栈是一个后入先出栈。Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指
的“栈”就是操作数栈。
3、动态连接。常量池中存有大量的符号引用,方法调用指令以这些符号引用作为参数。如果符号引
用在类加载阶段或者第一次使用的时候转化为直接引用,这种转换称为静态解析;如果是在运行期
转化为直接引用,则称为动态连接。
4、方法返回地址。方法的返回分为两种情况,一种是正常退出,退出后会根据方法的定义来决定是
否要传返回值给上层的调用者。一种是异常导致的方法结束,这种情况是不会传返回值给上层的调
用方法。无论是那种方式的方法结束,在退出当前方法时都会跳转到当前方法被调用的位置。如果
是正常退出,则调用者的PC计数器的值就可以作为返回地址,异常退出需要通过异常处理表来确定

方法调用
方法调用阶段唯一任务就是确定调用哪一个方法,暂时还不涉及方法内部的具体运行过程。
1、解析
类加载的解析阶段,会将一部分符号引用转化为直接引用。前提是方法在程序运行之前就有一个可
确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。解析调用主要包括静态方法和
私有方法。
2、分派与多态
(1)静态分派:所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型
应用是方法重载。
(2)根据实际类型确定方法执行版本的分派过程称为动态分派。动态分派的典型应用是方法重写。
(3)单分派多分派
单分配由于编译期已经确定方法签名,对目标方法的选择由对象的类型决定。
多分派对目标方法的选择由对象的类型以及方法签名决定。
Java内存模型
Java内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性这3个特征来建立的。Java内
存模型的主要目的是定义程序中的各个变量的访问规则,即在虚拟机中将变量存储到内存和内存中
取出变量这样的细节。此处的变量包括实例字段、静态字段和数组对象的元素,但不包括局部变量
与方法参数。
主内存与工作内存
Java内存模型规定所有的变量都存储在主内存中。每条线程还有自己的工作内存,线程的工作内存
中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进
行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程
间变量值的传递均需要通过主内存来完成。
Java内存模型定义了8种操作(lock、unlock、read、load、use、assign、store、write)来完成主内存与
工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存,如果从工作内存同步回
主内存之类的实现细节。
(1)如果一个变量从主内存复制到工作内存,顺序执行read和load。
(2)如果把变量从工作内存同步回主内存,顺序执行store和write。
Volatile
Volatile是Java虚拟机提供的最轻量级的同步机制。它具备两种特性。
(1)可见性,当一个线程修改了这个变量的值,新值对于其他线程可以立即得知的。
(2)有序性,普通变量仅仅会保证在该方法执行过程中所有依赖赋值结果的地方都能获取正确的结
果,而不能保证变量赋值操作的顺序与程序代码中执行顺序一致。
Java与线程
Java API中Thread类的所有关键方法都声明为Native。一个Native方法意味着这个方法没有使用或无法
使用平台无关的手段来实现。所以,操作系统支持怎样的线程模型,很大程度上决定了Java虚拟机
的线程是怎样映射的。
1、线程的3种实现方式
(1)使用内核线程实现:直接由操作系统内核支持的线程,这种线程由内核来完成线程的切换,内
核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。
(2)使用用户线程实现:完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。用
户线程的建立、同步、销毁和调度完全在用户态中完成。
(3)使用用户线程加轻量级进程混合实现:操作系统提供支持的轻量级进程作为用户线程和内核线
程之间的桥梁。这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要
通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。
2、Java线程调度
Java线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同式线程
调用和抢占式线程调度。
(1)协同式调度:线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通
知系统切换到另外一个线程上。
(2)抢占式调度:每个线程将由系统来分配执行时间,线程的切换不由本身来决定。Java使用这种
线程调度方式。
3、Java线程5种状态
新建、运行、无限期等待、限期等待、阻塞、结束。

参考资料
《深入理解Java虚拟机》
《Java虚拟机规范》
原文:http://blog.csdn.com/hliman/article/details/11382591

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics