前言

相信很多JAVA中高级的同学在面试的时候会经常碰到一个面试题

事实上,如果用户量不大的情况下,在你的代码还算正常的情况下,在工作中除非真正碰到与JVM相关的问题是少之又少,就算碰到了也是由公司的一些大牛去排查解决,那么我们又如何积累这方面的经验呢?下面由冲锅带大家一起来实践JVM的调优吧

Java堆溢出

package com.example.demo.jvm;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: Wang Chong
 * @Date: 2019/9/22 9:37
 * @Version: V1.0
 */
public class HeapOutMemoryTest {
    static class ChongGuo {

    }
    public static void main(String[] args) {
        List<ChongGuo> chongGuos = new ArrayList<>();
        while (true) {
            chongGuos.add(new ChongGuo());
        }
    }
}

运行结果如下:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9352.hprof ...
Heap dump file created [28701160 bytes in 0.122 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:261)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
    at java.util.ArrayList.add(ArrayList.java:458)
    at com.example.demo.jvm.HeapOutMemoryTest.main(HeapOutMemoryTest.java:18)
Disconnected from the target VM, address: '127.0.0.1:54599', transport: 'socket'

可以看到控制台出现java.lang.OutOfMemoryError: Java heap space的错误,这是为什么呢,首先先解释一下上面的运行参数

Connected to the target VM, address: '127.0.0.1:56433', transport: 'socket'
java.lang.OutOfMemoryError: Metaspace
Dumping heap to java_pid9232.hprof ...
Heap dump file created [1604635 bytes in 0.024 secs]
FATAL ERROR in native method: processing of -javaagent failed
Exception in thread "main" Disconnected from the target VM, address: '127.0.0.1:56433', transport: 'socket'

Process finished with exit code 1

虚拟机栈和本地方法栈栈溢出

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
  • 如果虚拟机在扩展栈无法申请到足够的内存空间,则抛出OutOfMemoryError异常

package com.example.demo.jvm;

/**
 * @Author: Wang Chong
 * @Date: 2019/9/22 19:09
 * @Version: V1.0
 */
public class StackOverflowTest {

    /**
     * 栈大小
     */
    private int stackLength = 1;

    /**
     * 递归压栈
     */
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
        StackOverflowTest stackOverflowTest = new StackOverflowTest();
        try {
            stackOverflowTest.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length is :" + stackOverflowTest.stackLength);
            throw e;
        }

    }

}

运行结果如下:

Exception in thread "main" stack length is :20739
java.lang.StackOverflowError
    at com.example.demo.jvm.StackOverflowTest.stackLeak(StackOverflowTest.java:20)
    at com.example.demo.jvm.StackOverflowTest.stackLeak(StackOverflowTest.java:20)

在VM参数-Xss参数未设置的情况下,该线程的内存支持的栈深度为20739,该测试结果与机器的内存大小有关,不过上面的第二点如何测试呢?正常来说如果是单线程,则难以测试内存泄露的情况,那么多线程呢?我们看一下以下测试代码:

package com.example.demo.jvm;

/**
 * @Author: Wang Chong
 * @Date: 2019/9/22 19:09
 * @Version: V1.0
 */
public class StackOOMTest implements Runnable{

    /**
     * 栈大小
     */
    private int stackLength = 1;

    /**
     * 递归压栈
     */
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
       while (true){
           StackOOMTest stackOverflowTest = new StackOOMTest();
           new Thread(stackOverflowTest).start();
       }

    }

    @Override
    public void run() {
       stackLeak();
    }
}

如果系统不假死的情况下,会出现Exception in thread "main" java.lang.OutOfMemoryError:unable to create new native thread

运行时常量池溢出

  • 字符型常量池溢出,在JAVA8中也是堆溢出,测试代码如下:
package com.example.demo.jvm;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: Wang Chong
 * @Date: 2019/9/22 19:44
 * @Version: V1.0
 */
public class RuntimePoolOOMTest {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i).intern());
        }
    }
}

结果如下:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:261)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
    at java.util.ArrayList.add(ArrayList.java:458)
    at com.example.demo.jvm.RuntimePoolOOMTest.main(RuntimePoolOOMTest.java:17)
Disconnected from the target VM, address: '127.0.0.1:50253', transport: 'socket'

证明字符常量池已经在Java8中是在堆中分配的。

方法区溢出

package com.example.demo.jvm;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;

/**
 * @Author: Wang Chong
 * @Date: 2019/9/22 19:56
 * @Version: V1.0
 */
public class MethodAreaOOMTest {
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(o,
                    objects));
            enhancer.create();
        }
    }

    static class OOMObject {

    }
}

运行结果如下:

java.lang.OutOfMemoryError: Metaspace
Dumping heap to java_pid8816.hprof ...
Heap dump file created [6445908 bytes in 0.039 secs]
Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
    at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:492)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114)
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
    at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
    at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:305)
    at com.example.demo.jvm.MethodAreaOOMTest.main(MethodAreaOOMTest.java:19)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459)
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336)
    ... 6 more
Caused by: java.lang.OutOfMemoryError: Metaspace
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    ... 11 more

Process finished with exit code 1

元空间内存报错,证明方法区的溢出与元空间相关。

  • 正常JVM调优都是针对堆内存和栈内存、元空间的参数做相应的改变
  • 元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
  • -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
  • -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
  • 字符串池常量池在每个VM中只有一份,存放的是字符串常量的引用值,存放在堆中

有更多的文章,请关注查看,更有面试宝典相送

01-22 05:27
查看更多