文章目录
栈
重要理论
- 栈管运行,堆管存储
- 程序 = 算法 + 数据结构
- 队列(FIFO)先进先出
- 栈(FILO)先进后出
- java方法在栈内就是栈帧(就是栈的一个格子)
基本介绍
- 栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,生命周期是跟随现成的生命期,线程结束占内存也就释放,对于栈来说不存在垃圾回收问题,是线程私有的。
- 8中基本类型的变量+对象的引用变量(对象名)+实例方法都是在函数的栈内存中分配
主要存储内容
栈帧中主要保存3类数据:
- 本地变量:输入参数和输出参数以及方法内的变量
- 栈操作:记录出栈、入栈的操作
- 栈帧数据:包括类文件、方法等
栈运行原理
每个方法执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。栈的大小和具体JVM实现有关,通常在265K~765K,约等于1KB
其中图例的曲线(父帧->方法索引)父帧存取的内容可理解为PC寄存器存储的内容
java.lang.StackOverflowError
- 这是一个错误,是java.lang.Error的子类(异常是Exception的子类)
- 栈溢出,常出现在递归中
栈帧存储的内容
- 局部变量表
- 操作数帧
- 指向运行时常量池的引用
- 方法返回地址
- 动态链接
栈、堆、方法区的交互关系
栈
基本
- 一个JVM实例只存在一个堆内存,堆内存的大小可以调节。
- 类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行其执行
- 堆内存分为三部分:
- 新生区(Young/New)
- 养老区(Old/Tenure)
- 永久区(Perm)
堆结构
- java8之后将永久存储区变为元空间,即7是永久代,8是元空间
- 幸存者0 是 From区,幸存者1 是 To区,但是由于MinorGC(垃圾收集)的机制,有时候幸存者0 是To区,幸存者1 是from区,下文解释
- 物理上,只有新生区和老年区
- 新生区中,伊甸区:From区:To区 = 8 : 1 :1
- 堆内存中,新生区 : 老年区 = 1:2
MinorGC机制
- 复制:eden、SurvivorFrom复制到SurvivorTo,年龄+1
- 首先,当Eden区满的时候会触发第一次GC,把活着的对象拷贝到From
- 当eden再次触发GC的时候会扫描eden和from区,对这两个区域进行垃圾回收,活着的对象复制到to
- 把活下来的对象年龄+1,若达到老年标准,则复制到老年区
- 清空:清空eden、from中的对象,也就是复制之后谁空谁是To
- 交换:To 和 From交换
- 最后,To 和 From互换,原To成为下一次GC时的From
- 不分对象会在from 和 to区域复制来复制去,如此交换15次还是存货,就存入老年代
- 交换次数JVM参数MaxTenuring Threshold决定,默认值15
堆溢出
-
java.lang.OutOfMemoryError:java heap soace异常
-
原因有二:
- java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整
- 代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)
永久代
- 虽然JVM规范将方法区描述为一个堆的逻辑部分,但它还有一个别名叫做Non-Heap(非堆),目的就是和堆分开
- 永久代是方法区的一个实现
- 永久存储区是一个常驻内存区域,用于存放JDK自身所懈怠的Class,Interface的元数据(比如rt.jar,项目导进去的jar),也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,必须关闭JVM才会释放此区域所占用的内存
永久代和元空间
- 永久代是用的是JVM堆内存,但是java8以后的元空间并不在虚拟机中而是使用本机物理内存
- 默认情况下,元空间的大小仅受本地内存限制
- 类的元数据放入native memory,字符串池和类的静态变量放入java堆中
- 元空间可以加在多少泪的元数据就不再由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
堆溢出
- 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]
- [PSYoungGen: 96K->64K(2560K)]:
GC类型:YoungGC前新生代内存占用 -> YoungGC后内存占用(新生代总大小) - 5198K->5166K(9728K)
YoungGC前JVM堆内存占用 -> YoungGC后JVM堆占用(JVM堆总大小),YoungGC耗时 - 0.0003285 secs
YoungGC耗时 - 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]
- [ParOldGen: 4015K->3996K(7168K)]
GC类型(Old):GC前Old区内存占用 -> GC后Old区内存占用(Old区总大小) - [Metaspace: 3247K->3247K(1056768K)]
GC类型(Perm):GC前Perm区内存占用 -> GC后Perm区内存占用(Perm区总大小)
GC4大算法
GC算法总体概述
- 普通GC(minor GC):只针对新生代区域的GC,指发生在新生代的垃圾收集动作,因为大多数Java对象存活率都不高,所以Minor GC非常频繁,一般回收速度也比较快。
- 全局GC(major GC or Full GC):指发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC(但并不是绝对的),Major GC的速度一般要比Minor GC 慢上10倍以上
4算法
引用计数法
- 记录每个变量的被引用数量,为0时GC该对象
- 现一般不用该算法,站空间,且循环引用不好处理
复制算法
- 年轻代中使用的是MinorGC,这种GC算法采用的是复制算法
- 原理
- 从根集合(GC root)开始,通过Ttacing从From中找到存货对象,拷贝到To中
- From、To交换身份,下次内存分配从To开始
- 优点:不会产生内存碎片
- 缺点:耗空间
标记清除
- 老年代一般是由标记清除或者是标记清除与标记整理的混合实现
- 原理:
- 从根节点开始标记遍历所有的GC Roots,先标记出要回收的对象
- 遍历整个堆,把标记的对象清除
- 优点:节省空间
- 缺点会产生内存碎片(对象之间内存空间不连续)
标记压缩
- 老年代一般是由标记清除或者是标记清除与标记整理的混合实现
- 原理
- 标记,清除:同标记清除
- 压缩:再次扫描,并往一段滑动存活对象
- 优点:节省空间,且不会产生内存碎片
- 缺点:需要移动对象的成本,效率较低,耗时多
- 标记-清除-压缩:多次标记清除之后再标记压缩
小总结
- 内存效率:复制>标记清除>标记整理
- 内存整齐度:复制=标记整理>标记清除
- 内存利用率:标记清除=标记整理>复制
分代收集算法
- 次数上频繁收集Young区,特点是区域相对较小,对象成活率低:复制算法
- 次数上较少收集Old区,特点是区域较大,对象成活率高:标记清除或者标记清除与标记整理的混合实现
- 基本不动元空间