第一章 JVM原理

一、什么是JVM

    JVM是Java Virtual Machine(Java虚拟机)的缩写。

        JAVA语言的平台无关性如何实现:通过Java虚拟机,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。

wKiom1lcxZiDAo60AADr2hcDLtQ274.gif

上图为 JVM结构图

二、JAVA代码编译和执行过程

    JAVA代码编译和执行包含如下三个过程:


    1.  Java源码编译机制

    2.   类加载机制

    3.   类执行机制

     java源码编译机制

        最终将.java文件编译生成.class文件

     类加载机制

        JVM的类通过ClassLoader及其子类来完成类加载的过程,类的层次关系和加载顺序可以由下图来描述:

wKiom1lcyQCCD7NbAALSslgvan4053.gif

        (1)Bootstrap ClassLoader

                负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

        (2)Extension ClassLoader

                负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包

        (3)App ClassLoader

                负责记载classpath中指定的jar包及目录中class

        (4)Custom ClassLoader

                属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

    类执行机制

        JVM是基于栈的体系结构来执行class字节码的。线程创建后,都会产生程序计数器(PC)和栈(Stack),程序计数器存放下一条要执行的指令在方法内的偏移量,栈中存放一个个栈帧,每个栈帧对应着每个方法的每次调用,而栈帧又是有局部变量区和操作数栈两部分组成,局部变量区用于存放方法中的局部变量和参数,操作数栈中用于存放方法执行过程中产生的中间结果。


三、JVM内存管理和垃圾回收

    JVM栈有堆、栈、本地方法栈、方法区组成,结构如图

wKiom1lcyzXTYrzxAABJZl9oGcI720.gif

1)堆

    所有通过new创建的对象的内存都在堆中分配,堆的大小可以通过-Xmx和-Xms来控制。

        堆分为新生代老年代持久带,新生代分为EdenSurvivor(Survivor又分为fromto)

        

wKioL1lczOrCrAtpAAB_AJnDzPk932.gif

    新生代:新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例

        老年代:用于存放新生代中经过多次垃圾回收仍然存活的对象

        持久带(Permanent Space):主要存放所有已加载的类信息,方法信息,常量池等等。可通过-XX:PermSize和-XX:MaxPermSize来指定持久带初始化值和最大值。Permanent Space并不等同于方法区,只不过是Hotspot JVM用Permanent Space来实现方法区而已,有些虚拟机没有Permanent Space而用其他机制来实现方法区。

wKioL1lczfHgIQhZAAEPoE1ZJmE973.jpg

-Xmx:最大堆内存,如:-Xmx512m

-Xms:初始时堆内存,如:-Xms256m

-XX:MaxNewSize:最大年轻区内存

-XX:NewSize:初始时年轻区内存.通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%

-XX:MaxPermSize:最大持久带内存

-XX:PermSize:初始时持久带内存

-XX:+PrintGCDetails。打印 GC 信息

-XX:NewRatio 新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3

-XX:SurvivorRatio 新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10


2)栈

     每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。

    -xss:设置每个线程的堆栈大小. JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。

3)本地方法栈

    用于支持native方法的执行,存储了每个native方法调用的状态

4)方法区

    存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用持久代(Permanet Generation)来存放方法区,可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值

-------------------------------------------------------------------------------------------------------------------------------------

JVM垃圾回收

    JVM垃圾回收策略:引用计数、标记-清除、复制、标记-整理

    JVM垃圾回收器:串行收集器、并行收集器、并发收集器

四、JVM内存调优

    对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。

 

新生代设置过小会导致如下后果

1、新生代GC次数频繁,增大系统消耗

2、导致大对象直接进入老年代,占据老年代剩余空间,易诱发Full GC


新生代设置过大会导致如下后果

1、导致老年代过小(heap总量一定),易诱发Full GC

2、新生代GC耗时大幅度增加

一般说来新生代占整个堆1/3比较合适


Survivor设置过小会导致如下后果

导致对象从eden直接到达老年代,降低了在新生代的存活时间


Survivor设置过大会导致如下后果

导致eden过小,增加了GC频率




当以吞吐量优先时的调优策略:

JVM以吞吐量为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,来达到吞吐量指标。这个值可由-XX:GCTimeRatio=n来设置

当以暂停时间优先时的调优策略:

JVM以暂停时间为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成。这个值可由-XX:MaxGCPauseRatio=n来设置


例子:设置tomcat内存大小

直接在catalina.sh脚本最前方增加
JAVA_OPTS='-Xms1024m -Xmx1024m -XX:PermSize=128m  -XX:MaxPermSize=256m'
将xms和xmx设置一样大,省着经常内存回收