专栏首页JavaEEJVM --- 堆&栈&堆参数调优

JVM --- 堆&栈&堆参数调优

一. 方法区:

线程共享的运行时内存区域,它存储了每一个类的结构信息。什么叫类的结构信息,其实就是上一篇讲类加载器时说的类的模板。也就是类的属性、构造器、方法、常量池等。而且,方法区是一种规范,不是具体实现java7及以前的实现叫永久代java8开始,方法区的实现叫元空间。

二. 栈:

1. 栈的基本介绍: 栈也叫栈内存,主要管java程序的运行,是线程私有的。它的生命周期是跟随线程的生命周期的,线程创建时创建,线程结束栈内存就释放。栈不存在垃圾回收。8种基本类型的变量、对象的引用变量和实例方法都是在函数的栈内存中分配的。

栈帧主要保存以下3类数据(栈帧就是方法,在java代码中它叫方法,压到栈里面就叫栈帧):

  • 本地变量:即输入参数、输出参数和方法内的变量;
  • 栈操作:记录出栈、入栈的操作;
  • 栈帧数据:类文件、方法等;

当你在main方法中调用另一个方法fun的时候,首先是main方法进栈,压到栈底,然后是fun方法进栈,等fun执行完,会自动将fun弹出,再继续执行main,最后main方法出栈。如果fun是一个没有终止条件的递归方法,那么就会不停地有fun入栈,直到栈装不下。此时会抛出一个错误:Exception in thread "main" java.lang.StackOverflowError。这就是栈内存溢出,注意,这是一个error,而不是exception。

2. 栈、堆、方法区的交互:

Person p1 = new Person();
Person p2 = new Person();

p1、p2是引用,上面说了,引用是栈中的,new Person()是在堆中完成的,Person的模板是存在方法区的,也就是,堆中new对象的时候,用的是方法区中的模板,所以能保证new出来的两个Person实例结构都是一样的。所以栈中的p1、p2存储的是实例在堆中地址值。

三. 堆:

1. 堆基本介绍:

一个JVM实例只存在一个堆,堆的内存大小可以调节,存放的是new出来的实例和数组。堆内存逻辑上分为三部分:

  • 新生区(新生代):占1/3的堆空间,又包括伊甸区幸存0区(S0区,from区)幸存1区(S1区,to区),这三个区的内存比例为:伊甸区 : S0区 : S1区 = 8 : 1 : 1,而且,from区和to区不是固定的,谁空谁是to;
  • 养老区(老年代):占2/3的堆空间;
  • 永久区(永久代)/元空间:在java7中叫永久区,java8换成了元空间,永久代是使用JVM的堆内存,而元空间是使用本机的物理内存。存放的是JDK自带的class、interface等元数据,此区域不会进行垃圾回收,关闭JVM才会释放此区域内存。

永久区/元空间是逻辑上的划分,所以物理上堆内存就是新生区 + 养老区

2. 新生区、养老区以及GC介绍:

new出来的对象首先是在伊甸区,伊甸园嘛,生命初始的地方。伊甸区对象满了的话,就会触发轻GC,即YGC。触发YGC后,幸存者将会被复制到from区,伊甸区域会被清空。当伊甸区第二次触发YGC,就会扫描伊甸区和from区,对这两个区进行垃圾回收,经过这两次垃圾回收还存活的对象,就会被复制到to区,伊甸区和form区就会被清空,并且会把这些对象的年龄加一,当年龄达到阈值(默认是15,可通过-XX:MaxTenuringThreshold配置)时,这些对象就会被复制到养老区。此时,原先的form区是空的,原先的to区存放了历经两次YGC还存活的对象。上面说了,谁空谁就是to区,所以原先的from区现在变成了to区。这就是YGC的三个过程:复制 ---> 清空 ---> 互换

如果养老区也满了,就会在养老区触发full GC,如果多次full GC还是没能腾出空间来,就会内存溢出,即OOM异常。

四. JVM调优

1. 基本介绍:

JVM调优,其实就是堆参数的调整。

jvm调优

先说一下这张图,Minor GC就是上面说的YGC,Major GC就是full GC,S0和S1区有双向箭头,表示它们不是固定的,谁空谁就是to区(S1区)。

常见堆参数:

  • -Xms:堆内存(新生区+养老区)的初始大小,默认为物理内存的1/64
  • -Xmx:堆内存(新生区+养老区)的最大值,默认为物理内存的1/4
  • -Xmn:新生区的大小
  • -XX:PermSize:永久代的初始值(jdk1.7)
  • -XX:MaxPermSize:永久代的最大值(jdk1.7)
  • -XX:MaxTenuringThreshold:设置对象在新生代中存活的次数,默认是15

2. 堆内存调优简介:

上面说了xms和xmx的默认大小,怎么证明呢?用下面的代码可以证明:

public static void main(String[] args) {
    System.out.println(Runtime.getRuntime().availableProcessors()); // cpu核数
    double xms = Runtime.getRuntime().totalMemory() / 1024 / 1024; // xms
    double xmx = Runtime.getRuntime().maxMemory() / 1024 / 1024; // xmx
    System.out.println("xms:" + xms + "MB");
    System.out.println("xmx:" + xmx + "MB");
}

16G的笔记本,打印出来的xms大概是240MB,xmx大概是3600MB。为什么打印出来的更小?因为16G内存的笔记本,实际可用的内存是不到16G的。

xms和xmx,虽然一个是初始值一个最大值,但是,生产上这两个值一定要一样,为的是避免GC程序和应用程序争抢内存,导致可用内存忽高忽低;

怎么配置这两个值呢?eclipse和idea中,点击run configuration,可以配置VM arguments,将下面这串配置进去,就可以配置xms和xmx的大小,以及打印堆的信息:

-Xms1024M -Xmx1024M -XX:+PrintGCDetails

这里将xms和xmx都配置了1024,并且打印了堆信息。配置了这些再次运行上面那段代码,就会打印出如下信息(jdk1.8):

执行结果

从打印出来的信息可以发现,xms和xmx的配置生效了。从堆信息可以发现,堆确实上述由新生区、养老区和元空间构成,而且,新生区305664k加上养老区的699392k刚好等于981M,也说明了物理上堆只分为新生区和养老区,元空间是逻辑上的存在。

3. OOM异常:

上面说了,如果堆内存被占用满了,就会出现OOM异常。但是默认情况下xmx是内存的1/4,不容易出现这个异常。上面又说了配置这两个参数的方法,所以,我们可以将xms和xmx都配制成10M,然后再执行下面这段代码,就很容易出现OOM了。

String str = "hello";
while (true) {
    str += str + new Random().nextInt(88888888) + new Random().nextInt(99999999);
}

string类型用加号拼接,其实是new一个string然后用append方法的,所以这里会一直new对象,并且用了随机数,所以基本上都不重复的对象,不会从常量池里拿到。执行这段代码后,程序报了如下的错误:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOfRange(Arrays.java:3664)
    at java.lang.String.<init>(String.java:207)
    at java.lang.StringBuilder.toString(StringBuilder.java:407)

这就是OOM异常了,并且中报错之前,打印了GC相关信息,如下:

[GC (Allocation Failure) [PSYoungGen: 1855K->491K(2560K)] 1855K->991K(9728K), 0.0024182 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2168K->272K(2560K)] 2668K->1753K(9728K), 0.0022543 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1621K->272K(2560K)] 7032K->5682K(9728K), 0.0009517 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 272K->272K(2560K)] 5682K->5682K(9728K), 0.0007530 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 272K->0K(2560K)] [ParOldGen: 5410K->3170K(7168K)] 5682K->3170K(9728K), [Metaspace: 2752K->2752K(1056768K)], 0.0076104 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 40K->32K(2560K)] 5829K->5821K(9728K), 0.0007024 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 32K->32K(1536K)] 5821K->5821K(8704K), 0.0005743 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 32K->0K(1536K)] [ParOldGen: 5789K->4479K(7168K)] 5821K->4479K(8704K), [Metaspace: 2752K->2752K(1056768K)], 0.0062029 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 20K->32K(2048K)] 7118K->7130K(9216K), 0.0007837 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(2048K)] [ParOldGen: 7098K->3170K(7168K)] 7130K->3170K(9216K), [Metaspace: 2752K->2752K(1056768K)], 0.0054553 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 20K->0K(2048K)] 5809K->5789K(9216K), 0.0003817 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 5789K->5789K(9216K), 0.0003808 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 5789K->5789K(7168K)] 5789K->5789K(9216K), [Metaspace: 2752K->2752K(1056768K)], 0.0040374 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 5789K->5789K(9216K), 0.0003072 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 5789K->5775K(7168K)] 5789K->5775K(9216K), [Metaspace: 2752K->2752K(1056768K)], 0.0075411 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 

可以看到,这里先进行GC,然后是full GC,full GC也搞不定了,就报错了,这与上面讲的是一样的。那么打印的GC信息是什么意思呢?拿出一条来分析一下:

[
  GC (Allocation Failure)
  [PSYoungGen: 1855K->491K(2560K)]
  1855K->991K(9728K), 0.0011226 secs
] 
[
  Times: user=0.00 sys=0.00, real=0.00 secs
] 

我们一行一行的看,意思分别是:

表示由于分配失败发生GC;
GC发生在新生区(总内存2560K),GC之前该区用了1855K,GC之后用了491K;
堆内存总共9728K,GC之前堆内存占用1855K,GC之后占用991K,本次GC总共耗时0.0011226秒;
最后一行是GC时用户耗时、系统耗时和实际耗时

full GC的也是一样的:

[
  Full GC (Allocation Failure) 
  [PSYoungGen: 0K->0K(2048K)] 
  [ParOldGen: 5789K->5775K(7168K)] 5789K->5775K(9216K), 
  [Metaspace: 2752K->2752K(1056768K)], 0.0075411 secs
] 
[Times: user=0.01 sys=0.00, real=0.01 secs] 

这里的意思分别是:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JVM笔记八-堆参数调优

    JVM垃圾收集器(Java Garbage Collection)。本教程均在JDK1.8+HotSpot为例来讲解的.

    凯哥Java
  • JVM系列第11讲:JVM参数之堆栈空间配置

    JVM 中最重要的一部分就是堆空间了,基本上大多数的线上 JVM 问题都是因为堆空间造成的 OutOfMemoryError。因此掌握 JVM 关于堆空间的参数...

    陈树义
  • JVM本地方法栈&堆

    上一节我们介绍了程序计数器和Java虚拟机栈,今天我们一起了解一下关于本地方法栈和Java堆的相关知识。

    shysh95
  • 大数据技术之_30_JVM学习_01

    熟悉 JVM 架构与 GC 垃圾回收机制以及相应的 JVM 调优,有过在 Linux 系统下的调优经验。

    黑泽君
  • 01- JavaScript 调用堆栈

    JavaScript 引擎是一个单线程解析器,而单线程解析器由堆和单一调用栈组成。浏览器提供 Web APIs,比如:DOM,AJAX 和 定时器。

    公众号---人生代码
  • 必备知识,针对SpringBoot项目优化和Jvm调优!

    作为一名工程师,项目调优这事,是必须得熟练掌握的事情。在 SpringBoot 项目中,调优主要通过配置文件和配置 JVM 的参数的方式进行。

    java思维导图
  • SpringBoot 这样调优,让你的项目飞起来!

    关于这些设置的JVM参数是什么意思,请参考第二步中的oracle官方给出的调优文档。

    Java团长
  • SpringBoot深度调优,让你的项目飞起来!

    关于这些设置的JVM参数是什么意思,请参考第二步中的oracle官方给出的调优文档。

    良月柒
  • 让你的项目飞起来!SpringBoot 简单调优!

    原文链接:https://cnblogs.com/jpfss/p/9753215.html

    业余草

扫码关注云+社区

领取腾讯云代金券