JVM


线程隔离数据区

程序计数器(Program Counter Register):
一小块内存空间,单前线程所执行的字节码行号指示器。字节码解释器工作时,通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

JVM虚拟机栈(Java Virtual Machine Stacks):
Java方法执行内存模型,用于存储局部变量,操作数栈,动态链接,方法出口等信息。是线程私有的。

本地方法栈(Native Method Stacks):
为JVM用到的Native方法服务,Sun HotSpot 虚拟机把本地方法栈和JVM虚拟机栈合二为一。是线程私有的。


线程共享的数据区

方法区(Method Area):
用于存储JVM加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。
运行时常量池(Runtime Constant Pool):
是方法区的一部分,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法取得运行时常量池中。具备动态性,用的比较多的就是String类的intern()方法。
JVM堆( Java Virtual Machine Heap):
存放所有对象实例的地方。
新生代,由Eden Space 和大小相同的两块Survivor组成
旧生待,存放经过多次垃圾回收仍然存活的对象


直接内存(Direct Memory):
它并不是虚拟机运行时数据区的一部分,也不是JAVA虚拟机规范中定义的内存区域。在JDK1.4中加入了NIO类,引入了一种基于通道(Channel)于缓冲区(Buffer)的I/O方式,他可以使用Native函数库直接分配堆外内存,然后通过一个存储在JAVA堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在JAVA堆中和Native堆中来回复制数据。

先看一个经典问题:



1
2
3
4
5
6
7
8
9
10
11
12
13
String s1 =  "小金子(aub)" ;
String s2 =  "小金子(aub)" ;
String s3 =  "小金子"  "(aub)" ;
String s4 =  new  String( "小金子(aub)" );
String s5 =  "小金子"  new  String( "(aub)" );
String s6 = s4.intern();
System.out.println( "s1 == s2: "  + (s1 == s2)); //true;
System.out.println( "s1 == s3: "  + (s1 == s3)); //true;
System.out.println( "s2 == s3: "  + (s2 == s3)); //true;
System.out.println( "s1 == s4: "  + (s1 == s4)); //false;
System.out.println( "s1 == s5: "  + (s1 == s5)); //false;
System.out.println( "s4 == s5: "  + (s4 == s5)); //false;
System.out.println( "s1 == s6: "  + (s1 == s6)); //true;


 原因就在与String对象特殊的内存分配方式:(Strings pool是JVM内存中运行时常量池的一部分)

1.String s1 = new String("小金子(aub)");

2.String s2 = "小金子(aub)";
3.String s3 = "小金子" + "(aub)";

  虽然两个语句都是返回一个String对象的引用,但是jvm对两者的处理方式是不一样的。

对于第一种,jvm会马上在heap中创建一个String对象,然后将该对象的引用返回给用户。

对于第二种,jvm首先会在内部维护的strings pool中通过String的 equels 方法查找是对象池中是否存放有该String对象,如果有,则返回已有的String对象给用户,而不会在heap中重新创建一个新的String对象;如果对象池中没有该String对象,jvm则在heap中创建新的String对象,将其引用返回给用户,同时将该引用添加至strings pool中。

注意:使用第一种方法创建对象时,jvm是不会主动把该对象放到strings pool里面的,除非程序调用 String的intern方法

对于第三种,jvm会进行“+”运算符号的优化,两遍都是字符串常量会做类似于第二种的处理,如果“+”任意一边是一个变量,就会做类似第一种的处理。


JVM栈和Native Method栈内存分配:

JAVA中八个基本类型数据,在运行时都是分配在栈中的。在栈上分配的内存,随着数据的进栈出栈,方法运行完毕,或则线程结束时,自动被回收掉了。

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public  class  JvmStackOOM {
private  int  stackLength =  1 ;
public  void  execute() {
try  {
stackLeak();
catch  (Throwable e) {
System.out.println( "stackLength : "  + stackLength);
e.printStackTrace();
}
}
private  void  stackLeak() {
stackLength++;
stackLeak();
}
}

用一个递归不断地对实例变量stackLength进行自增操作,当JVM在扩展栈时无法申请到足够的空间,将产生StackOverflowError

可以使用Jvm 参数-Xss配置栈大小,例如:-Xss2M,栈内存越大,可的栈深度越大,在内存不变的情况下,jvm可创建的线程就越少,需要合理设置。


方法区内存分配:

 类信息和运行时常量将会分配到此区域。

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public  class  JvmRuntimeConstantPoolOOM {
private  int  runtimeConstantCount =  1 ;
public  void  execute() {
try  {
runtimeConstantLeak();
catch  (Throwable e) {
System.out.println( "runtimeConstantCount : "  + runtimeConstantCount);
e.printStackTrace();
}
}
private  void  runtimeConstantLeak() {
List<String> list =  new  ArrayList<String>();
while  ( true ) {
list.add(String.valueOf(runtimeConstantCount++).intern());
}
}
}

使用String的intern()方法向方法区中灌入数据,当方法区内存不足时,抛出OutOfMemoryError: PermGen space,

也可以加载过多的类的方式,测试是否有OutOfMemoryError: PermGen space异常,如果有说明类信息也是存放在方法区中的可以

使用Jvm 参数-XX:PermSize和-XX:MaxPermSize配置栈大小,例如:-XX:PermSize=10M -XX:MaxPermSize=10M


堆内存分配:

所有对象实例及数组都会在堆上分配。

堆分为新生代和老年代。新生代分为3个区域:一个eden区,和两个survivor区(互为From、To,相对的),

新建对象时首先想eden区申请分配空间,如果空间够,就直接进行分配,否则进行一次Minor GC(新生代垃圾回收)。

Minor GC后再次尝试将对象放到eden区,如果空间仍然不够,直接在老年代创建对象。

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public  class  JvmHeapOOM {
private  int  bojectCount =  1 ;
public  void  execute() {
try  {
heapLeak();
catch  (Throwable e) {
System.out.println( "bojectCount : "  + bojectCount);
e.printStackTrace();
}
}
private  void  heapLeak() {
List<OOMObject> list =  new  ArrayList<OOMObject>();
while  ( true ) {
list.add( new  OOMObject());
bojectCount++;
}
}
private  class  OOMObject {
}
}

创建多个OOMObject对象放到List中,当堆内存不足时,产生OutOfMemoryError:Java Heap space

使用Jvm 参数-Xm -Xmx -Xmn -XX:SurvivorRatio配置堆,例如:-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8


本地直接内存分配: 

堆外内存,NIO相关操作将在此分配内存

使用Jvm 参数-XX:MaxDirectMemorySize配置,例如:-XX:MaxDirectMemorySize=10M


所有用到的JVM启动参数:
-Xss2M       设置JVM栈内存大小
-Xms20M    设置堆内存初始值
-Xmx20M    设置堆内存最大值
-Xmn10M    设置堆内存中新生代大小
-XX:SurvivorRatio=8  设置堆内存中新生代Eden 和 Survivor 比例
-XX:PermSize=10M  设置方法区内存初始值
-XX:MaxPermSize=10M  设置方法区内存最大值
-XX:MaxDirectMemorySize=10M 设置堆内存中新生代大小 


这里将介绍几款sun hotspot jvm 自带的监控工具:

请确保java_home/bin配置到path环境变量下,因为这些工具都在jdk的bin目录下


jps(JVM Process Status Tool):JVM机进程状况工具


用来查看基于HotSpot JVM里面所有进程的具体状态, 包括进程ID,进程启动的路径等等。与unix上的ps类似,用来显示本地有权限的java进程,可以查看本地运行着几个java程序,并显示他们的进程号。使用jps时,不需要传递进程号做为参数。
Jps也可以显示远程系统上的JAVA进程,这需要远程服务上开启了jstat服务,以及RMI注及服务,不过常用都是对本对的JAVA进程的查看。
命令格式:jps [ options ] [ hostid ]
常用参数说明:
-m 输出传递给main方法的参数,如果是内嵌的JVM则输出为null。
-l 输出应用程序主类的完整包名,或者是应用程序JAR文件的完整路径。
-v 输出传给JVM的参数。

例如:

C:\Users\Administrator>jps -lmv
1796  -Dosgi.requiredJavaVersion=1.5 -Xms40m -Xmx512m -XX:MaxPermSize=256m
7340 sun.tools.jps.Jps -lmv -Denv.class.path=.;D:\DevTools\VM\jdk1.6.0_31\\lib\dt.jar;D:\DevTools\VM\jdk1.6.0_31\\lib\tools.jar; -Dapplication.home=D:\DevTools\VM\jdk1.6.0_31 -Xms8m

其中pid为1796的是我的eclipse进程,pid为7340的是jps命令本身的进程


jinfo(Configuration Info for Java):JVM配置信息工具


可以输出并修改运行时的java 进程的opts。用处比较简单,用于输出JAVA系统参数及命令行参数

命令格式:jinfo [ options ] [ pid ]

常用参数说明:

-flag  输出,修改,JVM命令行参数

例如:

 C:\Users\Administrator>jinfo 1796

将会打印出很多jvm运行时参数信息,由于比较长这里不再打印出来,可以自己试试,内容一目了然


Jstack(Stack Trace for Java):JVM堆栈跟踪工具

jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64“

命令格式:jstack [ option ] pid

常用参数说明:

-F 当’jstack [-l] pid’没有相应的时候强制打印栈信息

-l  长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表.

-m 打印java和native c/c++框架的所有栈信息.

-h | -help打印帮助信息

 例如:

C:\Users\Administrator>jstack 1796
2013-05-22 11:42:38
Full thread dump Java HotSpot(TM) Client VM (20.6-b01 mixed mode):

"Worker-30" prio=6 tid=0x06514c00 nid=0x1018 in Object.wait() [0x056af000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at org.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188)
        - locked <0x1ad84a90> (a org.eclipse.core.internal.jobs.WorkerPool)
        at org.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220)
        at org.eclipse.core.internal.jobs.Worker.run(Worker.java:50)
......
......
......
......


jstat(JVM statistics Monitoriing Tool):JVM统计信息监视工具


对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控
命令格式:jstat [ option  pid [interval [ s | ms ] [count] ] ] 
常用参数说明:
     -gcutil  输出已使用空间占总空间的百分比
     -gccapacity 输出堆中各个区域使用到的最大和最小空间

 例如:每隔1秒监控jvm内存一次,共监控5次

C:\Users\Administrator>jstat -gccapacity  1796  1s  5
 NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC      PGCMN    PGCMX     PGC       PC     YGC    FGC
 13632.0 174720.0  40896.0 4032.0 4032.0  32832.0    27328.0   349568.0    81684.0    81684.0  12288.0 262144.0  80640.0  80640.0     42    96
 13632.0 174720.0  40896.0 4032.0 4032.0  32832.0    27328.0   349568.0    81684.0    81684.0  12288.0 262144.0  80640.0  80640.0     42    96
 13632.0 174720.0  40896.0 4032.0 4032.0  32832.0    27328.0   349568.0    81684.0    81684.0  12288.0 262144.0  80640.0  80640.0     42    96
 13632.0 174720.0  40896.0 4032.0 4032.0  32832.0    27328.0   349568.0    81684.0    81684.0  12288.0 262144.0  80640.0  80640.0     42    96
 13632.0 174720.0  40896.0 4032.0 4032.0  32832.0    27328.0   349568.0    81684.0    81684.0  12288.0 262144.0  80640.0  80640.0     42    97
C:\Users\Administrator>jstat -gcutil  1796  1s  5
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT
  0.00   0.00   0.52  53.35  99.77     42    0.513    99   38.119   38.632
  0.00   0.00   0.52  53.35  99.77     42    0.513    99   38.119   38.632
  0.00   0.00   0.52  53.35  99.77     42    0.513    99   38.119   38.632
  0.00   0.00   0.52  53.35  99.77     42    0.513    99   38.119   38.632
  0.00   0.00   0.52  53.35  99.77     42    0.513    99   38.119   38.632

  一些术语的中文解释:

         S0C:年轻代中第一个survivor(幸存区)的容量 (字节)
         S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
         S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
         S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
           EC:年轻代中Eden(伊甸园)的容量 (字节)
           EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)
           OC:Old代的容量 (字节)
           OU:Old代目前已使用空间 (字节)
           PC:Perm(持久代)的容量 (字节)
           PU:Perm(持久代)目前已使用空间 (字节)
         YGC:从应用程序启动到采样时年轻代中gc次数
       YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
         FGC:从应用程序启动到采样时old代(全gc)gc次数
       FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
         GCT:从应用程序启动到采样时gc用的总时间(s)

    NGCMN:年轻代(young)中初始化(最小)的大小 (字节)

    NGCMX:年轻代(young)的最大容量 (字节)

        NGC:年轻代(young)中当前的容量 (字节)

   OGCMN:old代中初始化(最小)的大小 (字节) 

   OGCMX:old代的最大容量 (字节)

       OGC:old代当前新生成的容量 (字节)

   PGCMN:perm代中初始化(最小)的大小 (字节) 

   PGCMX:perm代的最大容量 (字节)   

       PGC:perm代当前新生成的容量 (字节)

          S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比

         S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比

           E:年轻代中Eden(伊甸园)已使用的占当前容量百分比

           O:old代已使用的占当前容量百分比

           P:perm代已使用的占当前容量百分比

  S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节)

 S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节)

    ECMX:年轻代中Eden(伊甸园)的最大容量 (字节)

       DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满)

          TT: 持有次数限制

       MTT : 最大持有次数限制


jmap( Memory Map for Java):JVM内存映像工具


打印出某个java进程(使用pid)内存内的所有‘对象’的情况(如:产生那些对象,及其数量)
命令格式:jmap [ option ] pid
常用参数说明:
        -dump:[live,]format=b,file=<filename> 使用二进制形式输出jvm的heap内容到文件中, live子选项是可选的,假如指定live选项,那么只输出活的对象到文件. 
        -histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量. 
        -F 强迫.在pid没有相应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效. 

例如:以二进制形式输入当前堆内存映像到文件data.hprof中

jmap -dump:live,format=b,file=data.hprof 1796

生成的文件可以使用jhat工具进行分析,在OOM(内存溢出)时,分析大对象,非常有用
通过使用如下参数启动JVM,也可以获取到dump文件:
      -XX:+HeapDumpOnOutOfMemoryError
      -XX:HeapDumpPath=./java_pid<pid>.hprof

      在jvm发生内存溢出时生成内存映像文件


jhat(JVM Heap Analysis Tool):JVM堆转储快照分析工具


用于对JAVA heap进行离线分析的工具,他可以对不同虚拟机中导出的heap信息文件进行分析,如LINUX上导出的文件可以拿到WINDOWS上进行分析,可以查找诸如内存方面的问题。
命令格式:jhat dumpfile(jmap生成的文件)

例如:分析jmap导出的内存映像

jhat data.hprof

 执行成功后,访问http://localhost:7000即可查看内存信息,


MAT(Memory Analyzer Tool):一个基于Eclipse的内存分析工具


官网: http://www.eclipse.org/mat/
update:http://download.eclipse.org/mat/1.2/update-site/

这是eclipse的一个插件,安装后可以打开xxx.hprof文件,进行分析,比jhat更方便使用,有些时候由于线上xxx.hprof文件过大,直接使用jhat进行初步分析了,可以的话拷贝到本地分析效果更佳。


图形化监控工具:


在JDK安装目录bin下面有两个可视化监控工具
1. JConsole(Java Monitoring and Management Console) 基于JMX的可视化管理工具。
2. VisualVM(All-in-one Java Troubleshooting Tool)随JDK发布的最强大的运行监视和故障处理程序。
推荐使用VisualVM,他有很多插件,可以更方便的监控运行时JVM

JVM的GC概述

GC即垃圾回收,是指jvm用于释放那些不再使用的对象所占用的内存。
在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能。

有些垃圾收集专用于特殊的应用程序。比如,实时应用程序主要是为了避免垃圾收集中断,而大多数OLTP应用程序则注重整体效率。
垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。两种常用的方法是引用计数和对象引用遍历。
引用计数
引用计数存储对特定对象的所有引用数,也就是说,当应用程序创建引用以及引用超出范围时,jvm必须适当增减引用数。当某对象的引用数为0时,便可以进行垃圾收集。
对象引用遍历
早期的jvm使用引用计数,现在大多数jvm采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,gc必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。

 

基本的回收算法:

空间维度:标记-清除、标记-压缩、标记-复制、增量回收、分代回收

时间维度:串行回收、并发回收、并行回收

 

标记-清除:

标记清除的算法最简单,主要是标记出来需要回收的对象,然后然后把这些对象在内存的信息清除,会产生大量内存碎片。

 

标记-压缩

有时也叫标记-清除-压缩收集器,这个算法是在标记-清除的算法之上进行剪切操作,将存活对象压缩在一起,减少内存碎片。由于压缩空间需要一定的时间,会影响垃圾收集的时间。

标记-复制

 

这个算法是把内存分配为两个空间,一个空间(A)用来负责装载正常的对象信息,另外一个内存空间(B)是垃圾回收用的。

每次把空间A中存活的对象全部复制到空间B里面,在一次性的把空间A删除。

这个算法在效率上比标记-清除-压缩高,但是需要两块空间,对内存要求比较大,内存的利用率比较低。适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。

 

 

增量回收

增量回收器:把堆分为多个域,每次对从一个域进行垃圾回收。这样只会早点一小部分程序暂停。

分代回收:

基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法进行回收。

 

串行回收:

用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势,所以此收集器适合单处理器机器

 

并行回收:

用多线程处理所有垃圾回收工作,利用多核处理器的优势。对于空间不大的区域(如young generation),采用并行收集器停顿时间很短,回收效率高,适合高频率执行。但是如果线程数量过多,导致线程之间频繁调度,也会影响性能。一半并行收集的线程是处理器的个数。

 

并发回收:

并发收集器GC时GC线程和应用线程大部分时间是并发执行,只是在初始标记(initial mark)和二次标记(remark)时需要stop-the-world,这可以大大缩短停顿时间(pause time),所以适用于响应时间优先的应用,减少用户等待时间。由于GC是和应用线程并发执行,只有在多CPU场景下才能发挥其价值,在一个N个处理器的系统上,并发收集部分使用K/N个可用处理器进行回收,一般情况下1<=K<=N/4。在执行过程中还会产生新的垃圾floating garbage(浮动垃圾),如果等空间满了再开始GC,那这些新产生的垃圾就没地方放了(并发收集器一般需要20%的预留空间用于这些浮动垃圾),这时就会启动一次串行GC,等待时间将会很长,所以要在空间还未满时就要启动GC。mark和sweep操作会引起很多碎片,所以间隔一段时间需要整理整个空间,否则遇到大对象,没有连续空间也会启动一次串行GC。采用此收集器,收集频率不能大,否则会影响到cpu的利用率,进而影响吞吐量。

 

JVM的GC触发原理

JVM的GC主要是对堆内存的回收,

一般把新生代的GC称为minor GC ,把老年代的GC成为 full GC,所谓full gc会先出发一次minor gc,然后在进行老年代的GC。

首先想eden区申请分配空间,如果空间够,就直接进行分配,否则进行一次Minor GC。

minor GC 首先会对Eden区的对象进行标记,标记出来存活的对象。然后把存活的对象copy到From空间。

如果From空间足够,则回收eden区可回收的对象。

如果from内存空间不够,则把From空间存活的对象复制到To区,

如果TO区的内存空间也不够的话,则把To区存活的对象复制到老年代。

如果老年代空间也不够(或者达到触发老年年垃圾回收条件的话)则触发一次full GC。

简单方向就是Eden->From->To->Old,如下图所示:

 

默认是不会对持久带(方法区)进行垃圾回收的,设置参数可回收:-XX:+CMSClassUnloadingEnabled

 

JVM支持的GC收集器

JVM采用的是分代回收,不同代有不同的垃圾收集器

如图所示:连线的是可以组合使用

 

各个收集器的细节我就不在这里COPY/PASTE了,SerialOld收集器在书的图中没有,是我后加上的,其实很少使用。

感兴趣的可以试读,试读地址http://book.51cto.com/art/201107/278857.htm,有前三章的内容。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值