JVM运行流程和结构
栈、本地方法栈、程序计数器这三个地方不会有垃圾回收,因为它们的生命周期是和线程同步的,随着线程的销毁,它们的内存也会自动释放。方法区和堆会进行垃圾回收,回收的对象是那些不存在任何引用的对象。
方法区
方法区存放了要加载类的信息(类名、修饰符等),静态变量、构造函数、final定义的常量、类中的字段和方法等。方法区是全局共享的,在一定条件下也会被GC。
**方法区中包含运行时常量池:**主要用于存储编译器生成的常量和引用,也存储运行时生成的常量,String类维护了一个常量池,如果调用的字符"hello"已经存在于常量池,就会直接返回它的地址值,如果没有,会创建一个"hello"的常量放入池中,并返回地址。
本地方法栈
凡是代码中有native修饰的方法会进入本地方法栈,调用它的时候会进入本地方法栈,再调用本地方法本地接口 (JNI),JNI的主要作用:扩展了Java的使用,融合不同的语言为Java所用。
本地方法栈只是登记native所修饰的方法。在实际调用的时候会通过JNI加载本地方法库中的方法。
栈
先进后出,如SpringBoot 启动器。以一个mian方法为入口,初始化相关SpringBoot组件。
栈存放局部变量表、操作数栈、动态链接、方法出口等信息。
局部变量表存放了八大基本类型,对象引用,实例的方法。他的内存空间在编译时就确定,运行时不再改变。
方法以压栈的形式存入栈中,当存在方法内部调用方法的情况,被调用的方法会压在站栈内存的上方,被调用的方法运行结束后会弹栈释放栈的内存,同理,在栈底部的方法在运行结束后也会依次弹栈。
堆
堆区的GC是最频繁的,堆中的所有线程共享,在虚拟机启动时创建,堆主要用于存放对象实例及数组,所有new出来的对象都存放在该区域。
由元空间(逻辑上存在堆,但实际占用内存不在堆)、年轻代=伊甸园区+幸存者0区+幸存者1区、老年代
程序计数器
内存分配在CPU中,程序员无法操作它。作用:JVM在解释字节码(.class)文件时,存储当前线程执行的字节码行号。字节码解释器工作时,就是通过改变程序计数器的值来取下一条要执行的指令,分支、循环、跳转等基础功能都是依赖程序计数器完成。
每个程序计数器都只能记录一个线程的行号,因此它是线程是私有的。
当程序正在执行的是一个java方法,则程序计数器记录的是正在执行的虚拟机字节码指令地址,如果执行的native方法,则程序计数器为空。
调优:
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
-Xms 设置初始化内存分配大小
-Xmx 设置最大分配内存
-XX:+PrintGCDetails 打印GC垃圾回收信息
-XX:+HeapDumpOnOutOfMemoryError 导出解析文件使用Jprofiles分析故障原因
Car Class 相当于是模板,new car()获取到的对象是Car Class具体的实现,不同的car的实例获取到的Class是同一个Car Class[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
查找算法
引用计数法
每个对象被引用一次,计数器计数+1;失去引用,计数-1。当对象的引用计数在一段时间为0后,对象将被回收。但是这个算法并不能解决对象循环引用的问题。(对象循环引用:多个对象互相引用,但是他们在程序中已经没用处。),因此JVM并没有采用这种算法搜索垃圾对象。
四种引用:强引用、软引用、弱引用、虚引用
强引用:new出来的对象都属于强引用,GC无论如何都不会回收,即使抛出OOM异常。
软引用:只有当JVM内存不足时才会回收。
弱引用:只要GC,立马就被回收,不管内存是否充足。
虚引用:忽略不计,JVM完全不会在乎它,它的唯一作用是做一些跟踪记录,辅助finalize函数的使用。
什么样的类需要被回收?
该类的所有实例都已被回收。
加载类的ClassLoad已经被回收。
该类对应的反射类java.lang.Class对象没有被任何地方引用。
根搜索算法
从GC ROOTS出发,向下搜索,如果一个对象没有达到GC ROOTS,说明该对象不再被引用,将会被回收。完美的解决了引用计数法的缺陷。如图中的object5、object6、object7
GC算法
复制算法、标记清除法、标记压缩算法
复制算法
JVM内存空间模型,新创建的对象会放在伊甸园区,当此区内存满时,会执行轻GC:此过程会将伊甸园区的垃圾对象清除,并将存活对象放入幸存者TO区,此时的幸存者FROM区未存放任何对象,当再次发生轻GC时,会将伊甸园区的对象和幸存者TO区的垃圾对象清除,两者将存活的对象整理后放入幸存者FROM区,此时之前的幸存者TO区内为空,将变为幸存者FROM区,而之前的存放对象的幸存者FROM区存放存活的对象将变为幸存者TO区。每次发生轻GC,都会将存活的对象标记+1,直到标记为15时,会将对象放入老年代。当老年代对象达到占用一定的内存大小时,会执行FullGC,此过程将发生STW。
缺陷:幸存者区的对象很多时,在复制到另一幸存者区时,消耗会比较大。内存需要专门开辟复制对象的空间,内存的利用率相对较小。
标记清除法
遍历整个内存将存活的对象标记,第二次遍历整个内存将未标记的垃圾对象清除。
缺陷:会有内存碎片,由于内存的使用是在位于内存中的最后一个对象往后放置新的对象,这样在清除后,内存区会存在不连续的空间,导致内存的使用率不高。
标记整理法(该算法适用于老年代)
遍历整个内存将存活的对象标记,第二次遍历整个内存将未标记的垃圾对象清除,再将存活的对象整理,使内存中存放的对象与对象之间不存在碎片空间。
缺陷:效率低。
内存效率:复制算法>标记清除法>标记整理法
内存整齐度:复制算法=标记整理法>标记清除法
内存利用率:标记整理法=标记清除法>复制算法
垃圾收集器
串行收集器(Serial GC)
JAVA SE5和JAVA SE6中客户端虚拟机采用的默认配置,比较适合只有一个处理器,在串行处理器收集器中minor和minor GC过程都是用一个线程进行回收的。他最大的特点是在进行垃圾回收时,需要对所有正在执行的线程暂停(STW),一般它的暂停时间为几十毫秒,对于实时性要求不那么高的程序是可以接受的,是客户端(Client)级别默认的GC方式。
ParNew GC
基本和Serial GC一样,但本质区别是加入了多线程机制,提高了效率,因为它可以被用于服务端(server)上,它同时也可以与CMS GC配合。
Parallel Scavenge GC
在整个扫描和复制过程采用多线程的方式,适用于多CPU、对暂停时间要求较短的应用。是server级别的默认GC方式。
CMS (Concurrent Mark Sweep)收集器
该收集器主要解决了Serial GC STW的问题,以达到最短回收时间。常见的B/S架构的应用就适合此种收集器,因为其高并发、高响应的特点,CMS是基于标记-清除算法实现的。
CSM优点:并发收集,低停顿。
缺点:
- 对CPU资源非常敏感,在并发阶段虽然不会导致用户停顿,但是会占用CPU资源而导致应用程序变慢,总吞吐量下降。
- CMS收集器无法处理浮动垃圾,可能会“Concurrnet Mode Failure(并发模式失败)”,失败导致另一次的Full GC
- CMS基于标记-清除算法完成,所以会产生内存碎片。
G1收集器
基于标记-整理算法实现,不会产生内存碎片还可以精确地控制停顿时间。
Parallel Old 收集器
Parallel Old 是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
RTSJ垃圾回收器
适用于java实时变成。
文章部分来源于:https://blog.csdn.net/anjoyandroid/article/details/78609971