Java虚拟机详解03----常用JVM配置参数

本文主要内容:

  • Trace跟踪参数
  • 堆的分配参数
  • 栈的分配参数

 

零、在IDE的后台打印GC日志:

既然学习JVM,阅读GC日志是处理Java虚拟机内存问题的基础技能,它只是一些人为确定的规则,没有太多技术含量。

既然如此,那么在IDE的控制台打印GC日志是必不可少的了。现在就告诉你怎么打印。

(1)如果你用的是Eclipse,打印GC日志的操作如下:

在上图的箭头处加上-XX:+PrintGCDetails这句话。于是,运行程序后,GC日志就可以打印出来了:

(2)如果你用的是IntelliJ IDEA,打印GC日志的操作如下:

在上图的箭头处加上-XX:+PrintGCDetails这句话。于是,运行程序后,GC日志就可以打印出来了:

当然了,光有-XX:+PrintGCDetails这一句参数肯定是不够的,下面我们详细介绍一下更多的参数配置。

 

一、Trace跟踪参数:

1、打印GC的简要信息:

-verbose:gc
-XX:+printGC

解释:可以打印GC的简要信息。比如:

[GC 4790K->374K(15872K), 0.0001606 secs]

[GC 4790K->374K(15872K), 0.0001474 secs]

[GC 4790K->374K(15872K), 0.0001563 secs]

[GC 4790K->374K(15872K), 0.0001682 secs]

上方日志的意思是说,GC之前,用了4M左右的内存,GC之后,用了374K内存,一共回收了将近4M。内存大小一共是16M左右。

 

2、打印GC的详细信息:

-XX:+PrintGCDetails

解释:打印GC详细信息。

-XX:+PrintGCTimeStamps

解释:打印CG发生的时间戳。

 

理解GC日志的含义:

例如下面这段日志:

[GC[DefNew: 4416K->0K(4928K), 0.0001897 secs] 4790K->374K(15872K), 0.0002232 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

上方日志的意思是说:这是一个新生代的GC。方括号内部的“4416K->0K(4928K)”含义是:“GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存区域总容量)”。而在方括号之外的“4790K->374K(15872K)”表示“GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量)”。

再往后看,“0.0001897 secs”表示该内存区域GC所占用的时间,单位是秒。

 

再比如下面这段GC日志:

上图中,我们先看一下用红框标注的“[0x27e80000, 0x28d80000, 0x28d80000)”的含义,它表示新生代在内存当中的位置:第一个参数是申请到的起始位置,第二个参数是申请到的终点位置,第三个参数表示最多能申请到的位置。上图中的例子表示新生代申请到了15M的控件,而这个15M是等于:(eden space的12288K)+(from space的1536K)+(to space的1536K)

疑问:分配到的新生代有15M,但是可用的只有13824K,为什么会有这个差异呢?等我们在后面的文章中学习到了GC算法之后就明白了。

 

3、指定GC log的位置:

-Xloggc:log/gc.log

解释:指定GC log的位置,以文件输出。帮助开发人员分析问题。

  

-XX:+PrintHeapAtGC

解释:每一次GC前和GC后,都打印堆信息。

例如:

上图中,红框部分正好是一次GC,红框部分的前面是GC之前的日志,红框部分的后面是GC之后的日志。

 

-XX:+TraceClassLoading

解释:监控类的加载。

例如:

[Loaded java.lang.Object from shared objects file]

[Loaded java.io.Serializable from shared objects file]

[Loaded java.lang.Comparable from shared objects file]

[Loaded java.lang.CharSequence from shared objects file]

[Loaded java.lang.String from shared objects file]

[Loaded java.lang.reflect.GenericDeclaration from shared objects file]

[Loaded java.lang.reflect.Type from shared objects file]

 

-XX:+PrintClassHistogram

 

解释:按下Ctrl+Break后,打印类的信息。

例如:

 

二、堆的分配参数:

1、-Xmx –Xms:指定最大堆和最小堆

举例、当参数设置为如下时:

-Xmx20m -Xms5m

然后我们在程序中运行如下代码:

System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");     //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");   //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");   //当前可用的总空间

 运行效果:

保持参数不变,在程序中运行如下代码:(分配1M空间给数组)

byte[] b = new byte[1 * 1024 * 1024];
System.out.println("分配了1M空间给数组");
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");   //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");   //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");   

运行效果:

注:Java会尽可能将total mem的值维持在最小堆。

保持参数不变,在程序中运行如下代码:(分配10M空间给数组)

byte[] b = new byte[10 * 1024 * 1024];
System.out.println("分配了10M空间给数组");
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");   //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");   //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");   //当前可用的总空间

运行效果:

如上图红框所示:此时,total mem 为7M时已经不能满足需求了,于是total mem涨成了16.5M。

 

保持参数不变,在程序中运行如下代码:(进行一次GC的回收)

System.gc();
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");    //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");   //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");   //当前可用的总空间 

运行效果:

问题1: -Xmx(最大堆空间)和 –Xms(最小堆空间)应该保持一个什么关系,可以让系统的性能尽可能的好呢?

问题2:如果你要做一个Java的桌面产品,需要绑定JRE,但是JRE又很大,你如何做一下JRE的瘦身呢?

 

2、-Xmn、-XX:NewRatio、-XX:SurvivorRatio:

  • -Xmn

    设置新生代大小

  • -XX:NewRatio

    新生代(eden+2*s)和老年代(不包含永久区)的比值

        例如:4,表示新生代:老年代=1:4,即新生代占整个堆的1/5

  • -XX:SurvivorRatio(幸存代)

    设置两个Survivor区和eden的比值

        例如:8,表示两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10

 

现在运行如下这段代码:

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

我们通过设置不同的jvm参数,来看一下GC日志的区别。

 

(1)当参数设置为如下时:(设置新生代为1M,很小)

-Xmx20m -Xms20m -Xmn1m -XX:+PrintGCDetails 

运行效果:

总结:

  没有触发GC

    由于新生代的内存比较小,所以全部分配在老年代。

 

(2)当参数设置为如下时:(设置新生代为15M,足够大)

-Xmx20m -Xms20m -Xmn15m -XX:+PrintGCDetails

运行效果:

上图显示:

没有触发GC

全部分配在eden(蓝框所示)

老年代没有使用(红框所示)

 

(3)当参数设置为如下时:(设置新生代为7M,不大不小)

-Xmx20m -Xms20m –Xmn7m -XX:+PrintGCDetails

运行效果:

总结:

  进行了2次新生代GC

  s0 s1 太小,需要老年代担保

 

(4)当参数设置为如下时:(设置新生代为7M,不大不小;同时,增加幸存代大小)

-Xmx20m -Xms20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails

运行效果:

总结:

    进行了至少3次新生代GC

    s0 s1 增大

 

(5)当参数设置为如下时:

-Xmx20m -Xms20m -XX:NewRatio=1

-XX:SurvivorRatio=2 -XX:+PrintGCDetails 

运行效果:

 

 

(6)当参数设置为如下时: 和上面的(5)相比,适当减小幸存代大小,这样的话,能够减少GC的次数

-Xmx20m -Xms20m -XX:NewRatio=1

-XX:SurvivorRatio=3 -XX:+PrintGCDetails

 

 

3、-XX:+HeapDumpOnOutOfMemoryError、-XX:+HeapDumpPath

  • -XX:+HeapDumpOnOutOfMemoryError

    OOM时导出堆到文件

      根据这个文件,我们可以看到系统dump时发生了什么。

  • -XX:+HeapDumpPath

    导出OOM的路径

例如我们设置如下的参数:

-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump

上方意思是说,现在给堆内存最多分配20M的空间。如果发生了OOM异常,那就把dump信息导出到d:/a.dump文件中。

然后,我们执行如下代码:

Vector v = new Vector();
for (int i = 0; i < 25; i++)
  v.add(new byte[1 * 1024 * 1024]);

上方代码中,需要利用25M的空间,很显然会发生OOM异常。现在我们运行程序,控制台打印如下:

现在我们去D盘看一下dump文件:

上图显示,一般来说,这个文件的大小和最大堆的大小保持一致。

我们可以用VisualVM打开这个dump文件。

注:关于VisualVM的使用,可以参考下面这篇博客:

使用 VisualVM 进行性能分析及调优:http://www.ibm.com/developerworks/cn/java/j-lo-visualvm/

或者使用Java自带的Java VisualVM工具也行:

上图中就是dump出来的文件,文件中可以看到,一共有19个byte已经被分配了。 

 

4、-XX:OnOutOfMemoryError:

  • -XX:OnOutOfMemoryError

    在OOM时,执行一个脚本。

      可以在OOM时,发送邮件,甚至是重启程序。

例如我们设置如下的参数:

-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p //p代表的是当前进程的pid 

上方参数的意思是说,执行printstack.bat脚本,而这个脚本做的事情是:D:/tools/jdk1.7_40/bin/jstack -F %1 > D:/a.txt,即当程序OOM时,在D:/a.txt中将会生成线程的dump。

5、堆的分配参数总结:

  • 根据实际事情调整新生代和幸存代的大小
  • 官方推荐新生代占堆的3/8
  • 幸存代占新生代的1/10
  • 在OOM时,记得Dump出堆,确保可以排查现场问题

 

6、永久区分配参数:

  • -XX:PermSize  -XX:MaxPermSize

    设置永久区的初始空间和最大空间。也就是说,jvm启动时,永久区一开始就占用了PermSize大小的空间,如果空间还不够,可以继续扩展,但是不能超过MaxPermSize,否则会OOM。

    他们表示,一个系统可以容纳多少个类型

代码举例:

我们知道,使用CGLIB等库的时候,可能会产生大量的类,这些类,有可能撑爆永久区导致OOM。于是,我们运行下面这段代码:

for(int i=0;i<100000;i++){
  CglibBean bean = new CglibBean("geym.jvm.ch3.perm.bean"+i,new HashMap());
}

上面这段代码会在永久区不断地产生新的类。于是,运行效果如下:

总结:

  如果堆空间没有用完也抛出了OOM,有可能是永久区导致的

    堆空间实际占用非常少,但是永久区溢出 一样抛出OOM。

 

三、栈的分配参数:

1、Xss:

设置栈空间的大小。通常只有几百K

  决定了函数调用的深度

  每个线程都有独立的栈空间

  局部变量、参数 分配在栈上

注:栈空间是每个线程私有的区域。栈里面的主要内容是栈帧,而栈帧存放的是局部变量表,局部变量表的内容是:局部变量、参数。

我们来看下面这段代码:(没有出口的递归调用)

public class TestStackDeep {
    private static int count = 0;public static void recursion(long a, long b, long c) {
        long e = 1, f = 2, g = 3, h = 4, i = 5, k = 6, q = 7, x = 8, y = 9, z = 10;
        count++;
        recursion(a, b, c);
    }public static void main(String args[]) {
        try {
            recursion(0L, 0L, 0L);
        } catch (Throwable e) {
            System.out.println("deep of calling = " + count);
            e.printStackTrace();
        }
    }
}

上方这段代码是没有出口的递归调用,肯定会出现OOM的。

如果设置栈大小为128k:

-Xss128K 

运行效果如下:(方法被调用了294次)

如果设置栈大小为256k:(方法被调用748次)

意味着函数调用的次数太深,像这种递归调用就是个典型的例子。

 

总结:

我们在本文中介绍了jvm的一些最基本的参数,还有很多参数(如GC参数等)将在后续的系列文章中进行介绍。我们将在接下来的文章中介绍GC算法。

 

时间: 2024-10-24 04:16:26

Java虚拟机详解03----常用JVM配置参数的相关文章

Java虚拟机详解01----初识JVM

主要内容如下: JVM的概念 JVM发展历史 JVM种类 Java语言规范 JVM规范 一.JVM的概念: JVM: Java Virtual Machine,意为Java虚拟机. 虚拟机: 指通过软件模拟的具有完整硬件系统功能的.运行在一个完全隔离环境中的完整计算机系统 有哪些虚拟机: VMWare.Virtual Box:都是使用软件模拟物理CPU的指令集 JVM:使用软件模拟Java 字节码的指令集   二.JVM发展历史: 1.Java发展史: 1996年:SUN JDK 1.0 Cla

Java虚拟机详解----JVM常见问题总结

[正文] 声明:本文只是做一个总结,有关jvm的详细知识可以参考本人之前的系列文章,尤其是那篇:Java虚拟机详解04----GC算法和种类.那篇文章和本文是面试时的重点. 面试必问关键词:JVM垃圾回收.类加载机制.   先把本文的目录画一个思维导图:(图的源文件在本文末尾)   一.Java引用的四种状态: 强引用: 用的最广.我们平时写代码时,new一个Object存放在堆内存,然后用一个引用指向它,这就是强引用. 如果一个对象具有强引用,那垃圾回收器绝不会回收它.当内存空间不足,Java

请教:朋友跟我想写一本关于JAVA虚拟机详解方面的书。

问题描述 朋友跟我想写一本关于JAVA虚拟机详解方面的书.书的内容主要包括JVM的原理,JVM源码分析等方面的问题.书本身内容清晰,层次很分明,也很通俗易懂.目前书已经写了一半,大概6章的内容..不知道怎么联系出版社,如果出版以后销路会如何.也不知道有没有多少读者会关注JAVA虚拟机方面的知识..大家给点意见,或者渠道..谢谢. 解决方案 解决方案二:顶,一直有个小理想,自己写本jvm分析的书,不过未能实现.感觉这种书很小众.jvm原理的书还可以,但代码分析的未必对大部分java程序员有多大价值

JVM(Java虚拟机)详解

可能有很多学习Java的朋友还不知道Java的运行原理.Java虚拟机是怎么工作的,本文将为你详细讲解(JVM)Java虚拟机. 在Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器.这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口.编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行.在Java中,这种供虚拟机理解的代码叫做字节码(ByteCode),它不面向任何特定的处理器,只面向虚拟机.每一种平台的解

Java虚拟机详解04----GC算法和种类【重要】

本文主要内容: GC的概念 GC算法  引用计数法(无法解决循环引用的问题,不被java采纳)     根搜索算法     现代虚拟机中的垃圾搜集算法: 标记-清除 复制算法(新生代) 标记-压缩(老年代)     分代收集 Stop-The-World   一.GC的概念: GC:Garbage Collection 垃圾收集 1960年 Lisp使用了GC Java中,GC的对象是Java堆和方法区(即永久区) 我们接下来对上面的三句话进行一一的解释: (1)GC:Garbage Colle

Java虚拟机详解02----JVM内存结构

主要内容如下: JVM启动流程 JVM基本结构 内存模型 编译和解释运行的概念   一.JVM启动流程: JVM启动时,是由java命令/javaw命令来启动的. 二.JVM基本结构: JVM基本结构图: <深入理解Java虚拟机(第二版)>中的描述是下面这个样子的:   Java中的内存分配: Java程序在运行时,需要在内存中的分配空间.为了提高运算效率,就对数据进行了不同空间的划分,因为每一片区域都有特定的处理数据方式和内存管理方式. 具体划分为如下5个内存空间:(非常重要) 栈:存放局

Java虚拟机详解05----垃圾收集器及GC参数

本文主要内容: 堆的回顾 串行收集器 并行收集器 CMS收集器   零.堆的回顾: 新生代中的98%对象都是"朝生夕死"的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块比较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor.当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间.HotSpot虚拟机默认Eden和Survivor的大

Java中JVM虚拟机详解

1. 什么是JVM? JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的.Java虚拟机包括一套字节码指令集.一组寄存器.一个栈.一个垃圾回收堆和一个存储方法域. JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行.JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上

HBase Java API详解

[本文转自HBase Java API详解] HBase是Hadoop的数据库,能够对大数据提供随机.实时读写访问.他是开源的,分布式的,多版本的,面向列的,存储模型. 在讲解的时候我首先给大家讲解一下HBase的整体结构,如下图: HBase Master是服务器负责管理所有的HRegion服务器,HBase Master并不存储HBase服务器的任何数据,HBase逻辑上的表可能会划分为多个HRegion,然后存储在HRegion Server群中,HBase Master Server中存