文章目录
JVM的内存结构
Jvm的内存分为五个区域:方法区,堆内存,程序计数器,虚拟机栈,本地方法栈。
对象关系分配最密切的就是堆和栈
栈[Stack]:线程独享
1.栈分为虚拟机栈VM Stack和本地方法栈Native Method Stack
2.存储:栈帧(方法),局部变量和运算过程的数据。
3.本地方法栈的作用和虚拟机线几乎一样,区别是本地方法栈存储本地方法,虚拟机栈存储java方法。
4.hot-spot虚拟机将本地方法栈和虚拟机栈合二为一。
5.生命周期:栈内存和线程同时创建,线程运行期间,栈帧出栈,入栈;线程结束的时候,栈内存销毁。
6.默认一个线程一个栈,内存是2MB.当你不创建线程的时候,默认有俩个线程,一个是主线程,一个是垃圾回收的线程。
堆[Heap]:线程共享
1.存储:所有类创建的对象,数组对象,垃圾回收器,主要负责回收这块内存区域。
2,线代垃圾收集器大部分基于分代收集理论设计,所以堆中会出现:
新生代,老年代,永久代,Eden空间,From Survivor空间,To Survivo空间。这些都是垃圾收集器的共同特性,或者说是设计风格,而非内存布局。
3.生命周期:虚拟机启动的时候创建堆内存,运行的时候,垃圾收集器回收堆内存中的对象,虚拟机关闭的时候销毁堆内存。
配置堆的空间大小:-xms -xmx(最小和最大内存空间)
方法区[Method Area]:线程共享
方法区也称之为编译代码存储区,元空间【Mate-Space】中的内容都在方法区里,是多个线程间,内存共享区域;
存储:虚拟机加载后的class字节码数据、静态变量、常量、JIT(just in time)编译器编译后的代码等数据。
目的就是区分Java堆中的永久数据,与堆类似,但是非堆【non-head】;
- JDK8之前,方法区普遍被认为是在堆内存中的"永久代";
- JDK6中,字符串常量池、静态变量在堆中的永久代,这个时候可以等价说永久代就是方法区了;
- JDK7中,HotSpot虚拟机,将字符串常量池、静态变量移出永久代,堆中给予单独存储区域;
- JDK8中,彻底废弃永久代的概念,改用元空间【Meta-space】来收纳静态数据、常量、字节码;
生命周期:虚拟机启动时被创建方法区;在虚拟机关闭是销毁方法区;
程序寄存器【Program Counter Register】:线程独享
字节码行号指示器
运行时常量池
运行时常量池【Runtime Constant Pool】是方法区的一部分。Class字节码文件中,除了有类、字段、方法、接口等描述信息,还有一项信息是常量池表,用于存放编译期生成的各种字面值与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是"Java虚拟机规范"中定义的内存区。
JDK1.4中引入了NIO,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。显著提高多线程中大数据操作的性能,因为避免了Java堆和Native堆之间来回复制数据。
GC垃圾回收
垃圾:内存中没有被引用的对象或者数据
(1)栈的空间在程序运行过程中会不断产生和销毁,当用户发出多条请求的时候,接口多次调用,创建很多线程。
(2)所有服务器加起来有一个堆内存,会new对象,开辟堆内存空间。
(3)用户请求结束后,栈内存释放,堆内存中的东西就变成垃圾。
一个应用程序对应一个JVM,一个服务器可以运行多个JVM。应用程序之间,如果对应同一个jvm,它们相当于进程和进程的关系,堆栈是隔离的。
APP=代码+JRE(JDK)+JVM
找到垃圾
引用计数算法
当这个对象引用都消失了,消失一个计数减一,当引用都消失了,计数就会变为0.此时这个对象就会变成垃圾。
堆内存中主要引用关系:
根可达算法(和根没有连着的就去除)
1.hotspot目前主要使用的垃圾回收算法,可以解决引用计数无法解决的问题。
2.根可达分析算法的基本思想:通过一系列名为"GC roots"的对象作为起始点,从这个被称为GC roots的对象开始,向下搜索,如果一个对象到GC roots没有任何引用链相连时,说明此对象不可用。可以解决循环引用问题。
3.GC roots为对象起始点,或者说tracing GC的"根集合"就是一组必须活跃的引用。
Java内存中的GC roots对象
(不会出现在堆中)
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
jdk:java develop kit [java开发工具套件]
包含jre,java.exe应用程序等.
消除垃圾
Mark-Sweep标记清除算法
缺点:导致内存空间碎片化.
优势:清除效率高,占用内存空间少
Copying拷贝算法
1.将可用内存按照容量分为大小相等的俩块,每次只使用其中一块.当这块的内存用完之后,把存活着的对象复制到另一块的上面.
2.再把已经使用过的内存空间一次性清理,使得每次都是对整个半区进行内存回收,内存分配的时候也不用考虑内存碎片等情况,只需要移动堆顶指针,按顺序分配内存即可.
优势:实现简单,没有碎片化,所有有用的空间都连接在一起,所有的空闲空间连接在一起
缺点:存在空间浪费.
Mark-Compact标记压缩算法
标记压缩算法,把存活对象拷贝到可回收的空间,然后把存活对象空间进行压缩,串联起来把未使用的对象空间串联起来,组成连续的内存空间,这样就不会有碎片。
缺点:性能较低,因为除了拷贝对象以外,还需要对象内存空间进行压缩,所以性能较低。
垃圾没有及时回收的后果
后果:
StackOverflowError栈溢出,一般由于递归过多,调用方法过多导致。
OutOfMemeryError堆内存溢出,即OOM,由于堆内存中没有被GC回收的对象过多导致。
解决内存泄漏
如果是内存泄漏,可进一步通过工具查看泄漏对象到GCRoots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。
- 导出Dump文件
- 对正在运行的程序使用jmap
- 分析Dump文件:如果Dump文件不太大的话,可以传到 http://heaphero.io/index.jsp 来分析
- 文件比较大,且想进行更加系统的分析,推荐使用MAT分析
内存泄露 内存溢出
内存泄露:对象已经没用了(不被任何程序逻辑所需要),还存在被根元素引用的情况,无法通过垃圾收集器进行自动回收,需要通过找出泄漏的代码位置和原因,才好确定解决方案;
内存溢出:内存中的对象都还存活着,JVM的堆分配空间不足,需要检查堆设置大小(-Xmx与-Xms),代码是否存在对象生命
周期太长、持有状态时间过长的情况。
用什么清除垃圾
垃圾回收器.
- 新生代回收器:Serial、ParNew、Parallel Scavenge
- 老年代回收器:Serial Old、Parallel Old、CMS
- 整堆回收器:G1
- 解析:新生代(年轻对象存储的空间) 老年代(多次回收还存在的对象的存储空间)
具体垃圾回收器
1)Serial(连续的)Collector
特点:单线程
- STW:
GC完才能继续
相反:Parallel并行 - 配置方式:-XX:+UseSerialGC
- 算法:Root Tracing & Mark-Sweep
- 场景:
吞吐量小:内存回收工作量不大
容忍延迟:不在意卡顿
单核、内存小:0~100M
2)Parallel(并行) Collector:
- 追求:高通吐量
- 特点:多线程,串行
- STW
和Serial一样(只不过并行)
Not Concurrent并发 - 配置方式:-XX:+UseParallelGC
- 算法:Root Tracing & Mark-Sweep
- 场景:吞吐量要求 > 延迟要求
3)Concurrent (并发) Collector:CMS
- 追求:低延时
- 特点:单核+多核,并发执行
- STW
Mark/Sweep/Compact/Copy等交替进行
Mark阶段(tracing阶段):利用多核(三色标记算法)
Sweep/Compact/Copy(单核):不需要STW - 配置方式:-XX:+UseConcMarkSweepGC
- 算法:Root Tracing & triColor Mark-Sweep & Copy/Compact & Aging
- 场景:
覆盖Serial/Parallel Collector的场景
需要减少 pause Time的场景
4)ZGC
- 追求:低延迟的GC
- 特点:
最大延迟时间几个ms
暂停时间不会随着堆大小、存活对象数目增加
8MB~16TB
5)Garbage-First (G1)
- Garbage First(简称G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。
- JDK 8以后G1收集器才被Oracle官方称为“全功能的垃圾收集器”(Fully-Featured Garbage Collector)。
- Garbage-First当今收集器技术发展的最前沿成果之一,G1是一款面向服务端应用的垃圾收集器。大内存,企业配置的垃圾收集器大多都是G1。JDK 9发布之日,G1宣告取代Parallel Scavenge加Parallel Old组合,成为服务端模式下的默认垃圾收集器,而CMS则沦落至被声明为不推荐使用(Deprecate)的收集器。
- 目标:大内存 兼顾:Latency、Throughput 替代:CMS
- 特点:
- 并行与并发:充分利用多CPU、多核环境下的硬件优势
- 多代收集:不需要其他收集器配合就能独立管理整个GC堆
- 空间整合:“标记—整理”算法实现的收集器,局部上基于“复制”算法不会产生内存空间碎片
- 可预测的停顿:能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒
G1收集器的主要步骤:
- 初始标记:标记一下GC Roots能直接关联到的对象,需要停顿线程,但耗时很短
- 并发标记:是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行
- 最终标记:修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录
- 筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划
G1中提供了三种模式垃圾回收模式,Young GC、Mixed GC 和 Full GC,在不同的条件下被触发。