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

147 阅读5分钟

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

一、方法区参数调优

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

image.png 核心参数设置如下: java -Xms2048M -Xmx1024M -Xss512k -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar microservice-eureka-server.jar 这是一个通用的设置。图中具体含义如下: • -Xms:堆空间最小值 • -Xmx:堆空间最大值 • -Xmn:新生代占堆空间大小 • -XX:MetaspaceSize:方法区(元空间)初始值 • -XX:MaxMetaspaceSize:方法区(元空间)最大值 • -Xss:每一个线程的空间大小 下面主要研究方法区参数设置 1.方法区(元空间)参数设置

image.png

在JDK8之前有个区域叫做永久代,在JDK8及以后改名字,叫元空间,这块内存空间占用的是直接物理内存。 元空间有一个特点:可以动态扩容。如果,我们没有设置元空间的上限,那么它可以扩大到整个内存。比如内存条是8G的,堆和栈分配了4G的空间,那么元空间最多可以使用4G。

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

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

• -XX:MetaspaceSize:元空间的初始空间大小,以字节为单位,默认是21M,达到该值就会触发full GC,同时收集器会对该值进行调整,如果释放了大量的空间,就适当降低该值,如果释放了很少的空间,提升该值,但最大不超过-XX:MaxMetaspaceSize 设置的值

比如:

初始值是21M,第一次回收了20M,那么只有1M没有被回收,下一次元空间会自动调整大小,可能会调整到15M

初始大小一人是21M,第二次回收发现回收了1M,有20M没有被回收,他就会自动扩大空间,可能扩大到30M,也可能是40M

-XX:MaxMetaspaceSize:这是元空间最大值,默认是-1,即不限制,或者说只受限于本地内存的大小

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

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

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

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

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

二。线程参数调优

-Xss512k 设置栈空间参数的

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

一个程序启动以后,系统为其分配的栈空间是固定的。理论上来说,如果每一个线程占用的空间少,那么就能分配更多的线程。否则相反。但这也是不确定的。还有和系统的cpu,内存有关系。 当线程分配的空间用完的时候,就会抛出栈溢出异常,下面来看一个例子

package com.jason;

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方法的时候,线程栈模型是什么样的呢?

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

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

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

.......

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

image.png 我们可以看到程序一共运行了19458次发生了栈溢出 当把栈空间设置小一些呢?比如256k

image.png 当运行到 2076 次的时候发生了栈溢出