JVM-栈、堆

重要理论

  1. 栈管运行,堆管存储
  2. 程序 = 算法 + 数据结构
  3. 队列(FIFO)先进先出
  4. 栈(FILO)先进后出
  5. java方法在栈内就是栈帧(就是栈的一个格子)

基本介绍

  1. 栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,生命周期是跟随现成的生命期,线程结束占内存也就释放,对于栈来说不存在垃圾回收问题,是线程私有的。
  2. 8中基本类型的变量+对象的引用变量(对象名)+实例方法都是在函数的栈内存中分配

主要存储内容

栈帧中主要保存3类数据:

  1. 本地变量:输入参数和输出参数以及方法内的变量
  2. 栈操作:记录出栈、入栈的操作
  3. 栈帧数据:包括类文件、方法等

栈运行原理

每个方法执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。栈的大小和具体JVM实现有关,通常在265K~765K,约等于1KB
在这里插入图片描述
其中图例的曲线(父帧->方法索引)父帧存取的内容可理解为PC寄存器存储的内容

java.lang.StackOverflowError

  1. 这是一个错误,是java.lang.Error的子类(异常是Exception的子类)
  2. 栈溢出,常出现在递归中

栈帧存储的内容

  1. 局部变量表
  2. 操作数帧
  3. 指向运行时常量池的引用
  4. 方法返回地址
  5. 动态链接

栈、堆、方法区的交互关系

在这里插入图片描述

基本

  1. 一个JVM实例只存在一个堆内存,堆内存的大小可以调节。
  2. 类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行其执行
  3. 堆内存分为三部分:
    • 新生区(Young/New)
    • 养老区(Old/Tenure)
    • 永久区(Perm)

堆结构

在这里插入图片描述

  1. java8之后将永久存储区变为元空间,即7是永久代,8是元空间
  2. 幸存者0 是 From区,幸存者1 是 To区,但是由于MinorGC(垃圾收集)的机制,有时候幸存者0 是To区,幸存者1 是from区,下文解释
  3. 物理上,只有新生区和老年区
  4. 新生区中,伊甸区:From区:To区 = 8 : 1 :1
  5. 堆内存中,新生区 : 老年区 = 1:2

MinorGC机制

在这里插入图片描述

  1. 复制:eden、SurvivorFrom复制到SurvivorTo,年龄+1
    • 首先,当Eden区满的时候会触发第一次GC,把活着的对象拷贝到From
    • 当eden再次触发GC的时候会扫描eden和from区,对这两个区域进行垃圾回收,活着的对象复制到to
    • 把活下来的对象年龄+1,若达到老年标准,则复制到老年区
  2. 清空:清空eden、from中的对象,也就是复制之后谁空谁是To
  3. 交换:To 和 From交换
    • 最后,To 和 From互换,原To成为下一次GC时的From
    • 不分对象会在from 和 to区域复制来复制去,如此交换15次还是存货,就存入老年代
    • 交换次数JVM参数MaxTenuring Threshold决定,默认值15

堆溢出

  1. java.lang.OutOfMemoryError:java heap soace异常

  2. 原因有二:

    • java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整
    • 代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)

永久代

在这里插入图片描述

  1. 虽然JVM规范将方法区描述为一个堆的逻辑部分,但它还有一个别名叫做Non-Heap(非堆),目的就是和堆分开
  2. 永久代是方法区的一个实现
  3. 永久存储区是一个常驻内存区域,用于存放JDK自身所懈怠的Class,Interface的元数据(比如rt.jar,项目导进去的jar),也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,必须关闭JVM才会释放此区域所占用的内存

永久代和元空间

  1. 永久代是用的是JVM堆内存,但是java8以后的元空间并不在虚拟机中而是使用本机物理内存
  2. 默认情况下,元空间的大小仅受本地内存限制
  3. 类的元数据放入native memory,字符串池和类的静态变量放入java堆中
  4. 元空间可以加在多少泪的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制

堆内存调优

堆内存优化简介

-Xms 设置初始分配大小,默认为物理内存的“1/64”
-Xmx 最大分配内存,默认为物理内存的"1/4"
-XX:+PrintGCDetails 输出详细的GC处理日志

查看参数

//Runtime对象,即运行时记录着各种参数的对象
int availableProcessors = Runtime.getRuntime().availableProcessors();//cpu处理器数量
System.out.println(availableProcessors);

//开发时这两个值必须一样:避免内存忽高忽低(GC和程序抢内存),导致程序停顿
long maxMemory = Runtime.getRuntime().maxMemory();//堆内存最大大小
long totalMemory = Runtime.getRuntime().totalMemory();//堆内存默认初始大小
System.out.println("-Xmx:MAX_MEMORY = "+maxMemory+"(字节)、"+(maxMemory/(double)1024/1024)+"MB");
System.out.println("-Xms:TOTAL_MEMORY = "+totalMemory+"(字节)、"+(totalMemory/(double)1024/1024)+"MB");

设置参数

IDEA设置参数

Run -> Exit Configuration -> VM options:

-Xms10m -Xmx10m -XX:+PrintGCDetails

堆溢出

  1. Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

在这里插入图片描述

输出详细GC收集日志信息

普遍规律:[ 名称 : GC前内存占用 -> GC后内存占用 (该区内存总大小) ]

GC

[GC (Allocation Failure) [PSYoungGen: 96K->64K(2560K)] 5198K->5166K(9728K), 0.0003285 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
  1. [PSYoungGen: 96K->64K(2560K)]:
    GC类型:YoungGC前新生代内存占用 -> YoungGC后内存占用(新生代总大小)
  2. 5198K->5166K(9728K)
    YoungGC前JVM堆内存占用 -> YoungGC后JVM堆占用(JVM堆总大小),YoungGC耗时
  3. 0.0003285 secs
    YoungGC耗时
  4. Times: user=0.00 sys=0.00, real=0.00 secs
    用户耗时,系统耗时,实际耗时

FullGC

[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 4015K->3996K(7168K)] 4015K->3996K(8704K), [Metaspace: 3247K->3247K(1056768K)], 0.0072710 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
  1. [ParOldGen: 4015K->3996K(7168K)]
    GC类型(Old):GC前Old区内存占用 -> GC后Old区内存占用(Old区总大小)
  2. [Metaspace: 3247K->3247K(1056768K)]
    GC类型(Perm):GC前Perm区内存占用 -> GC后Perm区内存占用(Perm区总大小)

GC4大算法

GC算法总体概述

  1. 普通GC(minor GC):只针对新生代区域的GC,指发生在新生代的垃圾收集动作,因为大多数Java对象存活率都不高,所以Minor GC非常频繁,一般回收速度也比较快。
  2. 全局GC(major GC or Full GC):指发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC(但并不是绝对的),Major GC的速度一般要比Minor GC 慢上10倍以上

4算法

引用计数法

  1. 记录每个变量的被引用数量,为0时GC该对象
  2. 现一般不用该算法,站空间,且循环引用不好处理

复制算法

  1. 年轻代中使用的是MinorGC,这种GC算法采用的是复制算法
  2. 原理
    • 从根集合(GC root)开始,通过Ttacing从From中找到存货对象,拷贝到To中
    • From、To交换身份,下次内存分配从To开始
  3. 优点:不会产生内存碎片
  4. 缺点:耗空间

在这里插入图片描述

标记清除

  1. 老年代一般是由标记清除或者是标记清除与标记整理的混合实现
  2. 原理:
    • 从根节点开始标记遍历所有的GC Roots,先标记出要回收的对象
    • 遍历整个堆,把标记的对象清除
  3. 优点:节省空间
  4. 缺点会产生内存碎片(对象之间内存空间不连续)

标记压缩

  1. 老年代一般是由标记清除或者是标记清除与标记整理的混合实现
  2. 原理
    • 标记,清除:同标记清除
    • 压缩:再次扫描,并往一段滑动存活对象
  3. 优点:节省空间,且不会产生内存碎片
  4. 缺点:需要移动对象的成本,效率较低,耗时多
  5. 标记-清除-压缩:多次标记清除之后再标记压缩

小总结

  1. 内存效率:复制>标记清除>标记整理
  2. 内存整齐度:复制=标记整理>标记清除
  3. 内存利用率:标记清除=标记整理>复制

分代收集算法

  1. 次数上频繁收集Young区,特点是区域相对较小,对象成活率低:复制算法
  2. 次数上较少收集Old区,特点是区域较大,对象成活率高:标记清除或者标记清除与标记整理的混合实现
  3. 基本不动元空间

AAAAA

发布了45 篇原创文章 · 获赞 1 · 访问量 555

猜你喜欢

转载自blog.csdn.net/BNMZY/article/details/105190817