美文网首页
一、几种jvm和自动化内存管理

一、几种jvm和自动化内存管理

作者: 无聊之园 | 来源:发表于2019-05-11 14:08 被阅读0次

来自于《深入理解java虚拟机》,进行概括总结。

一、几种java虚拟机:classic、hotSpot、JRockit。

了解即可,最重要的是hotSpot虚拟机:是Sun JDK和OpenJDK所带的虚拟机。

二、自动化内存管理机制。

2.1 内存区域:

2.1.1运行时数据区

五大区域:
线程私有区:程序计数器、虚拟机栈、本地方法栈。
线程共享区:方法区、堆。

程序计数器:线程所执行字节码的行号指示器。指向哪里,就运行哪里。占用内存很小,唯一不会产生OutOfMemoryError的区域。

虚拟机栈:方法执行过程中,存储和操作栈帧,栈帧存储了:局部变量表,操作数栈等。其中局部变量表可看成我们常说的堆栈中的栈,存放编译器可知的基本数据类型、引用类型的指针,所占区域大小编译器就已经知道并且固定了,不会更改。如果栈帧太多了,比如递归调用,就会抛stackOverFlowError错误。

本地方法栈:本地方法栈和虚拟机栈类似,只不过本地栈针对时执行native方法的,其他都一样。不过HotSpot虚拟机把本地方法栈和虚拟机栈合二为一了。

java堆:java堆时占用内存最大的一块区域。用来存放对象实例,是垃圾回收器管理的主要区域,比如如果垃圾回收器采用的是分代回收算法,那么这里就划分为新生代、年老代。因为是线程共享的,所以因为线程安全问题可能划分线程私有的分配缓冲区。-xms和-xmx就是控制java堆区域的。

方法区:用于存放已加载的类信息,常量,静态变量等公共的东西。有叫永久代的(permannent generation),认为其保存的信息是永久的,但是比如hotspot虚拟机的gc就会堆这一块也进行gc回收,因为如果不对方法去进行回收,会产生内存泄露,比如String.intern()方法一直扩充字符串池,类卸载也会gc回收。
class文件的常量池在类加载后就是放入方法区的。

直接内存:除了上述5块之外,还有一个区域:或者堆外内存。比如nio的ByteBuffer.allowDirect()申请的内存就是直接内存,并不在java堆中,在某些场景就省去了java堆和native堆复制这一步,提供性能。这块区域默认的大小和-xmx一样大,也会报outofMemoryError。

2.1.2 对象创建和访问

对象创建:如果java堆内存是规整的,则是指针碰撞,如果不是规整的,则使用空闲列表记录可用的内存区域。内存是否规整取决于gc算法。
对象内存布局:对象头、实例数据、对齐填充。
对象头:hashcode、分代年龄等。
实例数据:对象的正常数据。
对齐填充:占位符的作用,保证对象大小是8的整数倍。
对象访问:1、句柄:局部变量表中存储句柄的地址,句柄中存储实际对象的地址。这样,对象地址变了,局部变量记录的地址不需要变。2、直接指针:直接存储对象的地址。访问比句柄快。

2.2 垃圾收集器

2.2.1哪些内存需要回收:标记需要回收的内存:

1.引用计数法: 对象存在一个引用,则引用计数+1,引用失效,则-1。
缺点:对于循环引用,无法进行回收。现在主流的jvm都不采用这个算法,比如hotspot。
2.可达性分析法:从一系列GC Roots,树形遍历,对象到GC Roots没有任何引用链的,则认为不可达,可进行回收。现在一般都是采用这个算法。

强引用、软引用、弱引用、虚引用: 对于有一些引用,食之无味,弃之可惜,内存紧张,则回收,内存不紧张则不回收,比如缓存的场景。所以出现了强引用、软引用、弱引用、虚引用、
强引用:Object o = new Object,这种只要有引用,则gc不能进行回收的。
软引用:内存在将要发生内存溢出的时候,才进行回收。SoftReference类实现了软引用。
弱引用:比软引用更弱,这种引用关联的对象,只要进行垃圾回收,就会回收。WeakReference类实现了弱引用。
虚引用:对象的回收与否不会收到虚引用的影响,也无法通过虚引用取得对象,虚引用关联的目的是gc回收的时候收到一个通知。PhantomReference类实现了虚引用。

finalize方法:Object方法的finalize方法是说gc回收的时候会调用,但是gc回收的时候并不会马上调用,会将其放入F-Quene队列中,一个一个执行,而且并不保证一定会执行,为了保证性能,可能gc回收完了finalize还没有执行。finalize中可以refer=this这样重新拯救对象。finalize方法最好不用,这是为了c/c++人的习惯弄出来的。

方法区的回收:方法区回收频率特别低,而且回收的内存很少。主要回收废弃的常量和卸载的类,废弃的常量:任何地方都没有引用这个常量。卸载的类:实例被回收,classloader被回收,class对象没有任何地方引用。方法区的回收很有必要:比如,热部署,动态代理需要jvm具有类卸载的功能。

2.2.1垃圾回收算法

1.标记清楚法:可达性分析标记内存,之后直接清除内存。
缺点:产生内存碎片。需要一个一个遍历清楚内存,效率低。
2、复制算法:内存分成两块,一块进行是使用,回收的时候,把存活的对象移入另一块,解决了内存碎片的问题。
缺点:内存利用率低,内存被分成了两块。存活对象多时,复制效率低。
3.标记整理算法:和标记清楚算法差不多,只不过在标记后,进行了整理,将存活的对象移动整理,之后对回收的一大块内存直接回收。
4.分代收集算法:将内存分成新生代和年老代。各个代使用不同的算法,比如:新生代使用复制算法,老年代使用标记整理算法。

2.2.2 垃圾回收器

有很多种垃圾回收器:
具有线相连说明可以合作使用

image.png
serial:单线程,gc运行的时候停止用户线程。新生代使用复制算法,年老代使用标记整理算法。
parNew:在serial的基础上,将单线程改成多线程。cms可以和其合作使用,所以其价值显得大一些。新生代使用复制算法,年老代使用标记整理算法。
paraller Scavenge: parNew关注的缩短用户线程停顿时间,paraller关注的是,吞吐量可控,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),paraller提供参数控制吞吐量,也就是说可以控制停顿时间,但是停顿时间越少,则说明回收的内存少,则回收的频率会更高,paraller关注吞吐量,所以适合和交互性不高的场景。paraller有一个自适应jvm配置参数:useAdaptiveSizePolicy,会自适应调整eden、survivor、晋升年老代年龄、吞吐量等参数。年轻代使用复制算法,年老代使用标记整理算法。
serial old:serial在年老代收集。
parraller old:parraller在年老代的版本。
cms收集器:以缩短停顿时间为目的,适合bs系统,基于标记-清除算法。回收分四个阶段:初始标记-》并发标记-》重新标记->并发清除。
初始标记:会停顿用户线程,只标记GC roots直接关联的对象,很快。
并发标记:并发标记则启动多线程并发标记,同时,用户线程不停顿。
重新标记:多线程并发标记,停止用户线程,标记前一个阶段新产生的对象。。
并发清除:多线程并发清除标记对象。
cms回收阶段,只有占用时间少的部分停顿了用户线程:初始标记、重新标记。所以用户交互性很好,适合bs应用。
缺点:1.gc运行的时候,占用cpu,用户线程变慢。2.产生浮动垃圾,Concurrent Mode Failure导致Full Gc。3.标记清除算法产生内存碎片,出发Full gc。
浮动垃圾:并发清除的时候用户线程还在运行,会产生垃圾。
Concurrent Mode failure:gc回收的时候,使用复制算法,将eden和survivor区存活的对象放入to survivor区,而如果to survivor区空间不够,则需要年老代做担保,年老代不知道多少对象会存活,所以只能当成所有对象都存活,或者取平均值(hotspot就是取平均值),所以年老代会准备这些空间,如果不够则进行full gc。gc并发清除的时候,还需要预留gc程序的空间。
G1 收集器:技术最前沿的收集器,jdk1.9的默认收集器,适合bs系统。有以下特点:
并发与并行:多线程并行,几乎用户线程和gc线程并发执行。
分代收集:g1不需要和其他垃圾回收器配合,独立管理新生代和年老代。g1的新生代和年老代不是物理隔离,是一个个不连续独立区域(region)的集合。
空间整理:cms采用标记清除,g1采用标记整理算法。
可预测停顿:使用者明确指定,几毫秒内,gc占用的时间不能超过多少毫秒。g1会跟走各个reigion的回收价值大小(回收空间和花费时间的比值),根据允许的时间,回收价值更大的reigion。
g1和cms类似也有几个步骤:
初始阶段-》并发标记-》最终标记-》筛选回收。

总结:如果交互性高,追求低停顿,g1比较适合。如果交互性低,追求吞吐量,parallel比较适合。

2.3 内存分配和回收策略

内存分配和回收的一些细节和具体的jvm和gc参数有关。
对象优先在eden区分配:eden区内存不够,则出发Minor Gc,Gc的时候仍存活的对想移入to survivor区,to survivor区不够,则找老年代借用空间做担保。

大对象直接进入老年代:垃圾回收的时候,eden通过复制算法进行回收,为了避免大对象的复制,jvm会直接将大对象分配到老年代。

长期存活的对象进入老年代:eden区进行回收的时候,从ede区复制到survivor区,存活一次,则年龄+1,默认年龄为15岁的时候,则移入老年区。

动态对象年龄判定:survivor区相同年龄的总和大于survivor空间的一半,则年龄大于或等于该年龄的对象直接进入老年区。

空间分配担保:Minor Gc之前,虚拟机会检查老年代连续空闲空间是否大于新生代所有对象之和,如果不成立,检查老年代空闲空间是否大于每次进入老年代的平均大小,是,则,Minnor Gc,否则进行一次Full Gc回收老年代空间。因为必须保证老年代有空间来做担保。

相关文章

网友评论

      本文标题:一、几种jvm和自动化内存管理

      本文链接:https://www.haomeiwen.com/subject/writaqtx.html