理解jvm

1 为了做到一次编译到处运行,java代码首先被编译成中间语言字节码形式(如果是机器码,那么就要与特定机器绑定),通过jvm来进行解释执行。另外在解释执行过程中,会对热点代码进行jit编译优化,编译成机器码,提高运行效率。

jvm为什么不对所有的代码进行编译优化,提高性能的原因有:

  • 静态编译不能根据程序运行的实际状况来优化执行代码,例如分析判断、逃逸分析等技术。
  • 解释执行更加节省内存
  • 启动时,解释执行启动比编译后再启动速度快

2 jvm字节码执行

invokestatic是执行静态方法的指令,invokevirtual是执行实例方法的指令,invokespecial是执行编译后生成的init方法或者是private方法的指令,invokeinterface是调用接口的指令

jvm是基于栈的方式来执行代码,栈的优势是代码紧凑,体积小。线程创建后都会产生一个PC和栈,PC存放下一条要执行的指令在方法里的偏移量;栈存放了栈帧,每个方法每次调用都会产生栈帧。栈帧主要包括局部变量和操作数栈,局部变量用于存放方法的局部变量和参数,操作数栈用于存放方法执行过程中产生的中间结果。

 

3 内存模型

包括jvm方法栈(局部变量区、操作数栈、pc寄存器)、方法区(类元数据、常量池、类的静态变量)、堆(eden、survivor1、survivor2、old)、本地方法栈。

其中pc寄存器占用了cpu寄存器或是操作系统内存、jvm方法栈占用操作系统内存,并且为线程私有,内存分配非常高效。

 

4 收集器算法

引用计数法,当被引用为0的时候,回收对象。无法解决循环引用的问题,引用计数不为0,但是却无用的对象。所以直接被否。

属于GC roots的对象:虚拟机栈(栈帧的本地变量)引用的对象;方法中类静态属性引用的对象;方法区中的常量引用对象;本地方法栈引用的对象。

复制法。扫描对象集合,如果根对象(活动线程方法栈引用的对象、常量、静态变量、传到本地方法未被释放的对象引用)可达,那么进行标记。然后把标记对象复制到一块新的内存区域,再清理旧的区域,最后更新引用其对象的指针。当存活对象较少的时候,该算法的效率会比较高,缺点是需要移动对象,并且浪费一块存储空间。适合产生大量朝生夕死的对象的场景,很多web应用都具有这个特点,所以被jdk的新生代回收采用了(eden\survivor1\survivor2)

标记清除法(mark sweep)。扫描出存活对象并做标记,然后再次扫描回收未存活的对象。优点是回收效率高,缺点是会产生内存碎片,故没被采用。

标记压缩法(mark compact)。扫描出存活对象并做标记,然后再次扫描回收未存活的对象,最后把存活对象向左端空闲空间移动,并更新引用其对象的指针。优点是没有内存碎片、缺点是要移动大量对象

 

5 jdk的GC

为了分而治之,各个击破。jvm根据对象的存活特点不一样,把内存分为新生代和旧生代,针对不同代的特点采用不同的收集算法。

对于minor gc,jvm采用复制算法,把存活对象从eden移动到survivor,当年龄到时,再把对象移动到old区,也有可能是survivor空间不足的时候。(young gc会暂停应用,所以速度很重要,不宜过大)

对于full gc用于回收old、perm区的对象:

  • 串行,采用标记压缩法,单线程执行,stop the world。
  • 并行,也是采用标记压缩法,但是会把old分成好几个内存区域,并发回收所有的区域,并且从左到右扫描,只对值得压缩的区域进行压缩(即碎片多的区域)。需要的时间长,也是要stop world。
  • 并发cms(concurrent mark sweep),回收后会有内存碎片,但是通过维护free list来记录空闲的空间。当minor gc需要移动对象到old区的时候,就要去free list查询第一个放得下的内存空间,提高了minor gc的开销。cms过程较为复杂,分为四步,只有两步会stop world,其他时候都是与用户线程并行,所以可以认为他的一个并行的gc,适合在服务器使用。第一步是初始标记,停顿应用,标记gc roots能直接关联的对象,速度很快;并发标记,恢复用户线程,轮询第一步标记的对象,遍历标记其对象引用树,并记录引用的增量修改;重新标记,停顿应用,主要是对第二步产生的增量进行标记;并发收集,把未标记的对象回收。其实可以把cms的这种机制理解成先全量、后增量的做法,全量和增量的时候都要停顿应用。如果一个对象之前是不可达的,那么之后必然也不可达,一个对象如果之前可达,之后可能不可达,这个就是增量标记的原因,即第三步。cms必须配合新生代gc parnew使用。


  枚举根节点在Java语言里面,可作为GC Roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中。如果要使用可达性分析来判断内存是否可回收的,那分析工作必须在一个能保障一致性的快照中进行——这里“一致性”的意思是整个分析期间整个执行系统看起来就像被冻结在某个时间点上,不可以出现分析过程中,对象引用关系还在不断变化的情况,这点不满足的话分析结果准确性就无法保证。这点也是导致GC进行时必须“Stop The World”的其中一个重要原因,即使是号称(几乎)不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。 停顿归根结底就是为了保证数据的一致性和准确性,必须保证每一次标记完成时刻,把存活对象都给标记进去,未存活对象都没有进行标记。

 

 

full gc触发的场景有:

  • old区不足,只有当新生代对象转入、创建大对象、大数据时才会出现不足。应该尽量让对象在新生代被回收,让对象在新生代多放一段时间,减少创建短命大对象。由于一般来说young区会比old区小很多,所以当需要分配大对象时,jvm会直接把大对象放到old区,以免影响young区的正常工作。所以创建大对象的话,最好是长期使用的,否则不划算。参考 http://liuzhaodong89.iteye.com/blog/1668352      http://www.colorfuldays.org/tag/java%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/
  • perm区不足,perm区存放了class的信息,当系统需要加载的类、反射的类和调用的方法较多时,perm区可能会被占满,这种情况需要增大perm区空间,并配置cms。
  • 统计得到的minor gc的晋升对象平均大小大于旧生代剩余空间。在进行minor gc之前会先统计之前每次minor gc后晋升到old区的对象大小,如果旧生代剩余空间不足的话,那么直接full gc。

 毕玄大师的ppt http://www.docin.com/p-417999249.html

6 线程同步和交互

线程启动后,jvm会分配一个working memory(一般是操作数栈),线程中对于变量的处理都是在working memory里面处理,在处理之前和之后,都要跟main memory进行同步。所以,当多个线程并发修改一个变量的时候,如果不做同步,会出现不一致的情况。如果使用了volatile关键字(可以认为使用了volatile就用不到cpu高速缓存),那么线程对变量的操作都会直接在main memory进行,但是对于非原子操作而言,依然需要注意数据的一致性问题。

线程之间需要交互,例如生产者和消费者问题。内置的机制有object的notify和wait,还有并发框架提供的条件对象await,singal(这个使用更为灵活)。

 

7 内存
理解jvm_第1张图片

PC是内存中一块较小的区域,是线程私有的。他的作用可以看做是当前线程正在执行的字节码行号指示器。字节码解释器的工作就是通过修改PC来决定下一条所要执行的字节码指令,分支、循环、异常处理、跳转、线程恢复等功能。

直接内存,是在nio里面引入的,直接调用native方法分配堆外内存。然后通过堆内的一个DirectByteBuffer对象来引用这块内存,进行操作。他在某些io场景下面可以用来显著提高性能,减少数据在java堆和native堆中来回复制。

访问对象的两种实现,大部分虚拟机都采用第二种,因为他只需要一次指针定位就能访问到对象。但也有部分虚拟机是采用第一种,第一种的好处就是当出现垃圾收集,对象移动的时候,栈引用的指针无需改变(栈引用十分稳定),只需要改变句柄池的引用。


理解jvm_第2张图片
 
理解jvm_第3张图片

8 内存溢出

当使用动态生成class技术,如gclib的时候,需要特别注意方法区的溢出。创造条件让GC能够及时回收内存。

 

9 类的生命周期

前期过程


理解jvm_第4张图片
延迟初始化 

The Java Virtual Machine specification gives implementations flexibility in the timing of class and interface loading and linking, but strictly defines the timing of initialization. All implementations must initialize each class and interface on its first active use. An active use of a class is: 

·  The invocation of a constructor on a new instance of the class   

·  The creation of an array that has the class as its an element type   

·  The invocation of a method declared by the class (not inherited from a superclass)   

·  The use or assignment of a field declared by the class (not inherited from a superclass or superinterface), except for fields that are both static and  final , and are initialized by a compile-time constant expression 

 An active use of an interface is: 

·  The use or assignment of a field declared by the interface (not inherited from a superinterface), except for fields that are initialized by a compile-time constant expression 

 

当loading过程中出现有问题的class文件,只会在初始化的时候(也就是第一次使用的时候)报错。所以对于一些运行中才加载初始化的类文件不能随意删除,避免出现运行时错误。比如不加考虑的随便exclusive,很有可能造成致命问题。

 

The Lifetime of an Object待续

 

 

 

 

 

 

 

你可能感兴趣的:(jvm)