- JAVA代码的执行机制:首先编译为class文件,包含三个步骤:分析和输入到符号表,包括词法分析和语法分析,然后将符号输入到符号表,通常包括确定类的超类型和接口,根据需要添加默认构造器,将类中的符号输入到类本身的符号表等;注解处理,处理用户自定义annotation,生成附加代码;语意分析和生产CLASS文件。class文件中包含字节码,辅助JVM执行的结构信息、元数据、方法信息。
- 类加载机制:类加载过程包括装载、链接和初始化。装载过程中找到二进制字节码加载到JVM中,通过类的全限定名称及类的加载器完成类的加载,同样也采用这两个元素来标示一个被加载的类,接口和非数组的类,由classloader负责加载,数组类则由JVM直接创建;链接,链接过程过程负责对二进制字节码格式进行校验,初始化装载类中的静态变量及解析类中的接口、类;初始化,执行类中的静态初始化代码、构造器代码及静态属性的初始化,在如下四种情况会触发初始化执行,1)调用了new ;2)反射调用了该类的方法;3)子类调用了初始化;4)JVM启动过程中指定的初始化类。JVM的类装载通过classloader和其子类完成,分为Bootstrap ClassLoader,Extension ClassLoader及User-Defined ClassLoader。
- 类的执行机制:JVM采用invokestatic,invokevirtual,invokeinterface,invokespecial四个指令来执行不同的方法调用。invokestatic调用static方法,invokevirtual调用对象实例的方法,invokeinterface调用接口的方法,invokespecial调用private方法和编译源码后生成的<init>方法,此方法为对象实例化时的初始化方法。
- JDK基于栈的体系结构来执行字节码,基于栈方式的好处为代码紧凑,体积小。1)指令解释执行,执行方式为冯诺依曼体系中的FDX循环方式,即获取下一条指令,解码并分派,然后执行。实现FDX循环时有 switch-threading、token-threading、direct-threading、subroutine-threading、inline-threading等多种方式。JDK重点为编译成机器码,并没有在解释器上做太复杂的处理,因此采用了token-threading方式。为了解释执行能更加高效,JDK做了一些其他优化,主要有栈顶缓存和部分栈帧共享。
- 编译执行:解释执行的效率较低,为提升代码执行性能,JDK提供将字节码编译为机器码的支持,编译在运行时进行,称为JIT编译器。JDK在执行过程中对执行频率高的代码进行编译,对执行频率不高的代码继续采用解释的方式,因此SUN JDK又称为Hotspot VM 。在编译上JDK提供了两种模式,client compiler 和 server compiler。client compiler又称C1,较为轻量级,只能做少量性能开销比高的优化,占内存少,适合桌面交互式应用。在其它方面优化主要有:方法内联,去虚拟化,冗余削除等。server compiler又称为C2,较为重量级,C2采用大量的传统编译优化技巧来进行优化,占用内存会多一些,适合于服务器应用。基于逃逸分析,C2编译时会做标量替换(去除对象引用,直接使用变量)、栈上分配(如果对象未逃逸,C2会直接在栈上创建对象,而不是JVM内)和同步削除(如果对象未逃逸,则没有同步的必要)等优化。
- 内存空间:JDK将内存空间划为方法区、堆、本地方法栈、PC寄存器及JVM方法栈。方法区,存放了要加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息。方法区域也是全局共享的,当方法区域内存超过大小时,会抛出OutOfMemory错误信息。SUN JDK中这块区域对应Permanet Generation,又称持久代,最小默认值为16MB,最大值为64MB,可通过-XX:PermSize 及 -XX:MaxPermSize来指定最小值和最大值。堆,存储对象实例及数组值。32位系统上最大为2GB,64位无限制,可通过-Xms和-Xmx来控制。为避免在运行时频繁调整heap的大小,通常设置为一样大小。为了让内存回收更高效,SUN JDK从1.2开始对堆采用分代管理方式。新生代,大多数JAVA程序中新建对象从新生代分配内存,可通过-Xmn参数指定大小。旧生代,用于存放新生代中经过多次垃圾回收仍然存活的对象,例如缓存对象。旧生代占用内存的大小为-Xmx减去-Xmn对应的值。本地方法栈,用于支持native方法的执行,在SUN JDK的实现中本地方法栈和JVM方法栈是同一个。PC寄存器和JVM方法栈,每个线程均会创建PC寄存器和JVM方法栈。当JVM方法栈空间不足时,会抛出StackOverflowError的错误,在SUN JDK中可以通过-Xss来指定其大小。
- 内存分配:对象所占用的内存主要从堆上进行分配,堆是所有线程共享的,因此在堆上分配内存时需要进行加锁,这导致创建对象开销比较大。当堆上空间不足时,会触发GC,如果GC后空间仍然不足,则抛出OutOfMemory 错误信息。SUN JDK为了提升内存分配效率,会为每个新创建的线程在新生代的Eden Space上分配一块独立的空间,称为TLAB ,可通过-XX:TLABWasteTargetPercent来设置可占用Eden Space的百分比。通常多个小的对象比大对象分配起来更加高效。
- 内存回收:JVM通过GC来回收堆和方法区中的内存,通常采用收集器的方式实现GC,主要的收集器有引用计数收集器和跟踪收集器。引用计数收集器,引用计数收集器采用的为分散式的管理方式,通过计数器记录对象是否被引用。当计数器为0时,即可进行回收。SUN JDK在实现GC时未采用这种方式。跟踪收集器,跟踪收集器采用的为集中式管理方式,全局记录数据的引用状态,基于一定条件触发,执行时需要从根集合扫描对象的引用关系,这可能会造成程序的暂停。主要有复制、标记-清除和标记-压缩三种实现算法。
- SUN JDK的GC:SUN JDK将JVM堆划分为了新生代和旧生代,并基于新生代和旧生代对象存活的时间特征提供了不同的GC实现。新生代可用GC,SUN JDK认为新生代中的对象存活时间较短,因此选择了基于复制算法来实现新生代对象的回收,提供了串行GC、并行回收GC和并行GC三种方式回收新生代对象内存,可称为Minor GC。旧生代和持久代可用的GC,JDK提供了串行、并行及并发三种GC来对旧生代及持久代对象所占用的内存进行回收。Full GC ,除CMS GC外,当旧生代和持久化触发GC时,其实是对新生代、旧生代及持久代都进行GC,又称为Full GC。调用System.gc、旧生代空间不足,Permanet Generation空间满,CMS GC时出现 promotion failed 和 concurrent mode failure ,统计得到Minor GC 晋升到旧生代的平均大小大于旧生代的剩余空间,都能触发Full GC。SUN JDK提供了两种简单的方式来帮助选在GC。吞吐量优先,吞吐量是指GC 所消耗的时间占用应用运行总时间的百分比,可以通过JVM参数中 指定-XX:GCTimeRatio=n来使用此策略。暂停时间优先,暂停时间是指每次GC造成应用的停顿时间,默认不启用这个策略,可通过指定-XX:MaxGCPauseMillis=n来使用。当两个参数都配置时,先满足暂停时间优先策略,再满足吞吐量优先策略。这两个策略要通过收集运行信息做动态调整,实现难度较高。大多数情况下只须配置JVM堆的大小及持久代大小就可以让GC符合应用的要求运行。Garbage First ,SUN JDK 6 UPDATE14以上版本增加了一种称为Garbage First 的GC ,简称G1。
- JVM内存状况查看方法和分析工具:输出GC日志,GC Portal ,JConsole,JVisualVM,JMAP,JHAT,JStat,Eclipse Memory Analyzer。
- JVM线程资源同步及交互机制:线程同步机制,JVM把对于working memory(通常是操作数栈)的操作分为use\assign、load、store、lock和unlock,对于working memory 的操作指令由线程发出,对于main memory的操作分别为 read、write、lock和unlock;对于main memory的操作指令由线程执行引擎发出。为了避免资源操作的脏数据问题,JVM提供了synchronized关键字、volatile关键字和 lock/unlock机制。volatile机制有所不同,它仅用于控制线程中对象的可见性,并不能保证此对象上操作的原子性。线程交互机制,JVM提供了wait/notify/notifyAll 方式来支持资源交互的需求。
JDK5.0的版本中增加了并发包,提供了更多方式来支持线程间的交互,如Semphore的 acquire和release,Condition的await和signal,CountDownLatch的await和countDown等。
- 线程状态分析:JVM把线程分为几种不同的状态,并根据状态放入不同的sets中来进行调度。线程创建完毕后进入new状态,调用线程的start方法线程进入Runnable状态,放入JVM的可运行队列中,等待获取CPU的执行权。为了跟踪运行时JVM中线程的状况,有一些不错的工具对于运行时判断什么操作是耗系统资源的,哪些操作出现了锁竞争或死锁现象等有很大的帮助。如, kill -3[pid] ,jstack,JCONSOLE ,ThreadXMBean,TDA,TOP命令 + ThreadDump等。