优秀的编程知识分享平台

网站首页 > 技术文章 正文

JVM-虚拟机-参数设置及内存溢出

nanyue 2025-01-17 12:29:16 技术文章 1 ℃

JVM-虚拟机-参数设置及内存溢出

之前写了一篇关于JVM专题的初步学习,这次来学习虚拟机的参数含义对性能的影响及内存溢出问题。

上一篇补充

栈上分配

虚拟机提供的一种优化技术,基本思想是,对于线程私有的对象,将它打散分配在栈上,而不分配在堆上。好处是对象跟着方法调用自行销毁,不需要进行垃圾回收,可以提高性能。

栈上分配需要的技术基础,逃逸分析。逃逸分析的目的是判断对象的作用域是否会逃逸出方法体。

任何可以在多个线程之间共享的对象,一定都属于逃逸对象。

下面是测试代码

/**
 * @Author: 非鸽传书
 * @Date: 2021/7/28 20:16
 * @Description:
 * VM 配置
 *  -server -Xmx 10m -Xms 10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
 *  -XX:-UseTLAB
 *
 * -server JVM运行的模式之一, server模式才能进行逃逸分析, JVM运行的模式还有mix/client
 * -Xmx10m和-Xms10m:堆的大小
 * -XX:+DoEscapeAnalysis:启用逃逸分析(默认打开)
 * -XX:+PrintGC:打印GC日志
 * -XX:+EliminateAllocations:标量替换(默认打开): 是否允许
 * -XX:-UseTLAB 关闭本地线程分配缓冲
 * TLAB: ThreadLocalAllocBuffer, 线程本地分配缓存,事先在堆中开辟线程内存。(虚拟机在分配对象的时候也涉及到加锁,开启这个可以增加分配的效率。注意这里是在堆中所以其他线程仍然是可以访问这部分内存的)
 */
public class StackAllocatTest {

    public static class User{
        public int id = 0;
        public String name;
    }

    public static void allocUser(){
        User u = new User();
        u.id = 1;
        u.name = "非鸽传书";
    }

    public static void main(String[] args) {
        long begin = System.currentTimeMillis();

        for(int i=0; i< 1000000000; i++){
            allocUser();
        }
        long end = System.currentTimeMillis();
        System.out.println(end - begin);
    }

}

// 分别开启和关闭逃逸分析后对比程序运行时间及GC次数

栈上分配发生影响的参数就是三个,-server、-XX:+DoEscapeAnalysis和-XX:+EliminateAllocations,任何一个发生变化都不会发生栈上分配. 另外jdk1.8默认是开启栈上分配的.

对象分配

执行new指令:执行类加载过程

  • 为新生对象分配内存。为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”。Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“*空闲列表*
  • 处理多线程问题方式采用CAS配上失败重试的方式保证更新操作的原子性另一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块私有内存,也就是本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)

对象内存布局:

对象头(Header)

  • 对象自身的运行时数据 如 hash码 锁状态 偏向锁id等等
  • 类型指针,指向对象类可以通过这个找到是属于哪个类的实例

实例数据(Instance Data): 程序代码中所定义的各种类型的字段内容

对齐填充: 虚拟机占位. 在Hotspot中要求对象大小必须是8个字节的整数倍.


参数含义及参数设置

  • 栈:

-Xss 调整大小,例如-Xss256k

  • 堆:

-Xms:堆的最小值

-Xmx:堆的最大值;

-Xmn:新生代的大小;

-XX:NewSize;新生代最小值;

-XX:MaxNewSize:新生代最大值;

  • 方法区/永久代

jdk1.7及以前:-XX:PermSize;-XX:MaxPermSize;

jdk1.8以后:-XX:MetaspaceSize; -XX:MaxMetaspaceSize

jdk1.8以后大小就只受本机总内存的限制

如:-XX:MaxMetaspaceSize=3M

  • 直接内存

-XX:MaxDirectMemorySize

其他更多的命令可百度或者谷歌查询.

Tip: -XX表示运行参数


OOM

堆溢出

虚拟机参数:-Xms5m -Xmx5m -XX:+PrintGC

代码片段1:

public class OOMHeap {
    public static void main(String[] args) {
        List<Object> list = new LinkedList<>();
        int i = 0;
        try {
            while(true){
                i++;
                if(i%10000 == 0){
                    System.out.println(i);
                }
                list.add(String.valueOf(i));
            }
        } catch (Throwable e){
            System.out.println("i:" + i + "Error:" + e);
        }
    }
}

// 输出如下
[GC (Allocation Failure)  1020K->656K(5632K), 0.0016767 secs]
[GC (Allocation Failure)  1680K->879K(5632K), 0.0011192 secs]
[GC (Allocation Failure)  1903K->1403K(5632K), 0.0084106 secs]
10000
[GC (Allocation Failure)  2427K->2443K(5632K), 0.0041601 secs]
20000
30000
[GC (Allocation Failure)  3467K->3491K(5632K), 0.0048149 secs]
[Full GC (Ergonomics)  3491K->3256K(5632K), 0.0697227 secs]
40000
[Full GC (Ergonomics)  4280K->4257K(5632K), 0.0457067 secs]
50000
[Full GC (Ergonomics)  4770K->4769K(5632K), 0.0194403 secs]
[Full GC (Ergonomics)  4770K->4770K(5632K), 0.0190617 secs]
...
i:55128Error:java.lang.OutOfMemoryError: GC overhead limit exceeded

代码片段2:

public class OOMHeap {
    public static void main(String[] args) {
        String[] strs = new String[1000000000];
    }
}

// 输出:
[GC (Allocation Failure)  1020K->664K(5632K), 0.0013220 secs]
[GC (Allocation Failure)  1688K->879K(5632K), 0.0019375 secs]
[GC (Allocation Failure)  1495K->975K(5632K), 0.0021262 secs]
[GC (Allocation Failure)  975K->999K(5632K), 0.0009794 secs]
[Full GC (Allocation Failure)  999K->914K(5632K), 0.0077754 secs]
[GC (Allocation Failure)  914K->914K(5632K), 0.0003017 secs]
[Full GC (Allocation Failure)  914K->896K(5632K), 0.0103908 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
...
  • java.lang.OutOfMemoryError: GC overhead limit exceeded 一般是(某个循环里可能性最大)在不停地分配对象,但是分配得太多,把堆撑爆了。
  • java.lang.OutOfMemoryError: Java heap space一般是分配了巨型对象

栈溢出

虚拟机参数:-Xss 256k


public class OOMStackTest {

    private int deepen = 1;

    private void recurrence(){
        deepen++;
        recurrence();
    }

    public static void main(String[] args) {
        OOMStackTest test = new OOMStackTest();
        try {
            test.recurrence();
        } catch (Throwable e){
            System.out.println("stack deep = " + test.deepen);
            e.printStackTrace();
        }
    }
}

// 输出
stack deep = 20733
java.lang.StackOverflowError
    at com.jmmq.load.jim.jvm.OOMStackTest.recurrence(OOMStackTest.java:17)
    at com.jmmq.load.jim.jvm.OOMStackTest.recurrence(OOMStackTest.java:17)
    at com.jmmq.load.jim.jvm.OOMStackTest.recurrence(OOMStackTest.java:17)
    ...
// 递归参数如果增加参数列表则栈深度会变小,因为参数也会打包到栈帧中    


java.lang.StackOverflowError 一般的方法调用是很难出现的,如果出现了要考虑是否有无限递归。

直接内存溢出

虚拟机参数 -Xmx10M -XX:MaxDirectMemorySize=10M

public class OOMDirectMemory {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(1024*1024*11);
    }
}
// 输出
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.nio.HeapByteBuffer.<init>(HeapByteBuffer.java:57)
    at java.nio.ByteBuffer.allocate(ByteBuffer.java:335)
    at com.jmmq.load.jim.jvm.OOMDirectMemory.main(OOMDirectMemory.java:12)
最近发表
标签列表