• JVM详解-栈&堆


    栈&堆

    栈结构

    栈是一种数据结构。程序=数据结构+算法

    栈:先进后出,后进先出

    队列:先进先出(FIFO)

    Q:为什么main()方法先执行后结束

    A:先进栈,最后出

    JVM中的栈

    Oracle关于栈和栈帧提供了如下描述:

    每个JVM线程拥有一个私有的 Java虚拟机栈,创建线程的同时栈也被创建。一个JVM栈由许多帧组成,称之为"栈帧"。JVM中的栈和C等常见语言中的栈比较类似,都用于保存局部变量和部分计算结果,同时也参与方法调用和返回。

    如Oracle官方说明,每个线程拥有自己的私有栈,因此在多线程应用中将有多个栈,每个栈有自己的栈帧

    Java中的栈

    • 当一个新的线程创建时,JVM会为这个线程创建一个新的Stack。一个Java Stack在一个个独立的栈帧中存储了线程的状态。JVM只会在Java Stack中做两个操作:push 和 pop.
    • 一个线程当前正在执行的方法称之为线程的 当前方法,当前方法对应的栈帧称为 当前帧,当前方法所属的类称为 当前类,当前类的常量池称为 当前常量池。 在执行一个方法时,JVM会保存当前类和当前常量池的轨迹。当JVM执行 需要操作栈帧中数据的指令时,JVM会在当前栈帧进行处理。
    • 当一个线程执行一个Java方法时,JVM将创建一个新的栈帧并且把它push到栈顶。此时新的栈帧就变成了当前栈帧,方法执行时,使用栈帧来存储参数、局部变量、中间指令以及其他数据。

    栈内存,主管程序的运行,生命周期和线程同步

    线程结束,栈内存释放了,对于栈来说,不存在垃圾回收,一旦线程结束,栈就Over了!

    1、栈里面存放什么

    栈:8大基本类型 + 对象的引用 + 实例的方法

    2、栈运行原理

    栈帧

    image-20210201182918164

    栈满了:StackOverflowError

    堆(Heap)

    一个JVM只有一个堆内存,堆内存的大小是可以调节的,我们可以通过选项"-Xmx"和"-Xms"来进行设置。一旦堆区中的内存大小超过“-xmx"所指定的最大内存时,将会抛出**outofMemoryError(OOM)**异常。

    **Q:**类加载器读取类文件后,一般会把什么东西放到堆中?

    **A:**类,方法,常量,变量~保存我们所有引用类型的真实对象

    堆内存中还要细分为三个区域:

    • 新生区(伊甸园区)
    • 养老区
    • 永久区

    GC垃圾回收主要是在伊甸园区和养老区

    1. 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
    2. 非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。

    新生区

    年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。

    • 类:诞生和成长的地方,甚至死亡;
    • 伊甸园
    • 幸存者区(0,1)

    经过研究,99%的对象都是临时对象!

    img

    老年区

    新生区的幸存者经过多次存入养老区

    永久区

    这个区域是常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境。这个区域不存在垃圾回收!关闭虚拟机就会释放这个区域的内存。

    一个启动类加载了大量的第三方jar包。Tomcat部署了太多应用,大量动态生成的反射类。不断被加载,知道内存满就会出现OOM;

    • JDK1.6之前:永久代,常量池在方法区中
    • JDK1.7:永久代,但是慢慢退化了,提出了去永久代的概念,常量池在堆中
    • JDK1.8之后:无永久代,常量池在元空间,元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存

    img

    但是,元空间:逻辑上存在,物理上不存在!

    package com.draco.heapOverflow;
    
    /**
     * 元空间逻辑上存在,物理上不存在
     */
    public class SanQu {
        public static void main(String[] args) {
            // 返回jvm试图使用的最大内存
            long max = Runtime.getRuntime().maxMemory();
            // 返回jvm的初始化内存
            long total = Runtime.getRuntime().totalMemory();
    
            System.out.println("max="+max+"字节	"+(max/(1024*1024))+"MB");
            System.out.println("total="+total+"字节	"+(total/(1024*1024))+"MB");
    
            //默认情况下,试图分配的最大内存是电脑内存的1/4,而初始化的内存是1/64
            // -Xms1024m -Xmx1024m -XX:+PrintGCDetails
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    运行结果:

    image-20210201230739376

    当修改了VM选项后:-Xms1024m -Xmx1024m -XX:+PrintGCDetails,输出结果:

    image-20210201230953431

    让我们来算一笔账,

    新生区:305664k;养老区:699392k

    加在一起:1,005,056k,除以1024后 = 981.5MB,等于jvm试图分配的最大内存,所以说元空间逻辑上存在,物理上不存在。

    出现OOM

    1. 尝试扩大堆内存去查看内存结果

      -Xms1024m -Xmx1024m -XX:+PrintGCDetails

    2. 若不行,分析内存,看一下是哪个地方出现了问题(专业工具)

      • 能够看到代码第几行出错:内存快照分析工具,MAT(eclipse),Jprofiler
      • Dubug,一行行分析代码!(不现实)

    MAT,Jprofiler作用:

    • 分析Dump内存文件,快速定位内存泄漏
    • 获得堆中的数据
    • 获得大的对象

    VM options参数

    -Xms 设置初始化内存分配大小,默认1/64

    -Xmx 设置最大分配内存,默认1/4

    -XX:+PrintGCDetails 打印GC垃圾回收信息

    -XX:+HeapDumpOnOutOfMemoryError 生成oomDump文件

    -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
    -Xms1024m -Xmx1024m -XX:+PrintGCDetails
    
    • 1
    • 2
  • 相关阅读:
    测试老鸟总结,Allure测试报告-自动化测试详解,惊险避坑...
    求解为什么登录校园网账户的网页打不开
    Linux之基于HTTPS的静态网站
    Unity 安卓包激励视频广告退后台再进入APP广告消失
    dom——页面的渲染过程
    神经网络训练不起来,怎么办?
    C中volatile总结
    【设计模式】原型模式的浅拷贝和深拷贝
    HarmonyOS开发(一):开发工具起步
    有效涨点!用于低分辨率图像和小物体的新 CNN 模块SPD-Conv
  • 原文地址:https://blog.csdn.net/m0_67394006/article/details/126412494