嘘~ 正在从服务器偷取页面 . . .

Jvm 专题 Stack & Heap


Stack & Heap

一、Stack

1、栈是什么

JVM中,栈分虚拟机栈和本地方法栈,是负责程序运行的地方。

主要特点是后进先出/先进后出。

栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命周期是跟随线程的生命周期的,线程结束,栈内存自动释放,所以栈没有垃圾回收问题。

而且线程有自己的运行栈,栈是属于线程私有。

常见的基本数据类型的变量和对象的引用变量都是在函数的栈内存中分配。

2、栈里面有什么

栈帧里主要保存三类数据:

  • 本地变量(Local Variables):输入参数和输出参数以及本地变量,如基本数据类型或引用类型。
  • 栈操作(Operand Stack):记录出栈、入栈的操作,方法入口。
  • 栈帧数据(Frame Data):类文件、方法等待。

3、运行原理

栈中的数据都是以栈帧的格式存在,栈帧是一个内存区域快,是一个数据集,是一个有关Method 和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧SF1,并被压入到栈中,方法A又调用方法B,于是产生栈帧SF2也会被压入栈,方法B又调用方法C,于是产生栈帧SF3也会被压入栈…

等待最后压入的栈方法执行完毕,先弹出(释放)栈帧SF3,再弹出(释放)栈帧SF2,最后弹出(释放)栈帧SF1

遵循“先进后出 / 后进先出”的原则。

每个方法从调用到执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

栈的大小和具体JVM实现有关,通常约1M左右。

4、StackOverflowError

应用程序运行时,某个线程的线程栈空间被耗尽,没有足够内存空间分配给新创建的栈帧,导致抛出错误。

引发 StackOverFlowError 的常见原因有以下几种:

  • 无限递归循环调用(最常见)。

  • 执行了大量方法,导致线程栈空间耗尽。

  • 方法内声明了海量的局部变量。

  • native 代码有栈上分配的逻辑,并且要求的内存还不小,比如 java.net.SocketInputStream.read0 会在栈上要求分配一个 64KB 的缓存(64位 Linux)。

  • 以上几种的可能混合存在。

Demo :

package com.xiaocai.jvm.oom;

/**
 * @description: TODO 功能角色说明:
 * TODO 描述: 模拟 java.lang.StackOverflowError 错误
 * @author: 张小菜
 * @date: 2020/11/4 23:59
 * @version: v1.0
 */
public class StackOverflowDemo {

    public void test(){
        test();
    }
    public static void main(String[] args) {
        new StackOverflowDemo().test();
    }
}

关于Jstack命令

c:\Users\xxx> jstack -help
Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message

参数说明

-F:当正常输出的请求不被响应时,可以强制输出线程堆栈信息。

-l:除堆栈信息外,还会打印出额外的锁信息,比如在发生死锁时可以用jstack -l pid来观察锁持有情况

-m:如果调用到本地方法的话,可以显示C/C++的堆栈

jstack -l  12308 

几种线程状态

  • Deadlock(重点关注),死锁线程,一般指多个线程调用间,进入相互资源占用,导致一直等待无法释放的情况。

  • Runnable,一般指该线程正在执行状态中,该线程占用了资源,正在处理某个请求,有可能正在发送SQL到数据库执行,有可能在对某个文件操作,有可能进行数据类型等转换,总之正在执行线程任务。

  • Waiting on condition(重点关注),等待资源,或等待某个条件的发生。具体原因需结合实际堆栈来分析。

  • Waiting on monitor entry(重点关注),等待进入一个临界区

死锁Demo

public class DeadLock implements Runnable {
    public int flag = 1;
    //静态对象是类的所有对象共享的
    private static Object o1 = new Object();
    private static Objecto2 = new Object();

    @Override
    public void run() {
        System.out.println("flag " + flag);
        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("1");
                }
            }
        }
        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("0");
                }
            }
        }
    }

    public static void main(String[] args) {
        DeadLock td1 = new DeadLock();
        DeadLock td2 = new DeadLock();
        td1.flag = 1;
        td2.flag = 0;
        //td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。
        //td2的run()可能在td1的run()之前运行
        new Thread(td1).start();
        new Thread(td2).start();
    }
}

包括元空间(永久代)。

在Heap堆内存中,实际总物理内存大小为年轻代+老年代。逻辑上除了年轻代和老年代还包括元空间(永久代)。

常用基本参数:

  • -Xms :设置初始堆内存大小,默认为物理内存的 1/64。
  • *-Xmx *:最大堆内存大小,默认为物理内存的1/4。
  • *-Xss *:设置每个线程的堆栈大小。 在相同物理内存下,减小这个值能生成更多的线程。一般默认为512k-1024K。
  • -XX:NewSize:新生代/年轻代初始化堆内存。
  • -XX:MaxNewSize:新生代/年轻代最大堆内存。
  • -XX:MetaspaceSize:元空间初始内存。相当于-XX:PermSize
  • -XX:MaxMetaspaceSize:元空间最大内存。相当于-XX:MaxPermSize

验证打印GC信息参数

-verbose:gc 
-XX:+PrintGC  
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:C:\gc.log

前两个没什么太大区别,java命令里可以看到verbose参数。

个人比较喜欢使用 -XX:+PrintGCDetails

命令查看默认大小

java -XX:+PrintFlagsInitial

程序获取堆内存大小Demo:

package com.xiaocai.jvm.heap;

/**
 * @description: TODO 功能角色说明:
 * TODO 描述:  
 * @author: 张小菜
 * @date: 2020/11/4 21:40
 * @version: v1.0
 */
public class HeapTestDemo {
    public static void main(String[] args) {
        long maxMemory = Runtime.getRuntime().maxMemory();
        long totalMemory = Runtime.getRuntime().totalMemory();

        long freeMemory = Runtime.getRuntime().freeMemory();
        System.out.println("maxMemory: "+maxMemory / (double) 1024 /1024 +"Mb");
        System.out.println("totalMemory: "+totalMemory / (double) 1024 /1024 +"Mb");
        System.out.println("freeMemory: "+freeMemory / (double) 1024 /1024 +"Mb");

    }
}


文章作者: Small-Rose /张小菜
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明来源 Small-Rose /张小菜 !
评论
  目录