显示标签为“JVM”的博文。显示所有博文
显示标签为“JVM”的博文。显示所有博文

2012年10月30日星期二

JVM原理简述-GC

GC

        GC-垃圾回收是一种动态存储管理技术,它可以自动释放那些不再被使用的对象,按照特定的算法来实现资源自动回收。

垃圾收集方式

引用计数

        堆中的每个对象对应一个引用计数器,每当创建或重新赋值的时候,引用计数器就+1,而当对象出了作用域之后,计数器-1,直到计数器为0,就达到了被回收的条件。
        引用计数方式速度较快,不会产生停顿。但是开销较大,因为每个对象都对应了一个计数器。

对象引用遍历

        从根集开始扫描,识别出哪些对象可达,哪些不可到达。并利用某种方式标记可达对象。

垃圾收集算法

复制

        找到活动对象并拷贝到新的空间。这种算法适合存活对象较少的情况,内存开销较大。

标记清除

        从根开始扫描,将活动对象标记。然后再扫描未标记的对象,一次清除。此方法的优点是不需要移动对象,仅对不存活对象操作,适合存活对象较多的情况,容易产生内存碎片。

标记压缩

        标记清除的基础上,移动活动对象。缺点是成本较高,优点是没有内存碎片。

分代回收

        GC是对JVM堆中的对象进行回收,在上一篇博客中,hotspot堆分为young,old和perm三部分。新生对象首先进入young区,随着young区不断的增加,一些老的对象将被移到old区。而perm区主要存储了java class的类型信息和静态变量等。

youngGC

即年轻代垃圾回收,当创建新对象,Eden空间不足时,触发youngGC。有三种选择
1. 串行GC
将Eden中存活的对象移到From->To->Old.适用于单线程,单cpu,新生代较小,要求不高的应用。在client模式或32位机器下默认选择。
启用:-XX:+UseSerialGC
2. 并行回收GC,Parallel Scavenge
多线程执行,适应多CPU,要求较高的应用。在server模式下是默认的。启用:-XX:+UseParallelGC, 在8核心以下的机器上默认为cpu核心数,大于8核,则默认为3+(核心数*5)/8.也可以通过-XX:ParallelGCThreads=8指定。
3. ParNew GC
需配合旧生代使用CMSGC。启用:-XX:+UseParNewGC.可以通过-XX:DisableExplicitGC禁用。

OldGC

1. 旧生代串行GC
        算法:标记清除和标记压缩。采用单线程执行,耗时较长,需要暂停应用。client模式或32位机默认采用这种模式。-XX:+PrintGCApplicationStoppedTime可以查看应用暂停时间。
2. 旧生代并行GC
        算法:标记压缩算法。采用多线程方式,暂停时间减少。server模式默认采用这种回收方式。
3. 旧生代并发GC
        采用标记清除算法。对GC进行并发执行,大大缩短应用暂停时间,但是整体GC时间会加长。启用方式:-XX:+UseConcMarkSweepGC。可以通过-XX:ParallelCMSThreads=10指定并发线程数。通过-XX:+CMSClassUnloadingEnabled来启用持久代CMS.
        垃圾整理:使用-XX:UseCMSCompactAtFullCollection每次fullGC后都会启动垃圾整理.-XX:CMSFullGCsBeforeCompaction=2表示2次fullGC后开始整理。

fullGC

旧生代和持久代GC时,即是对新生代,旧生代持久代都GC,FullGC。
执行时,先对新生代GC,可以通过-XX:ScanengeBeforeFullGC禁止fullGC时对新生代GC,然后对旧生代持久代进行GC。
触发时机:
1. system.gc,可以通过-XX:DisableExplicitGC禁用。
2. 旧生代空间不足
3. 持久代空间满
4. CMSGC时出现promotion failed
5. 统计youngGC后要移到旧生代的对象大于旧生代剩余空间

查看JVM使用的GC算法

找到java进程ID,使用jmap命令:
jmap -heap {pid}
会打印出JVM的基本信息,包括分区信息,回收算法等。
打印之前先看一下启动参数:
java -server -Xms512m -Xmx512m -XX:NewSize=100m -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=58 -XX:PermSize=64m -XX:MaxPermSize=64m -XX:ThreadStackSize=512

下面是jvm-heap信息:
Attaching to process ID 31410, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 20.8-b03

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
   MinHeapFreeRatio = 40
   MaxHeapFreeRatio = 70
   MaxHeapSize      = 536870912 (512.0MB)
   NewSize          = 104857600 (100.0MB)
   MaxNewSize       = 283508736 (270.375MB)
   OldSize          = 314572800 (300.0MB)
   NewRatio         = 7
   SurvivorRatio    = 8
   PermSize         = 67108864 (64.0MB)
   MaxPermSize      = 67108864 (64.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 94371840 (90.0MB)
   used     = 7155464 (6.823982238769531MB)
   free     = 87216376 (83.17601776123047MB)
   7.582202487521702% used
Eden Space:
   capacity = 83886080 (80.0MB)
   used     = 6906936 (6.586967468261719MB)
   free     = 76979144 (73.41303253173828MB)
   8.233709335327148% used
From Space:
   capacity = 10485760 (10.0MB)
   used     = 248528 (0.2370147705078125MB)
   free     = 10237232 (9.762985229492188MB)
   2.370147705078125% used
To Space:
   capacity = 10485760 (10.0MB)
   used     = 0 (0.0MB)
   free     = 10485760 (10.0MB)
   0.0% used
concurrent mark-sweep generation:
   capacity = 432013312 (412.0MB)
   used     = 7523480 (7.174949645996094MB)
   free     = 424489832 (404.8250503540039MB)
   1.7414926325233238% used
Perm Generation:
   capacity = 67108864 (64.0MB)
   used     = 39960504 (38.10930633544922MB)
   free     = 27148360 (25.89069366455078MB)
   59.545791149139404% used

2012年10月9日星期二

JVM原理简述

什么是JVM?

         JVM是Java Virtual Machine(java虚拟机)的简称。它是一种规范。通过在实际计算机上仿真模拟各种计算功能来实现的。Java虚拟机有自己完善的硬件体系架构,例如处理器,堆栈,寄存器等,还有相应的指令系统。由于JAVA虚拟机屏蔽的具体操作系统平台的相关信息,使得java程序只需要生成在java虚拟机上运行的代码(字节码),就可以在多种不同平台上运行。而字节码又经过java虚拟机解释成具体平台的机器指令来执行。

什么是字节码?

          ByteCode指通过编译生成,但是与机器码无关,通常要经过解释器转义后才可以成为机器码的中间代码。编译器将特定的源码编译为字节码,而字节码又经过虚拟机转义成为可以直接运行的指令。

JVM的执行过程

编译

        由java编译器将源码编译为字节码(class文件)
        编译过程如下:
        1. 词法分析,扫描类名,变量和函数
        2. 语法分析
        3. 注解处理
        4. 语义分析,名字,表达式,变量,方法,类型,参数等一系列检查
        5. 生成字节码文件(class)

装载

        首先,由类装载器(classloader)装载class文件。
        1. BootStrapClassLoader装载jdk/lib下和Xbootclasspath指定的所有jar包;
        2. ExtClassLoader装载jdk/lib/ext下和-Djava.ext.dirs指定的所有jar包
        3. 由AppClassLoader装载-classpath和-Djava.class.path指定的目录和jar;
        4. 自定义类装载器,运行时动态加载class文件。
       注:装载时会进行验证,如果找不到class文件会抛出classNotFoundException
       装载完成后,会进行字节码校验:
       1. FileFormat-VerifyError
       2. 引用类:NoClassDefFoundError
       3. 变量:NoSuchFieldError
       4. 方法:NoSuchMethodError
       5. 权限
       检查完毕后,开始初始化class。
       jvm中执行代码有两种方式:1. 解释执行,即由jvm中的java解释器将字节码解释为不同操作系统平台的指令执行;2. 编译执行,将字节码编译为机器码执行。这个编译过程由JVM中的JIT完成。只有使用频率高的代码才会被JIT编译为机器码执行,以提高效率。

JVM内存区域

由图1中可以看出,JVM内存区分为1. 方法区,2. 堆,3. 栈,4. 程序计数器,5. 本地方法栈。

方法区(Perm区)

        由classloader加载的class的类型信息全部存储在方法区中。所有的静态变量也存在于方法区中。方法区是所有线程共享的,因此方法区是线程安全的。方法区是内存永久保存区域,当perm空间不足时,会引发full GC,GC之后还不足,会抛出permGen space不足的错误。-XX:PermSize, -XX:MaxPermSize来指定方法区的最小和最大值。
        主要存储内容:
        1. 此类型的全限定名
        2. 此类型的超类的全限定名
        3. 此类型是接口还是类
        4. 此类型的访问修饰符号(public,private等)
        5. 此类型的常量池,字段,方法信息
        6. 除常量意外的所有静态变量
        7. 一个到classloader的引用
        8. 一个到class的引用

        JVM的堆中存在了所有类的实例和所有数组。一个JVM只有一个堆空间,各个线程都共享这个空间。
        图中可以看出,堆分为young区和old区,一般young和old的配置比例为1:m。
        young区保存大部分新对象,便于高效回收。其中Eden区存储新创建的对象,Eden满的时候,会有一次小范围的youngGC,将Eden中依然存活的对象存储至from,当from满时,此区域的活动对象会移动到to,直到from/to区域都满的时候,依然存活的对象会移到old区。-Xmn设置新生代大小。-XX:NewSize和-XX:MaxNewSize设置young的最小值和最大值;-XX:NewRatio=m设置young:old=1:m。-XX:SurvivorRatio=8设置Eden和from/to的比例为8:1:1
        old区保存存活时间较长的对象,当整个新生代满的时候,会触发一次full GC,将新生代的存活对象移到old。通过-Xmx-Xmn设置旧生代大小。
-Xmx和-Xms来指定heap的最大和最小值,避免JVM动态调整则设置为同一个值即可。

程序计数器

每一个线程都有自己的程序计数器,里面存放了下一条被执行指令的地址。

        每一个线程被创建的时候,都会得到自己的程序计数器和java栈。java栈以帧为单位保存调用信息。当线程调用一个方法,JVM会向栈中压入一个新的栈帧,当方法调用结束时,栈帧会被弹出。java栈帧是线程私有的,不存在多线程的安全性问题。
        栈帧由3部分构成,局部变量区,操作数栈,帧数据区。
        1. 局部变量区
        编译器将调用方法的局部变量和参数按照声明顺序放入局部变量数组中,从0开始计数。如果是实例方法,this将被存储到数组的第一个位置。java中的对象传递都是按照引用传递的,因此java对象全部存储在堆中,而局部变量区和操作数栈中只存储了对象的引用。
        2. 操作数栈
        JVM把操作数栈作为它的工作区,大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈,然后等相关的指令将结果再次弹出。操作数栈扮演了暂存操作数的角色。
        3. 帧数据区
        栈帧还可以存储常量池的解析,正常方法返回和异常派发机制。这些都存在帧数据区。当方法要访问常量池时,会通过帧数据区的指向常量池的指针来访问。
        如果方法正常返回,jvm会将返回值压入调用方法的栈帧的操作数栈中。
        为了处理异常,帧数据区还保存了一个对异常表的引用。

本地方法栈

        主要是为JVM使用本地方法而服务的。线程调用本地方法时,JVM会保持java栈不变,不再压入新的栈帧。在sun的hotspotJVM中,本地方法栈和栈是合二为一的。

垃圾回收

放在下一篇文章中讨论。

2012年4月19日星期四

JVM命令行分析工具

先附上一张JVM参数大全:http://kenwublog.com/docs/java6-jvm-options-chinese-edition.htm
1. jstat-是JDK自带的一个轻量级小工具。全称“Java Virtual Machine statistics monitoring tool”,它位于java的bin目录下,主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。
命令行格式:

jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]


jstatOptions:
-gcutil 打印gc的统计信息,例如 jstat -gcutil 1234 1000 100 表示每隔1秒打印一次pid为1234的gc统计信息,打印100次;

S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT
100.00   0.00   7.86  96.32  99.36 124512  329.588   346  161.916  491.504
  0.00  97.27  21.61  96.33  99.36 124513  329.591   346  161.916  491.507
  0.00  91.02  99.64  96.38  99.36 124515  329.597   346  161.916  491.513
E:Eden区使用的百分比;
O:Old区使用的百分比;
P:perm区使用的百分比;
YGC:youngGC的次数;
YGCT:youngGC总共耗时(秒)
FGC:fullGC的次数
FGCT:fullGC的总耗时(秒)
GCT:GC总共耗时
-----------
其他选项:

-class Option 显示class的数量和占用空间等情况
-compiler Option 显示VM实时编译的数量等信息;
-gc Option 显示GC的一些信息,包含各个区所占大小和GC的次数以及时间统计
-gccapacity Option
-gccause Option
-gcnew Option
-gcnewcapacity Option
-gcold Option
-gcoldcapacity Option
-gcpermcapacity Option
-printcompilation Option
2.jstack 打印JVM堆栈信息,可以通过这个命令查询cpu过高或死锁一类的问题。
例如:jstack -l 1234 打印pid为1234的JVM的堆栈信息;
如果cpu占用过高,可以关注那些runnable的进程;
如果停止响应,可以关注那写locked的进程信息;
3.jmap-对JVM的内存进行查询或dump操作的命令;例如:jmap -histo pid 可以打印出JVM中占用内存按排名的java对象个数和所占内存大小;
命令行格式:

       jmap [ option ] pid
       jmap [ option ] executable core
       jmap [ option ] [server-id@]remote-hostname-or-IP

options:
executable Java executable from which the core dump was produced.
(可能是产生core dump的java可执行程序)
core 将被打印信息的core dump文件
remote-hostname-or-IP 远程debug服务的主机名或ip
server-id 唯一id,假如一台主机上多个远程debug服务
基本参数:
-dump:[live,]format=b,file=<filename> 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的,假如指定live选项,那么只输出活的对象到文件.
-finalizerinfo 打印正等候回收的对象的信息.
-heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况.
-histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量.
-permstat 打印classload和jvm heap长久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来.
-F 强迫.在pid没有相应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效.
-h | -help 打印辅助信息
-J 传递参数给jmap启动的jvm.
pid 需要被打印配相信息的java进程id,创业与打工的区别 - 博文预览,可以用jps查问.
常用命令:
jmap -histo pid 打印内存中的对象信息;
jmap -dump:format=b file=heap.bin pid 将JVM以2进制文件的形式dump到磁盘上;
heap.bin可以通过mat或jhat进行进一步分析;
4. jhat&mat
分析jvm-dump的二进制文件的两种方法,其中mat是eclipse插件,jhat是命令行工具;
命令格式:
jhat [ options ] <heap-dump-file>


例如:jhat -J-Xmx512m heap.bin 可以在本地的7000端口启动一个服务,可以通过浏览器来访问获取统计信息;