互联网技术学习27———JVM各组成及相关参数

虚拟机是一款软件,用来执行一系列虚拟计算机指令,虚拟机可以分为系统虚拟机(如VirtualBox、Vmware)和程序虚拟机(如Java虚拟机)。系统虚拟机是对物理计算机的仿真,提供了一个可以运行完成操作系统的软件平台。程序虚拟机专门为执行单个计算机程序而设计,如在java虚拟机中执行的指令为java字节码指令。java发展至今,出现过很多虚拟机,最初使用的是Classic的虚拟机,到现在应用最管饭的是HotSpot虚拟机。

Java虚拟机的基本结构

类加载子系统、方法区、java堆、直接内存、java栈、本地方法栈、垃圾回收系统、pc寄存器、执行引擎

  1. 类加载子系统:负责从文件系统或网络中加载calss信息,加载的信息存放在一款称之为方法区的内存空间,被所有线程共享

  2. 方法区:存放类信息、常量信息、常量池信息,包括字符串字面量和数字常量等,被所有线程共享。平时,说到永久带(PermGen space)的时候往往将其和方法区不加区别。这么理解在一定角度也说的过去。 因为,《Java虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 同时,大多数用的JVM都是Sun公司的HotSpot。在HotSpot上把GC分代收集扩展至方法区,或者说使用永久代来实现方法区。

      虽然可以牵强的解释这种将方法区和永久带等同对待观点。但最终方法区和永久带还是不同的。一个是标准一个是实现。这就相当于你将java中的接口和接口的实现等同对待了一样

  3. java堆:在java虚拟机启动的时候建立java堆,它是java程序最主要的内存工作区域,几乎所有的对象实例都放在java堆中(例如:部分较小的对象可能放在TLAB中),被所有线程共享。

  4. 直接内存:java的NIO库允许ava程序使用直接内存,从而提高性能,通常直接内存速度 优于java堆,在读写频繁的场合可能考虑使用

  5. java栈:每个虚拟机线程都有一个私有的栈,一个线程的java栈在线程创建时被创建,java栈中保存着局部变量、方法参数、java的方法调用、返回值等。

  6. 本地方法栈:与java栈非常类似,最大不同为本地方法栈用于本地方法调用,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法。如何去服务native方法?native方法使用什么语言实现?怎么组织像栈帧这种为了服务方法的数据结构?虚拟机规范并未给出强制规定,因此不同的虚拟机实可以进行自由实现,我们常用的HotSpot虚拟机选择合并了虚拟机栈和本地方法栈。

  7. 垃圾收集系统:它是java的核心,也是必不可少的,java有一套自己进行垃圾回收的机制,开发人员无需手工清理

  8. 8pc寄存器:它是每个线程私有的空间,java虚拟机会为每个线程创建pc寄存器,在任意时刻,一个java线程总是在执行一个方法,这个方法被称为当前方法,如果当前方法不是本地方法,pc寄存器就会执行当前正在被执行的指令,如果是本地方法,则pc寄存器值为undefined,寄存器存放如房钱执行环境指针、程序计数器、操作栈指针、计算的变量指针等信息。

  9. 执行引擎:它是虚拟机最核心组件,复制执行虚拟机的字节码。一般先进行变异成机器码,执行引擎对机器码进行执行。

Java堆

堆解决的是数据存储的问题,即数据怎么放,放在那里。栈解决的程序运行的问题,即程序如何执行,或者说如何处理数据。方法区则是辅助堆栈的快永久区,解决堆栈信息的生产,是先决条件。若我们创建一个新的User类的对象user,则User类的信息及静态信息存放在方法区中,新创建的user对象存放在java堆中,当我们去使用的时候回,都是用用对象的引用user,这个user放在java栈中。

  判断新生代和老年代是通过对象经历的垃圾回收的次数

  java堆是java应用程序关系最密切的内存空间,几乎所有的对象都是放在其中。并且堆是通过垃圾回收机制完全自动化管理的。

  根据垃圾回收机制的不同,java堆可能有不同的结构。最为常见的就是将整个java堆分成新生代和老年代,其中新生代存放新生的对象或者年龄不大的对象,老年代存放老年对象。新生代分为eden区(伊甸区)、s0区、s1区,其中s0和s1也被称为form区和to区,他们是两块大小相等并且可以角色互换的空间。绝大多数情况下,对象首先分配在eden区,在一次新生代回收后,如果对象还活着,则会进入s0或者s1区,之后每经过一次新生代回收,如果对象存活则它的年龄增加1,当对象达到一定年龄后,则进入老年代。

  新生代的from和to空间使用的垃圾收集算法是复制算法,复制算法的核心就是将内存空间分为两块,每次只使用其中的一块,在垃圾回收时,将正在使用的内存中的对象复制到未被使用的内存块中,之后清除之前正在使用的内存块中所有对象,反复去交换这两个内存块的角色,完成垃圾回收。

  老年代使用的垃圾收集算法是标记压缩法,标记压缩法在标记清除法的基础上做了优化,把存活的对象压缩到内存的一端,而后进行垃圾回收。标记的过程是用过GcRoots可达性判断的。

  新生代和老年代都会经历GC,区别是GC频率不同。新生代GC是比较频繁的,而老年代是经历多次GC知乎仍然保存下来的,已经比较稳定,老年代gc没有那么频繁,

java栈

  java栈是一块线程私有的内存空间,一般分为三部分:局部变量表、操作数栈。帧数据区

  1. 局部变量表:用于保存函数的参数及局部变量
  2. 操作数栈:保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
  3. 帧数据区:除了局部变量表和操作数栈之外,栈还需要一些数据还支持常量池的解析,这里帧数据区保存着访问常量池的指针,方便程序访问常量池。另外,当函数返回或者出现异常时,虚拟机必须有一个异常处理表,方便发现异常的的时候找到异常的代码,异常处理表也是帧数据区的一部分。

虚拟机参数设置

在虚拟机运行过程中,如果可以跟踪系统的运行状态,那马对于文艺的故障排查会有一定的帮助,为此,虚拟机提供了一些跟踪系统状态的参数,使用给定的参数执行java虚拟机,就可以在系统运行时打印相关日志,用于分析实际问题。进行虚拟机参数配置,主要围绕着堆、栈。方法区进行设置。

 堆分配参数

-XX:+PRINTGC  使用这个参数,虚拟机启动后,只要遇到GC就会打印日志

-XX:+UseSerialGC 配置串行垃圾回收器

-XX:+PrintGCDetails  可以查看详细信息,包括各个区的情况

-Xms:  设置java程序启动时初始化堆大小

-Xmx:  设置java程序能获得的最大堆大小

-Xmx20m -Xms5m -XX:+PrientCommandLineFlags   初始化堆大小5m,最大堆大小20m,将显示的输出传给虚拟机的配置参数

在实际工作中,可以直接将初始堆大小设置相等,这样可以减少程序运行时垃圾回收次数,从而提高性能。

在idea中配置jvm参数

在eclipse中配置jvm参数

Run as -- Run Configurations ,在Main选项卡中设置project、main class;在Arguments中,Program arguments部分可以给main主函数传递一些参数,VM arguments可以设置JVM参数。

Test01.java

package com.jvmTest;

/**
 * Created by BaiTianShi on 2018/9/20.
 * 测试gc打印
 */
public class Tesst01 {
    public static void main(String[] args) {
        System.out.println("max memory:"+Runtime.getRuntime().maxMemory());
        System.out.println("total memory:"+Runtime.getRuntime().totalMemory());
        System.out.println("free memory:"+Runtime.getRuntime().freeMemory());

        byte[] b = new byte[1024*1024];
        System.out.println("分配了1m");
        System.out.println("max memory:"+Runtime.getRuntime().maxMemory());
        System.out.println("total memory:"+Runtime.getRuntime().totalMemory());
        System.out.println("free memory:"+Runtime.getRuntime().freeMemory());

        byte[] b4 = new byte[1024*1024*4];
        System.out.println("分配了4M");
        System.out.println("max memory:"+Runtime.getRuntime().maxMemory());
        System.out.println("total memory:"+Runtime.getRuntime().totalMemory());
        System.out.println("free memory:"+Runtime.getRuntime().freeMemory());
    }
}

配置的参数:

-Xms5m -Xmx20m -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintCommandLineFlags

打印结果

-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 
max memory:20316160
total memory:6094848
free memory:4846128
[GC (Allocation Failure) [DefNew: 1219K->192K(1856K), 0.0047945 secs] 1219K->660K(5952K), 0.0049086 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
分配了1m
max memory:20316160
total memory:6094848
free memory:4337080
[GC (Allocation Failure) [DefNew: 1247K->0K(1856K), 0.0035955 secs][Tenured: 1684K->1684K(4096K), 0.0051267 secs] 1716K->1684K(5952K), [Metaspace: 3180K->3180K(1056768K)], 0.0088664 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
分配了4M
max memory:20316160
total memory:10358784
free memory:4405072
Heap
 def new generation   total 1920K, used 51K [0x00000000fec00000, 0x00000000fee10000, 0x00000000ff2a0000)
  eden space 1728K,   2% used [0x00000000fec00000, 0x00000000fec0cc50, 0x00000000fedb0000)
  from space 192K,   0% used [0x00000000fedb0000, 0x00000000fedb0000, 0x00000000fede0000)
  to   space 192K,   0% used [0x00000000fede0000, 0x00000000fede0000, 0x00000000fee10000)
 tenured generation   total 8196K, used 5780K [0x00000000ff2a0000, 0x00000000ffaa1000, 0x0000000100000000)
   the space 8196K,  70% used [0x00000000ff2a0000, 0x00000000ff8452f0, 0x00000000ff845400, 0x00000000ffaa1000)
 Metaspace       used 3186K, capacity 4494K, committed 4864K, reserved 1056768K
  class space    used 348K, capacity 386K, committed 512K, reserved 1048576K

Process finished with exit code 0

-Xmn: 设置新生代的大小,若设置的新生代比较大会减小老年代的大小,这个参数对系统性能以及Gc行为有很大影响,新生代大小一般会设置为整个堆空间的1/4到1/3左右。

-XX:ServivorRatio:用来设置新生代中eden空间和from/to的空间比例,即 eden/from = eden/to

不同的堆分布情况,对系统执行会产生一定的影响,在实际工作中,应该根据系统的特点做出合理的配置。尽可能将对象预留在新生代,减少老年代的GC次数。

Test02.java

package com.jvmTest;

/**
 * Created by BaiTianShi on 2018/9/21.
 */
public class Test02 {

    public static void main(String[] args) {
        byte[] b = null;
        for (int i = 0; i < 10 ; i++) {
            b = new byte[1024*1024];
        }
    }

}

JVM参数
-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:UserSerialGC -XX:+PrintGCDetails

-XX:NewRatio  设置老年代和新生代的比,即老年代/新生代

继续使用Test02.java

JVM参数

-Xms20m -Xmx20m -XX:NewRatio=2  -XX:+UseSerialGC  -XX:+PrintGCDetails

堆溢出处理

在java程序的运行过程中,如果堆空间不足,则会抛出内存溢出的错误(Out Of Memory) oom,一旦这类问题发生在生产环境,可能引起严重的业务中断,可以考虑使用下面的两个参数

-XX:+HeapDumpOnOutOfMemoryError   可以在内存溢出时导出整个堆信息

-XX:HeapDumpPath   设置导出堆的存放路径

Test03.java

package com.jvmTest;

import java.util.Vector;

/**
 * Created by BaiTianShi on 2018/9/21.
 */
public class Test03 {
    public static void main(String[] args) {
        Vector v = new Vector();
        for (int i = 0; i < 5; i++) {
            v.add(new byte[1024*1024]);
        }
    }
}

JVM参数

-Xms1吗-Xmx1m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d/test03.dump

控制台打印及输出的文件

使用内存分析工具Memory Analyzer可进行内存情况分析

栈配置

Java虚拟机提供了参数-Xss来指定线程的最大栈空间,该参数直接决定了函数可调用的最大深度

Test04.java

package com.jvmTest;

/**
 * Created by BaiTianShi on 2018/9/21.
 */
public class Test04 {
    //栈调用深度
    private static int count =0;

    public static void recursion() {
        count++;
        recursion();
    }

    public static void main(String[] args) {

        try {
            recursion();
        } catch (Throwable  e) {
            System.out.println("掉调用最大深度:"+count);
            e.printStackTrace();
        }
    }
}

JVM参数

-Xss1m

方法区设置

  JDK1.8之后,方法区中的永久带PermGen被移除,替换为元空间MateSpace,其实在JDK1.7就已经着手做这件事了,把永久带中的部分数据转移到本地方法栈和虚拟机栈中了,直到1.8之后才完全移除。

  元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:

  -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
  -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。

  除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
  -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
  -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

直接内存配置

  直接内存也是java程序中非常重要的一部分,特别是广泛应用在NIO中,直接跳过java堆,是java程序可以直接访问原生堆空间,因此在一定程度桑加快了内存空间的访问速度。但是说直接内存一定就可以提高内存访问熟读也不见得,具体情况具体分析。

  相关配置参数:-XX:MaxDirectMemorySize,如果不设置,则默认为最大堆空间-Xmx。直接内存使用达到上限时,就会触发垃圾回收,如果不能有效释放空间,也会引起系统OOM

Client和Server虚拟机工作模式

目前java虚拟机支持Client、Server两种模式,使用-client可以指定使用Client模式,使用-server可以指定使用Server模式。可以直接在命令行中查看当前计算机系统自动选择的运行模式,即:java-version

二者区别:Client模式相对Server模式启动较快没如果不追求系统长时间使用性能,而仅仅是测试,可以使用Client模式。而Server模式则启动比较慢,原因是会对其进行复杂的系统性能信息收集和使用更发展的算法对程序进行优化,一般在生产环境下都会使用Server模式,长期运行其性能要远远快于Client模式。但是64为貌似没有Client模式,摸索中。

猜你喜欢

转载自blog.csdn.net/qq_28240551/article/details/82780317