最近一直在持续看JVM的相关知识(每天睡不着,所以看着看着就看困了)
本文将主要涉及几个方面:1.GC的过程 及算法
2.GC主要的几个收集器:CMS SERIAL G1
3.运行时内存分区及JMM(内存模型)
4.类的加载机制
5.性能调优的工具:jps jstat jmap jinfo jstack
一. 首先来说一下GC
java 是一个面向对象的高级语言, 相比与同样是面向对象的C++ 有着一定的优势. 他可以免除开发人员自己对无效对象的清理,而实现这一优势最主要的原因就是 JVM来将这个事情给做了. 下面来简述一下GC的工作过程
首先我们要了解到的是 GC是一个后台的守护线程. 也就是说,会随着我们运行的主类的结束而死亡.
GC的分代收集分为:年轻代、老年代、永久代。(方法区是被当做永久代的,不过JDK1.6后将被取消掉了)年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)
GC触发条件:Eden区满了触发Minor GC,这时会把Eden区存活的对象复制到Survivor区,当对象在Survivor区熬过一定次数的Minor GC之后,就会晋升到老年代(当然并不是所有的对象都是这样晋升的到老年代的),当老年代满了,就会报OutofMemory异常。
GC的核心是对对象进行一个可达性分析,通过逆向寻找某个对象的引用来判断一个对象是否有用. 值得一提的是Java中的引用(reference) 分为 强引用(Strong) 软引用 (soft) 弱引用 (weak) 虚引用() 强引用就是我们经常使用的 new obj(); 这种形式. 软引用会在内存不足的时候被回收 虚引用如果不被回收 ,那么引用他的对象永远不会被回收,需要主动调用clear 方法来释放. 弱引用在GC时一定会被GC回收
finalize 是位于Object类的一个方法,该方法的访问修饰符为protected,由于所有类为Object的子类,因此用户类很容易访问到这个方法。由于,finalize函数没有自动实现链式调用,我们必须手动的实现,因此finalize函数的最后一个语句通常是 super.finalize()。通过这种方式,我们可以实现从下到上实现finalize的调用,即先释放自己的资源,然后再释放父类的资源。
根据Java语言规范,JVM保证调用finalize函数之前,这个对象是不可达的,但是JVM不保证这个函数一定会被调用。另外,规范还保证finalize函数最多运行一次。
很多Java初学者会认为这个方法类似与C++中的析构函数,将很多对象、资源的释放都放在这一函数里面。其实,这不是一种很好的方式。原因有三,其一,GC为了能够支持finalize函数,要对覆盖这个函数的对象作很多附加的工作。其二,在finalize运行完成之后,该对象可能变成可达的,GC还要再检查一次该对象是否是可达的。因此,使用 finalize会降低GC的运行性能。其三,由于GC调用finalize的时间是不确定的,因此通过这种方式释放资源也是不确定的。
通常,finalize用于一些不容易控制、并且非常重要资源的释放,例如一些I/O的操作,数据的连接。这些资源的释放对整个应用程序是非常关键的。在这种情况下,程序员应该以通过程序本身管理(包括释放)这些资源为主,以finalize函数释放资源方式为辅,形成一种双保险的管理机制,而不应该仅仅依靠finalize来释放资源。
最后简单说一下几个gc minor gc , full gc 等. 当对象创建时候,会被优先分配在eden区域(新生代),经过了几轮的gc后会进入到老年代 当然这只是一般对象,当对象足够大的时候会直接进入survivor(老年代,这个足够大需要结合JVM参数配置) 默认的新生代与老年代在内存中的内存配比是8:1:1.(两个老年代的分区) 当新生代满了之后 会触发minor gc. 当老年代满了会抛出 outOfMemeryError .
一(2) . 垃圾回收的算法
目前我了解到的几个算法 标记清除算法 标记整理算法 复制算法 可达性分析
标记清除: 顾名思义 会对垃圾对象进行标记 然后对其进行清除 , 缺点是会造成空间碎片.比如清除后的内存区域是不连续的15M, 但是最大的连续区域是5M 那么当一个6M的对象被创建的时候,则无法创建在该区域.
标记整理: 是标记清除算法的一个优化, 会定期将清理后的内存区域进行整合,使现有的对象集中放置.会避免空间碎片的产生.适用与存活对象较多的区域 如老年代survivor
复制算法:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收. 不适用与老年代. 复制算法是一种比较高效的方法,但缺点是会浪费内存空间(以空间换时间)
二.GC的几种垃圾收集器
年轻代收集器包括Serial收集器、ParNew收集器以及Parallel Scavenge收集器。老年代 Serial Old收集器、Parallel Old收集器以及CMS收集器。 还有一个最新的收集器 在JDK7后开始广泛使用 G1
serial收集器是一个串行的收集器,也是最为古老的垃圾收集器. 最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World
ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩
Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供
CMS 是B/S架构中使用最为广泛的收集器 采用标记清除算法 可能会造成空间碎片 但是因为他会并行清除 所以效率还是很高的 适用于 老年代 新生代一般是parnew
G1 收集器 是最新的研究成果 最大的优势是 空间整合和可预测停顿 详细可参考
https://www.cnblogs.com/ityouknow/p/5614961.html
三. 运行时内存分区和JMM(内存模型)
一般的来说,大家都会把内存分为堆内存和栈内存. 其实这样只是粗略的划分. 在我们java程序运行的时候 , 其实会有更详细的划分. 首先我们会把一个程序的运行分为 共享区和独占区. 在共享区中 就包含了我们常说的堆内存 和 方法区( 其实方法区也是存储在堆内存上的,只不过因为其存储的内容不同 将其单独划分) ,通常意义上来讲的堆 就是我们存放对象的地方, 而方法区存放的是静态变量信息,常量池,类信息等. 在另外一方面 独占区 会分为: 栈内存 寄存器(程序计数器) 和本地方法栈(native stack) .
所谓独占区的意思就是当我们每执行一个线程的时候,都会未其分配独有的独占区,是不和其他线程共享的. 简单解释一下 寄存器这个东西, 我们在线程调用的时候 难免会有一些线程出现等待的情况,这个程序计数器就是用来记录当前线程执行的情况,可以理解为当前字节码文件执行的行数. 而栈内存中存储的就是一些栈信息 (采用了stack 的结构 first in last out ) ,本地方法栈现在还不是太明白 有看到的大佬可以留言告知 感谢~
JMM: 在我们多线程编程中,每一个线程都不会对堆内存进行直接操作. 而是采用了线程副本的机制,每次操作会从堆内存中进行复制,然后对线程副本进行操作 在复制到堆内存中. 这样的话 线程一和线程二是不会互相感知到彼此的存在.而线程间的通信也是靠堆内存来完成的. 在之前的开发过程中曾经踩过这个坑 当时在线程类中new 了一个 concurrent hashmap 希望以此来存储一个KV的值供另一个方法调用,由于没有考虑到添加的时候是多线程 而获取的时候是单线程(其实相当于new 了 三个 juc hashmap) 所以会有一部分值取不到. 最后选择持久化到了redis 来完成这个设定.
四. 类的加载机制
JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化。 参考
http://www.importnew.com/25295.html
五.性能调优的工具:jps jstat jmap jinfo jstack
jps: 类似于linux 的ps 指令 返回当前执行的进程号 可以使用jsp -l 来查看进程号及方法名称
其他几个命令都需要进程号的参数 所以 jps是基础
jmap: jmap -histo pid pid 为进程号
持久代 jmap -permstat pid
jstack: 用来输出 某个进程的堆栈信息
jstack [option] pid
jstack [option] executable core
jstack [option] [server-id@]remote-hostname-or-ip
jstat: JVM统计工具
jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]
另外还可以通过 jconsole 这个可视化工具 查看到内存的动态变化 会有个线性的图动态展示