JVM读书笔记篇二:码农的福利

开篇闲话:
c哥(c语言)统治着编程界达20年余久,横跨北美、欧洲、亚洲等大陆板块,c哥名气大是因为他能贴近硬件、高效运行。但是c哥也有两个让人难受的臭毛病:①指针(直接操作内存,高效)不提供越界检查工具;②自己创建的内存空间,自己释放!
java哥穿着一件蓝色T恤,犀利的眼神闪闪发光,嘴唇不算厚,但是声音却很有穿透力:“码农宝宝们,跟我混,对象销毁的事就不用你们管了,你们只需要创建对象就行啦”。java哥说完,摄影镜头竟然也颤抖了一下。

预备知识:

1、JVM运行时内存分配
JVM读书笔记篇一:如何管理4个G的“封地”
2、client模式和server模式

当虚拟机运行在-client模式的时候,使用的是一个代号为C1的轻量级编译器, 而-server模式启动的虚拟机采用相对重量级,代号为C2的编译器。 C2比C1编译器编译的相对彻底,服务起来之后,性能更高。

3、并行和并发

并行(Parallel):在同一时刻有多条垃圾收集线程并行工作,当然,此时的用户线程是处于等待状态的。
并发(Concurrent):在一个时间段内,垃圾回收线程与用户线程同时执行(可能是并行,可能是交替执行),比如用户程序继续运行,而垃圾回收器运行在另一个CPU上。

4、吞吐量(Throughput)

吞吐量=运行用户代码时间/(运行用户代码时间+垃圾回收时间)

5、安全点(SafePoint)

GC时需要在某一快照状态下(某一时刻的运行状态),从而保持一致性(在分析期间对象引用关系保持不变)。此时会产生GC停顿(因为所有java线程都被停止了),如果数据量比较大的话,在进行GC Roots Tracing(对象引用链)查找对象引用关系时,停顿时间会很长,在HotSpot虚拟机中,使用OopMap这个数据结构(在编译字节码指令时指明偏移量+基地址的内存物理地址处有什么引用)来存储对象的引用。
有了这个数据结构仍然不能解决字节码指令执行时导致的引用关系变化,所以并不会为每条指令都生成OopMap,而是在“安全点”:也就是在特定的字节码指令处生成。安全点一般设置在“方法调用”、“循环跳转”、“异常跳转”等指令序列复用的地方(指令序列复用的意思是说,这些指令包含的指令流都是可以被反复调用的)

哪些内存需要回收?

篇一介绍了java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域与线程同生共死,栈中的栈帧空间大小是在类结构确定下来就已知的(最大栈深度、最大局部变量表个数)。所以这3个区域的内存分配和回收都是确定的。
方法区和堆的内存分配是不固定的,例如一个接口中的多个实现类、一个方法中的多个分支所需的内存都可能不同, 堆中的对象是在运行时才创建的。因此方法区和堆才是我们关注的重点。

如何判定对象是否存活?

对象存在的意义是被使用,而被使用在JVM中也有明确的定义,即对象引用。引用存在于函数栈帧中的局部变量表、操作数栈(指向堆)和常量池引用(指向方法区)。我们顺着引用便可以找到对象是否存活的依据了。有两种方式可以判定:

1、引用计数法:
方式:给每个对象添加一个引用计数器,每当有一个地方引用它时,计数器加1,失效时,计数器减1;为0时进行回收。很简单吧,但是它没法解决循环引用,请看:

public class Main {
    public static void main(String[] args) {
        GCObject obj1 = new GCObject();//obj1的计数器值为1
        GCObject obj2 = new GCObject();//obj2的计数器值为1
        obj1.object = obj2;//obj2的计数器值为2
        obj2.object = obj1;//obj2的计数器值为2
        obj1 = null;//obj1的计数器值为1
        obj2 = null;//obj2的计数器值为1
    }
   class GCObject{
      public Object object = null;
  }
}

我们发现obj1和obj2永远都不会被回收。

2、可达性分析:
从GC Roots对象开始往下搜索,当一个对象到GC Roots没有任何引用链相连,也就是从GC Roots到这个对象不可达时,则证明此对象是可回收的,如下图的object5、object6、object7三个:

可达性分析模型示意图

哪些对象可以做为GC Roots:

①虚拟机栈中引用的对象(局部变量表)
②方法区中静态属性引用的对象
③方法区中常量引用的对象
④本地方法栈中JNI引用的对象

怎么回收?

1、标记-清除 算法 (Mark-Sweep)
分为标记与清除两个阶段:标记出所有需要回收的对象,然后回收所有标记的对象。不足的地方有二:
①标记和清除两个过程效率都比较低,
②产生大量的空间碎片

Mark-Sweep

2、复制算法
将内存容量均分两份A、B,每次只使用其中一块,当块A内存用完后,将存活的对象复制到块B,清理掉块A即可。缺点是内存浪费了50%。

Copying

3、复制算法的改进
将内存的比例大致划分为8:1:1的Eden和Survival1、Survival2。当回收时,将Eden和Survival中存活的对象复制到另外一块Survival中,这样内存的浪费比例只占10%。
适用范围:垃圾回收频次较高的新生代

内存分配比例8:1:1

备注:如果新生代的内存空间不足,则由老年代分配担保(即将对象分配在老年代中)。

4、标记-整理算法
标记后,让所有存活的对象往一端移动,然后清理掉边界以外的内存。


Mark-Compact

适用范围:垃圾回收频次较低的老年代

5、分代算法

新生代:对象创建后,大部分即会死去,采用复制算法;
老年代:对象存活率高,且没有额外的担保空间,采用标记清理或标记整理算法

根据对象存活周期分配在不同的内存区域

内存分配与回收策略

1、对象优先在Eden分配:

当Eden区没有足够的空间时,虚拟机发起一次Minor GC。下面展示一个案例:通过-Xms20M -Xmx20M -Xmn10M设置堆初始大小为20M,新生代10M,老年代10M;当分配第四个对象时,由于空间不足会发起一次MinorGC,通过-XX:+PrintGCDetails打印GC日志:

/**
*vm参数 -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 GCTest
*/
public class GCTest {
    private static final int _1MB=1024*1024;
    public static void main(String[] args) {
        byte[] allocation1,allocation2,allocation3,allocation4;
        allocation1=new byte[2*_1MB];
        allocation2=new byte[2*_1MB];
        allocation3=new byte[2*_1MB];
        allocation4=new byte[4*_1MB];
    }
}
运行结果:
[GC [PSYoungGen: 7307K->480K(9216K)] 7307K->6624K(19456K), 0.0072860 secs] [Times: user=0.01 sys=0.01, real=0.00 secs]
Heap
 PSYoungGen      total 9216K, used 7143K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 87% used [0x00000000ff600000,0x00000000ffcf9fc8,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400010,0x00000000ff600000)
 PSPermGen       total 21504K, used 2621K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
  object space 21504K, 12% used [0x00000000f9a00000,0x00000000f9c8f610,0x00000000faf00000)

2、大对象直接进入老年代

大对象:需要大量连续内存空间的对象,最典型的大对象就是那种很长的字符串及数组(如上面的例子中byte数组对象)。
虚拟机提供-XX:PretenureSizeThreshold参数设置在老年代进行分配

3、长期存活的对象进入老年代

每个对象都有一个年龄(age), 在Mark Word中
如果age > MaxTenuringThreshold(默认为15), 晋升老年代

4、动态对象年龄判断:

如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半, 年龄大于或等于该年龄的对象可以直接进入老年代。

新生代——>老年代

5、方法区的回收(HotSpot虚拟机中将方法区放在永久代中,1.7已将字符串常量池从永久代中移除)

①废弃无用的常量
例如:常量池中的字符串 “abc” 不再被任何字符串引用, 可以清除掉
②无用的类回收条件:
1、该类的所有实例都被回收;
2、加载该类的Classloader 已经被回收;
3、该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法

垃圾收集器

垃圾收集器是垃圾回收算法的具体实现,用户体验与运行效率之间的博弈在不同场景下都有需求,所以这些高度封装的收集器本质上并没有好坏,在不同场景下,他们都能独当一面。
以下是JDK1.7 update 14之后Hotspot虚拟机的七种收集器,分别作用于不同分代:

Hotspot7种垃圾收集器

1、Serial收集器

算法:新生代采用复制算法,暂停所有用户线程,老年代采用标记-整理算法,暂停所有用户线程;
特点:①单线程运行(第一层意义:它是运行在一个CPU核或一个收集线程,第二层意义:Stop the World,回收时,所有用户线程必须停掉);②不用关心线程之间的交互,运行效率高,是client模式下默认的新生代收集器。

2、ParNew收集器
是Serial的多线程版本,可通过-XX:ParallelGCThreads参数来限定线程数量

3、Parallel Scavenge收集器
算法:复制算法;
特点:是一个新生代收集器,关注点在达到一个可控的吞吐量。有两个参数来控制:①-XX:MaxGCPauseMills最大垃圾收集停顿时间,②-XX:GCTimeRatio直接设置吞吐量大小

4、Serial Old收集器
是Serial收集器的老年代版本,应用于Client模式
算法:标记-整理

Serial收集器与Serial Old收集器配合
ParNew收集器与Serial Old收集器配合

5、Parallel Old收集器
Parallel Scavenge收集器的老年代版本,之前Parallel Scavenge只能与Serial Old,无法与CMS配合工作,导致在server模式下,由于Serial Old性能的“拖累”,导致整体应用上吞吐量获得最大化的效果。在JDK 1.6中开始提供Parallel Old配合来解决这个问题
算法:多线程+标记-整理

Parallel Scavenge与Parallel Old收集器配合

6、CMS收集器(Concurrent Low Pause Collector)
CMS收集器基于标记-清除算法,应用于老年代。收集过程分为初始标记、并发标记、重新标记、并发清除四个阶段:

①初始标记、重新标记这两个步骤仍然需要Stop the World;
②初始标记阶段仅仅是对GC Roots能直接关联到的对象进行标记;
③并发标记进行GC Roots Tracing的过程,即按照引用链进行标记;
④重新标记是为了在并发标记期间因用户线程继续运行而导致标记变动的标记修正;

特点:

以获取最短回收停顿时间为目标
互联网网站, B/S服务器端
容易产生碎片

CMS收集器运行示意图

7、G1收集器(Garbage-First)

面向服务端应用的垃圾收集器,成熟版基于JDK1.7;
可针对年轻代和老年代;
采用标记-整理算法

特点:

充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间;
G1收集器可收集新生代与老年代两种,不需要其他收集器配合就可以独立管理整个GC堆;
建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不得超过N毫秒

G1收集器运行示意图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • JVM架构 当一个程序启动之前,它的class会被类装载器装入方法区(Permanent区),执行引擎读取方法区的...
    cocohaifang阅读 1,622评论 0 7
  • 1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供...
    简欲明心阅读 89,286评论 17 311
  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,927评论 2 31
  • http://www.cnblogs.com/angeldevil/p/3801189.html值得一看 Clas...
    snail_knight阅读 1,387评论 1 0
  • 1.一些概念 1.1.数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始...
    落落落落大大方方阅读 4,502评论 4 86