jvm学习笔记

1.java内存模型

1.1 jvm架构图

我们一般关注的是运行时数据区。

1.2 运行时数据区

1.2.1线程共享

  • 堆区(heap):内存创建的地方
    堆区存放对象的实例变量以及数组将被存储在这里。因为是线程共享的所以也是线程不安全的。这里也是gc的主要场所。

堆分为新生代和老年代。
新生代分为Edan,from survival(survival0),to survival(survival1),比例大概是8:1:1
老年代主要用于保存一些在新生代里面多次gc依然存活的对象,一般是15次gc。一般是一些大的对象。老年代和新生代的内存大小比例一般是 2:1。

还有一个持久代(永久代 perm)的区域,一般指的是方法区。表示此空间很少被回收,但是不表示不会被回收。
满足下面条件依然会被垃圾回收:


	1.常量池中的常量,常量如果没有被引用则可以被回收
	2.无用的类信息(同时满足以下条件): 
		2.1. 类的所有实例都已经被回收了 
		2.2. 加载类的ClassLoader已经被回收
		2.3. 类对象的class对象没有被引用(即没有通过反射引用该类的地方)
  • 方法区
    如下
    方法区包含
  • ClassLoader引用
  • 字段数据
  • 常量池
  • 方法数据
  • 方法代码
    一个java类被类加载器加载后,类的相关的信息会保存到方法区,包括:即加载类时需要加载的信息,包括版本、域、方法、接口等信息,也包括静态变量。
    每个jvm实例只有一个方法区,这里会被jvm下的线程共享,so方法区是线程不安全的。

1.2.2线程私有

  • 栈区(也称为虚拟机器栈区)
    栈区是线程安全的,每个线程都会创建自己私有的栈区。每个线程在执行的时候都会创建自己私有的虚拟机栈,里面保存下面3种信息:
    1)局部变量,方法中的局部变量
    2)操作数栈:即执行的指令,a+b:a入栈+入栈b。通过入栈出栈计算结果
    3)帧数据: 方法所有符号都保存在这里。异常情况下catch块的信息将会被保存在桢数据中。
    线程里面所有的方法在执行的时候,都把方法打包成一个栈针,执行时入栈,执行结束后,出栈。


  • 本地方法栈(native method stack,有的虚拟机会把本地方法栈和虚拟机栈放到同一个位置。)

  • 程序计数器(pc寄存器)
    从寄存器的概念上我们就可以了解到空间很小但是很重要。主要用于记录代码执行到哪一行的信息。可以理解为当前线程的行号指示器(字节码)字节码解释器在工作时,会通过改变这个计数器的值来取下一条指令。

1.2.3直接内存

内存一部分被jvm管理,一部分没有被jvm管理,没有的那部分就是直接内存。

2. GC垃圾搜索算法

2.1 内存泄露和内存溢出

  • 内存泄漏:是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
  • 内存溢出:通俗的说就是系统内存不够,导致程序崩溃,一般内存泄漏很严重会导致内存溢出。

2.2 引用计数算法

对象中存在一个引用计数器,一旦该对象被引用则计数器加1,一旦对象应用被释放,则计数器减1。因为这种算法无法解决相互引用的问题,所有虚拟机并没有使用这个垃圾搜索算法。

@Test
publicvoidcontextLoads(){
Objectobj1=newObject();
Objectobj2=obj1;
Objectobj3=obj1;

obj1=null;
System.out.println(obj2);
System.out.println(obj3);
obj2=null;
obj3=null;
System.out.println(obj2);
System.out.println(obj3);
}

输出结果:
java.lang.Object@18948cd
java.lang.Object@18948cd
null
null

//相互引用的问题
Object obj1  = new Object();
Object obj2  = new Object();
obj1.object = obj2;
obj2.object = obj1;

obj1 = null;
obj2 = null;

2.3可达性分析算法

从GC root开始可达的为存活对象,不可达的为待gc对象。

GC Roots的条件:
+ 虚拟机栈的栈桢的局部变量表所引用的对象
+ 本地方法栈的JNI所引用的对象
+ 方法区的静态变量和常量所引用的对象
如下图所示:

3.垃圾回收算法

3.1 标记-清除算法(Mark-Sweep)

从GCRoot开始,如果是可达对象标记为存活对象,然后对不可达对象进行清楚。

优缺点

简单,需要停止程序,而且Gc后的内存区域零散。

3.2 复制算法

将内存区分为两部分:空闲区域和活动区域。现把可达对象复制到空闲区域,然后把空闲区间变为活动区间,同时把之前的活动区间Gc掉,变为空闲区间。

有缺点

不需要停止程序,速度快,但是耗费空间

3.3 标记-整理算法(Mark-Compact)

标记可达对象,清除不可达对象,整理内存空间。

3.4 分代算法

年轻代(Yound Generation)

  • Edan(8)
  • Survival0(1)
  • Survival1(1)

在年轻代会不定时发生Minor GC。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的。,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。

当survivor1已经无法存放edan和survivor0所有存活的对象时,会把survivor1里面的存活对象,复制到老年代。如果老年代也满了,会触发一次Full GC。即新生代和老年代同时垃圾回收。

老年代(Old Genneration)大概和新生代的比例是 2:1

持久代(Permanent Generation)

4. GC收集器

+ 新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge

+ 老年代收集器使用的收集器:Serial Old、Parallel Old、CMS

5.jvm常用参数

-XmsxxM : -Xms64M 设置最小堆内存为64MB
-Xmxxxm : -XMx128M 设置最大堆内存128MB
-XX:NewSize :设置年轻代的大小
-XX:NewRatio : 设置年轻代和老年代的比值,如:3 表示年轻代与老年代的比值为1:3
-XX:SurvivorRatio :年轻代中eden区与两个survivor区的比值
-XX:MaxPermSize : 设置持久代的大小

6. 如何选择垃圾收集器

没有最好的收集器也没有万能的收集器,只有最合适的收集器。收集器是jvm对于不同算法具体实现,Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、版本的虚拟机所提供的垃圾收集器都可能会有很大差别。

6.1 并行 和 并发的区别

  • (A)、并行(Parallel)

指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;

如ParNew、Parallel Scavenge、Parallel Old;

  • (B)、并发(Concurrent)

指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行);

用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上;

如CMS、G1(也有并行);

6.2 Minor GC 和 Full GC的区别

(A)、Minor GC

又称新生代GC,指发生在新生代的垃圾收集动作;

因为Java对象大多是朝生夕灭,所以Minor GC非常频繁,一般回收速度也比较快;

(B)、Full GC

又称Major GC或老年代GC,指发生在老年代的GC;

出现Full GC经常会伴随至少一次的Minor GC(不是绝对,Parallel Sacvenge收集器就可以选择设置Major GC策略);

Major GC速度一般比Minor GC慢10倍以上;

6.3 收集器具体介绍

收集器之间有连线表示可以配合使用。
新生代收集器:Serial、ParNew、Parallel Scavenge;
老年代收集器:Serial Old、Parallel Old、CMS;
整堆收集器:G1;

6.3.1 Serial收集器

Serial(串行)垃圾收集器是最基本、发展历史最悠久的收集器;
JDK1.3.1前是HotSpot新生代收集的唯一选择;
//特点

针对新生代;

采用复制算法;

单线程收集;


//应用场景


//设置参数
      "-XX:+UseSerialGC":添加该参数来显式的使用串行垃圾收集器;

//其他说明

6.3.2 ParNew

//特点
是Serial的多线程版本。特点和Serival类似。

//应用场景
  在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;

//设置参数
 "-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器;
"-XX:+UseParNewGC":强制指定使用ParNew;    
"-XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
//其他说明
为什么只有ParNew能与CMS收集器配合?
CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;因为Parallel Scavenge和G1是独立实现的收集器,Parallel Scavenge(以及G1)都没有使用传统的GC收集器代码框架,而其他的收集器都部分使用了框架的代码。

6.3.3 Parallel Scavenge

//特点
	新生代收集器;

      采用复制算法;

      多线程收集;

//应用场景
	CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间;

      而Parallel Scavenge收集器的目标则是达一个可控制的吞吐量(Throughput);

//设置参数
Parallel Scavenge收集器提供两个参数用于精确控制吞吐量:

(A)、"-XX:MaxGCPauseMillis"

      控制最大垃圾收集停顿时间,大于0的毫秒数;

      MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降;

      因为可能导致垃圾收集发生得更频繁;

(B)、"-XX:GCTimeRatio"

      设置垃圾收集时间占总时间的比率,0<n<100的整数;

      GCTimeRatio相当于设置吞吐量大小;

      垃圾收集执行时间占应用程序执行时间的比例的计算方法是:

      1 / (1 + n)

      例如,选项-XX:GCTimeRatio=19,设置了垃圾收集时间占总时间的5%--1/(1+19);

      默认值是1%--1/(1+99),即n=99;
(C)、"-XX:+UseAdptiveSizePolicy"
	      开启这个参数后,就不用手工指定一些细节参数,如:
	      新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等;
	      JVM会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或最大的吞吐量,这种调节
 
//其他说明
推荐的设计方式

      (1)、只需设置好内存数据大小(如"-Xmx"设置最大堆);

      (2)、然后使用"-XX:MaxGCPauseMillis"或"-XX:GCTimeRatio"给JVM设置一个优化目标;

      (3)、那些具体细节参数的调节就由JVM自适应完成; 

6.3.4 Serial Old收集器

//特点
   针对老年代;

   采用"标记-整理"算法(还有压缩,Mark-Sweep-Compact);

   单线程收集;

//应用场景
主要用于Client模式;

      而在Server模式有两大用途:

      (A)、在JDK1.5及之前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配);

      (B)、作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用(后面详解);

//设置参数
 
//其他说明

6.3.5 Parallel Old收集器

Parallel Old垃圾收集器是Parallel Scavenge收集器的老年代版本;
JDK1.6中才开始提供;

//特点
      针对老年代;

      采用"标记-整理"算法;

      多线程收集;

//应用场景
       JDK1.6及之后用来代替老年代的Serial Old收集器;

      特别是在Server模式,多CPU的情况下;

      这样在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge加Parallel Old收集器的"给力"应用组合;

//设置参数
 
      "-XX:+UseParallelOldGC":指定使用Parallel Old收集器;

//其他说明

6.3.6 CMS收集器

并发标记清理(Concurrent Mark Sweep,CMS)收集器也称为并发低停顿收集器(Concurrent Low Pause Collector)或低延迟(low-latency)垃圾收集器;

//特点
 针对老年代;

      基于"标记-清除"算法(不进行压缩操作,产生内存碎片);            

      以获取最短回收停顿时间为目标;

      并发收集、低停顿;

      需要更多的内存(看后面的缺点);

//应用场景
       与用户交互较多的场景;        

      希望系统停顿时间最短,注重服务的响应速度;

      以给用户带来较好的体验;

      如常见WEB、B/S系统的服务器上的应用;

//设置参数
 
      "-XX:+UseConcMarkSweepGC":指定使用CMS收集器;

//其他说明

6.3.7 G1收集器

G1(Garbage-First)是JDK7-u4才推出商用的收集器;

//特点
(A)、并行与并发

      能充分利用多CPU、多核环境下的硬件优势;

      可以并行来缩短"Stop The World"停顿时间;

      也可以并发让垃圾收集与用户程序同时进行;

(B)、分代收集,收集范围包括新生代和老年代    

      能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;

      能够采用不同方式处理不同时期的对象;

                
      虽然保留分代概念,但Java堆的内存布局有很大差别;

      将整个堆划分为多个大小相等的独立区域(Region);

      新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合;
 
(C)、结合多种垃圾收集算法,空间整合,不产生碎片

      从整体看,是基于标记-整理算法;

      从局部(两个Region间)看,是基于复制算法;

      这是一种类似火车算法的实现;

 

      都不会产生内存碎片,有利于长时间运行;

(D)、可预测的停顿:低停顿的同时实现高吞吐量

      G1除了追求低停顿处,还能建立可预测的停顿时间模型;

      可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒;

//应用场景
 面向服务端应用,针对具有大内存、多处理器的机器;

      最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案;

      如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒;

            

      用来替换掉JDK1.5中的CMS收集器;

      在下面的情况时,使用G1可能比CMS好:

      (1)、超过50%的Java堆被活动数据占用;

      (2)、对象分配频率或年代提升频率变化很大;

      (3)、GC停顿时间过长(长于0.5至1秒)。

//设置参数
 
   "-XX:+UseG1GC":指定使用G1收集器;

      "-XX:InitiatingHeapOccupancyPercent":当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45;

      "-XX:MaxGCPauseMillis":为G1设置暂停时间目标,默认值为200毫秒;

      "-XX:G1HeapRegionSize":设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region;
//其他说明

总结

收集器 串行、并行or并发 新生代/老年代 算法 目标 适用场景
Serial 串行 新生代 复制算法 响应速度优先 单CPU环境下的Client模式
Serial Old 串行 老年代 标记-整理 响应速度优先 单CPU环境下的Client模式、CMS的后备预案
ParNew 并行 新生代 复制算法 响应速度优先 多CPU环境时在Server模式下与CMS配合
Parallel Scavenge 并行 新生代 复制算法 吞吐量优先 在后台运算而不需要太多交互的任务
Parallel Old 并行 老年代 标记-整理 吞吐量优先 在后台运算而不需要太多交互的任务
CMS 并发 老年代 标记-清除 响应速度优先 集中在互联网站或B/S系统服务端上的Java应用
G1 并发 both 标记-整理+复制算法 响应速度优先 面向服务端应用,将来替换CMS

参考连接:
https://segmentfault.com/a/1190000016200996
https://blog.csdn.net/tjiyu/article/details/53983650

posted on 2019-04-29 21:54  lukelin1989  阅读(372)  评论(0编辑  收藏  举报

导航