7.JVM调优-方法区,堆,栈调优详解

2021-10-13 16:09:35 浏览数 (1)

通常我们都知道在堆空间新生代Eden区满了,会触发minor GC, 在老年代满了会触发full GC, 触发full GC会导致Stop The World, 那你们知道还有一个区域满了一会触发Full GC么?而且这个区域满了会直接影响我们的开发效率。

一、方法区参数调优

我们可以对运行时数据区的内存进行参数设置. 这是jvm调优的重点. 参数的变化将影响到整体效率

核心参数设置如下:

代码语言:javascript复制
java -Xms2048M -Xmx1024M -Xss512k -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar microservice-eureka-server.jar

这是一个通用的设置。图中具体含义如下:

  • -Xms:堆空间最小值
  • -Xmx:堆空间最大值
  • -Xmn:新生代占堆空间的大小
  • -XX:MetaspaceSize:方法区(元空间)初始值
  • -XX:MaxMetaspaceSize:方法区(元空间)最大值
  • -Xss:每一个线程的空间大小

下面主要研究方法区参数设置

1. 方法区(元空间)参数设置

在jdk8之前有个区域叫做永久代, 在jdk8及以后改名字了, 叫做元空间. 这块内存空间占用的是直接的物理内存.

元空间有一个特点: 可以动态扩容。如果, 我们没有设置元空间的上限, 那么他可以扩大到整个内存. 比如内存条是8G的, 堆和栈分配了4G的空间, 那么元空间最多可以使用4G。

我们可以通过参数来设置使用的元空间内存。

对于64位的JVM来说, 元空间默认大小是21M, 元空间的默认最大值是无上限的, 他的上限就是内存空间

  • -XX:MetaspaceSize: 元空间的初始空间大小, 以字节位单位, 默认是21M,达到该值就会触发full GC, 同时收集器会对该值进行调整, 如果释放了大量的空间, 就适当降低该值, 如果释放了很少的空间, 提升该值,但最到不超过-XX:MaxMetaspaceSize设置的值
代码语言:javascript复制
比如: 
初始值是21M, 第一次回收了20M, 那么只有1M没有被回收, 下一次, 元空间会自动调整大小, 可能会调整到15M
初始大小依然是21M, 第二次回收发现回收了1M, 有20M没有被回收, 他就会自动扩大空间, 可能扩大到30M,也可能是40M
  • -XX:MaxMetaspaceSize: 设置元空间的最大值, 默认是-1, 即不限制, 或者说只受限于本地内存的大小

由于调整元空间的大小需要full GC, 这是非常昂贵的操作, 如果应用在启动的时候发生大量的full GC, 通常都是由于永久代或元空间发生了大小的调整, 基于这种情况, 一般建议在JVM参数中将-XX:MetaspaceSize和-XX:MaxMetaspaceSize设置成一样的值, 并设置的比初始值还要大, 对于8G物理内存的机器来说, 一般会将这两个值设置为256M或者512M都可以

2. 建议设置元空间值, 如果不设置会怎么样?

不设置元空间默认就是21M, 很容易就会放满, 通常我们的war可能都是几十M, 甚至几个G. 如果我们在启动程序的时候, 会启动几分钟. 这很有可能是没有设置元空间的大小.

放满后会发生full GC, 然后在扩大一点元空间, 扩大到25M, 重新开始, 过了一会又放满了, 再次full GC, 在扩大一点, 元空间扩大到30M, 就这样一直发生full GC, 然后一直扩大元空间, 直到扩大的元空间大小合适, 不再发生full gc, 程序才会正常启动运行. 这是个很耗时耗性能的操作, 这样的full GC也是没有必要的.

如果项目启动较慢,多次重复启动,考虑是不是元空间设置不合理,或者内存不够导致。

二. 线程栈参数调优

代码语言:javascript复制
-Xss512k:设置栈空间参数的

这个参数就是用来设置栈空间的. 他是设置的一个线程栈占用的空间, 一个程序启动后可能有多个线程栈, 那么他们占用的空间都是512k。

一个程序启动以后,系统为其分配的栈空间是固定的。理论上来说,如果每一个线程占用的空间少,那么就能分配更多的线程。否则则相反。但这也不是确定的,还有和系统的cpu,内存有关系。

当线程分配的空间用完的时候,就会抛出栈溢出异常。下面来看一个例子

代码语言:javascript复制
package com.lxl.jvm;

public class StackOverflowTest {
    /**
     * jvm 设置-Xss128M, (默认是1M)
     */
    static int count = 0;
    public static void redo(){
        count   ;
        redo();
    }

    public static void main(String[] args) {
        try {
            redo();
        }catch (Throwable e) {
            e.printStackTrace();
            System.out.println(count);
        }
    }

}

这里定义了一个变量count, main方法里调用了redo()方法. 当我们执行main方法的时候, 线程栈模型是什么样的呢?

当程序执行到main方法的时候, 会在线程栈中开辟一个main方法的栈帧

继续执行, 执行到redo()的时候, 会在线程栈在开辟一块redo方法栈帧

redo方法里又调用了redo方法. 继续开辟一块redo方法栈帧,

.......

栈帧是占用内存空间的. 总有一个时刻会把栈内存消耗完. 就会报栈内存溢出了

我们看到程序一共运行了16979次发生了栈溢出.

当栈空间设置的小一些呢?比如256k

我们运行看效果

当运行到2079次的时候, 发生了栈溢出。

0 人点赞