一、jvm解析
Java程序都是在JVM中运行, 实现了Java语言的跨平台性。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的,Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码,class文件),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令来执行。这就是Java的能够“一次编译,到处运行”的原因。
二、jVM体系结构
jvm可以执行多种语言,
每种语言都有一套规范。
java语言是一套规范,java虚拟机是一套规范。
java语言编译之后就变成了字节码文件,可以在jvm上面运行。
为什么要将jvm进行分区?
分区:方便管理。
jvm体系结构详解:
1、程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
【注】:每个线程都应该有一个,属于线程私有
2、Java 虚拟机栈
它描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame )用于存储局部变量表、操作栈、动态链接、方法出口等信息。
与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,如果不是私有的话,就会把多个线程的方法都进入同一个栈,显然是不可能的。,它的生命周期与线程相同,即在线程创建时创建,线程结束时栈内存也随之释放。
3、本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法(c或c++使用)服务。
4、Java 堆(jdk1.8后常量池在堆里)
此内存区域的唯一目的就是存放对象实例
一个JVM实例只存在一个堆,堆内存的大小是可以调节的。类加载器加载了类文件之后,需要把类放到堆内存中,以便执行器执行。
堆内存是线程共享的。
5、方法区
方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、静态变量、即时编译器编译后的代码等数据。
三、垃圾回收机制(GC)
首先,我们需要思考的几个问题?
- 什么是GC?
- Why need GC?
- 哪些内存需要回收?
- 什么时候回收?
3.1 现实内存管理和自动内存管理
显式内存管理(C/C++)
内存管理是程序开发者的职责
显示管理常见问题
内存泄露:内存空间已经申请,使用完毕后未主动释放。会一直占用内存。
野指针:使用了一个指针,但是该指针指向的内存空间已经被Free释放掉。指向了个未知的区域
自动内存管理(Java /C#/ 一些脚本语言)
内存空间是由一个叫做垃圾回收器的程序自动管理
优点:
增加了程序的可靠性,减小了memory leak的情况、提高了程序猿的效率。
缺点:
程序猿无法控制GC的时间,system.gc();
判断哪些内存需要回收需要耗费系统开销
逻辑上的内存泄露依然会存在
3.2什么是GC
GC ——Garbage Collection
GC的工作原理:
找出不再使用的对象( Garbage ),进行回收( Collection )
什么是垃圾:
所有不再使用的对象都是垃圾
不能在访问的对象就是垃圾
常见判断垃圾方法:
引用计数法
根搜索算法
3.2.1GC算法——引用计数法
引用计数法:(通过判断对象的引用对用来决定对象是否可以被回收)
给堆中的每一个对象增加一个引用计数器,当每一次创建一个对象并赋值给一个变量是,引用计数器就加1.当对象不再使用时(出了作用域),引用计数机减一。一但引用计数器为0,对象就满足了垃圾回收的条件
特点:
实现简单
缺点:
无法解决循环引用问题(内存溢出)
对象的引用计数器的频繁更新,性能低
可能会出现线程安全问题
3.2.2 GC算法——根搜索算法(标记算法)
根搜索算法(Root Tracing)
在主流的商用程序处理语言中(hotswap),都是使用根搜索算法来判断对象是否存活的。
该算法的基本思路就是通过一系列的名为GC Root的对象作为起始点,从这些节点开始向下搜索,搜索所走的路径称为引用链(Reference chain)。当一个对象到所有的GC root之间没有任何引用链相连(用图论的话来说就是没GC roots到这些对象不可达)时,证明该对象是不可用的,GC程序即可回收这些对象。
就是在栈中作为root节点,然后依次引用堆内存的对象,如果没有跟栈节点相连接的,就看为垃圾,可以回收。
GC roots
- 虚拟机栈中的引用对象
- 方法区中的静态属性引用的对象
- 方法区中常量引用的对象
专业术语:shallow size、retained size
Shallow size 就是对象本身占用的内存大小,也就是对象头加成员变量占用内存大小的总和
Retained size 是该对象自己的shallow size 加上仅可以从该对象访问(直接或者间接访问)的对象的shallow size之和。
Retained size是该对象被GC之后所能回收的内存的总和。
(蓝色部分为Retained size)
四、垃圾回收算法
- 标记清除算法
- 标记整理算法
- 标记复制算法
- 分代收集算法
4.1标记清除算法
JVM 和 Dalvik(Android 虚拟机)使用Mark Sweep算法实现垃圾回收(Android5.0之前的虚拟机 之后ART)
1)mark:标记出被引用的对象
2)Sweep:清除那些没有任何引用的对象
缺点:
1.会产生空间碎片,创建大对象(大数组或者大字符串)就会出现,空间大小足够,但是不能创建成功
2.创建对象会比较慢。
4.2 标记整理算法
适合场景:垃圾比较少的情况
优点:不会产生内存空间碎片
缺点:耗时
4.3 标记复制算法:
适合场景:存活对象比较少的情况
优点:不会产生碎片
缺点:浪费空间
4.4 分代收集算法
就是标记整理和标记复制的综合方法,所以在真实环境中用的正是该算法
jdk1.8之前,它首先将堆分为了几个不同的区域:
但是jdk1.8(包括1.8)之后,就没有永久代了!!!
1、新生代:尽可能快速的收集掉那些生命周期短的对象
分为:Eden区和两块的Survivor区,两个survivor复制有用对象到对方的内存中。依次迭代到某个值15,变成老年代
2、老年代:存放生命周期较长的对象
Full GC和Minor GC
触发FullGC的条件:
- 新生代出现大对象,直接存入老年代,然而老年代的空间也不足时触发。
- 调用System.gc()
- jdk1.7之前的永久代空间不足
3、永久代
怎么理解呢?就比如一个老板娘开了个客栈,每天都有很多客人在那居住,租一两天的那种短租的客人是最多的,他们被称为是新生代;而也有部分人在那和老板娘一言不合就签下一两年合同的人,他们属于常驻客,也就是老年代,而如果你把老板娘的女儿娶了,那么你就是他们家人了,就是传说中的永久代!
【注意】:
1.新生代默认是经过16次GC依然存活的对象就会变成老年代。
2.大对象:可以直接是老年代。
所以针对不同的区域采取不同的措施:
新生代:会产生大量垃圾。-->标记复制算法
老年代: 产生的垃圾比较少。--->标记整理算法
常用的调优参数:
-XX:SurvivorRatio:Eden和Survivor的比值,默认是8:1
-XX:NewRatio:老年代和年轻代内存大小的比较
-XX:Max:GC迭代次数的阈值
五、GC触发的时机
1.申请堆空间失败后会进行GC回收
2.系统进入idle后一段时间会进行回收
3.主动调用GC进行回收
六、常见的垃圾收集器
【知识前提】
JVM有两种运行模式Server与Client。
两种模式的区别在于,Client模式启动速度较快,Server模式启动较慢;
年轻代:
1.Serial收集器
特点:
- 单线程收集,必须暂停所有工作线程
- 简单高效,Client模式下默认的年轻代收集器
2.ParNew收集器
特点:多线程收集
3.Paraller Scavenge收集器
特点:
- 更关注系统的吞吐量
- Server模式下默认的年轻代收集器
老年代
1.Serial old收集器(单线程)
2.Paraller old(多线程,吞吐量优先)
3.CMS收集器:(一边丢垃圾,一边清除)标记清除算法。会产生碎片
4.Garbage First:(并发和并行)
优秀博文推荐:
https://juejin.im/post/5ae450b9f265da0b9a69b903