实战JAVA虚拟机 JVM故障诊断与性能优化(二)

时间:2022-12-27 16:05:16

认识java虚拟机的基本结构

  java虚拟机基本结构

    实战JAVA虚拟机  JVM故障诊断与性能优化(二)

  类加载子系统:负责从文件系统或网络中加载class信息,加载的类信息存放于一块称为方法区的内存空间。除类信息外,方法区中可能还会存放运行时常量池信息,包括字符串常量和数字常量

  java堆:虚拟机启动时建立,它是java程序最主要的内存工作区域,几乎所有的Java对象实例都存放于java堆中,堆空间是所有线程共享的,这是一块与java应用密切相关的内存空间。 

  直接内存:java的NIO库允许java程序使用直接内存,直接内存是java堆外的、直接向系统申请的内存区间。通常,访问直接内存的速度优于java堆,所以读写频繁的场合可以考虑使用java直接内存,

       由于直接内存在java堆外,因此它的大小不会直接受限于Xmx制定的最大堆的大小,但是系统内存也是有限的,java堆和直接内存的总和受限于系统能给出的最大内存。

  垃圾回收系统:可以对方法区、Java堆、直接内存进行回收,其中java堆是垃圾收集器的重点,全都是隐式的,完成对方法区、Java堆、直接内存全自动化管理。

  Java栈:每一个Java虚拟机线程都有一个私有的Java栈。一个线程的java栈在线程创建时候被创建。Java栈中保存帧信息、局部变量、方法参数,同时和java的方法调用和返回密切相关。

  本地方法栈:与java栈类似,用于本地方法调用。

  PC寄存器(Program Counter):是每个线程私有空间,java虚拟机会为每一个java线程创建pc寄存器。在任意时刻,一个线程总在执行一个方法,这个正在执行的方法为当前方法。如果当前方法不为本地方法,

                pc寄存器就会指向当前正在被执行的方法。如果当前方法是本地方法,则pc寄存器的值是undefined。

  执行引擎:是java虚拟机最核心的组件之一,它负责执行虚拟机的字节码。现代虚拟机为了提高效率,会使用即时编译技术将方法编译成机器码后再执行。

 

学会设置Java虚拟机参数

    java [-options] class [args...]

      -options:java启动参数

      argrs:传递给主函数main()的参数

      class:带有main方法的Java类

    eg: java -Xmx32m simple a     

       -Xmx32m参数传给java虚拟机,使得系统堆内存为32mb,参数a传给main方法。

辨清java堆内存:

  Java堆是和java应用程序关系最为密切的内存空间,几乎所有的对象都存放在堆中,并且Java堆是完全自动化管理的。

  根据垃圾回收机制的不同,Java堆有可能拥有不同的结构。最为常见的一种是将整个java堆分为新生代和老年代,

   实战JAVA虚拟机  JVM故障诊断与性能优化(二)

  s0,s1也被称为form和to区域,两块大小相等。

eg:

public class SimpleHeap {

private int id;

public SimpleHeap(int id) {
this.id = id;
}

public void show() {
System.out.println("My Id:" + id);
}

public static void main(String[] args) {
SimpleHeap simpleHeap1 = new SimpleHeap(1);
SimpleHeap simpleHeap2 = new SimpleHeap(2);
simpleHeap1.show();
simpleHeap2.show();
}
}
堆,方法区,java栈分布:

  实战JAVA虚拟机  JVM故障诊断与性能优化(二)

函数如何调用:出入java栈

  java栈线程私有的空间,与线程执行密切相关,函数调用都是通过java栈完成的,在java栈中保存主要内容为栈帧。

  java方法有两种返回函数的方式:

    1.正常的函数返回,使用return指令。

    2.抛出异常,

  不管方式都会导致栈帧被弹出。

 

  在一个栈帧中,至少包含:局部变量表(保存被调用函数内部的局部变量)、操作数栈(用于计算过程中间的结果)和帧数据区(保存访问常量池的指针,异常处理表)。

 

  java栈上分配,是java虚拟机提供的一种优化技术,基本思路是线程私有的对象(指不能被其他线程访问的对象),可以分配在栈上,好处是在函数调用结束后自行销毁,而不需要垃圾回收器的介入,提高系统的效率。

逃逸分析:

  目的是判断对对象的作用域是否有可能逃逸出函数体。

   eg: 

-server 参数,执行程序,因为在SERVER模式下,才可以启动逃逸分析。
public class aaa1 {
private static Date date = null;

public static void main(String[] args) {
date = new Date(); //对象date是类成员,该字段有可能被其他线程访问,因此属于逃逸对象。
    }
}
public class aaa1 {
      public static void main(String[] args) {
      private static Date date = new Date(); // date 一个非逃逸对象。
    }
}

  每次函数调用都会占用一定的栈空间,当栈空间不足时,自然不能再调用函数,系统会抛出*Error栈溢出错误。

  -Xss  来指定线程的最大栈空间,也决定了函数调用的最大深度。

  -XX:+PrintGC  参数,可以看到垃圾回收前后堆的大小变化。

识别方法区:

  和java堆一样,方法区是一块所有线程共享的内存区域。用于保存系统类的信息,比如类的字段、方法、常量池等。方法区的大小决定保存多少个类。如果程序定义了太多类,则会导致方法区溢出,虚拟机同样会抛出内存溢出的错误。在jdk1.6和jdk1.7中方法区理解为永久区。

  永久区可以使用

  参数 -XX:PermSize 和 -XX:MaxPermSize指定。

  默认-XX:PermSize 为64MB,一个大方法区可以保存更多的类的信息,如果是用到动态代理,怎需要方法区设置大一点。

  eg:

    -XX:+PrintGCDetails -XX:PermSize=5M -XX:MaxPermSize=5m

 

补充: 监控jvm的实时动态信息:可以使用jdk自带的Visual VM进行分析,即${JAVA_HOME}/bin/jvisualvm.exe