java虚拟机在执行java程序的过程中会把他所管理的内存划分为若干个不同的数据区域,运行时数据区分别为:程序计数器,java虚拟机栈,本地方法栈,java堆,方法区。还有一个非运行时数据区:直接内存(Direct Memory)。

运行时内存以及垃圾收集器

1)程序计数器

每个线程创建的时候会有一个独立的程序计数器,可以看成是线程执行的字节码的行号指示器,不同线程之间的程序计数器互不影响,存储于程序计数器所在的内存中,这块内存很小。线程私有

2)java虚拟机栈

每个方法被执行的时候虚拟机都会创建一个栈帧,用于存储局部变量表, 操作数栈,动态链接,方法出口等信息,一个方法被调用至结束,就对应着栈帧在虚拟机栈中从入栈到出栈的过程。线程私有

局部变量表存放了编译器可知的各种基本数据类型:boolean,byte,char,short,int,float,long,double,对象引用类型和returnAddress类型。对象引用类型可以是一个指向对象起始地址的指针,也可以是指向一个代表对象的句柄。returnAddress类型指向一条字节码指令地址。

3)本地方法栈

调用native方法所需要用的栈

4)java堆

java堆是虚拟机所管理的内存中最大的区域,在java虚拟机启动的时候创建。几乎所有的对象实例以及数组都在堆中分配。堆是垃圾收集器管理的主要对象,又称“GC堆”,如果要细分的话,java堆还可以分为Eden,From Survivor,To Survivor等。线程共享

5)方法区

方法区用于存储被虚拟机加载的类信息,常量,静态变量,即时编译器编译的代码等,方法区又称“非堆”,用于与堆区分。线程共享,对于方法区的内存回收主要是对常量池的回收以及对对象的卸载。运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量以及符号引用。

  直接内存(Direct Memory)并不是java虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道与缓冲区的I/O方式,它可以使用native函数库直接分配对外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作,避免了java堆与native堆中来回复制数据。

垃圾收集器的种类有:Serial收集器,ParNew收集器,Parallel Scavenge收集器,Serial Old收集器,Parallel Old收集器,CMS收集器,G1收集器

运行时内存以及垃圾收集器

1)Serial收集器

单线程,工作时需要暂停用户所有线程,可以和CMS配合工作

2)ParNew收集器

多线程,工作时需要用户暂停所有线程,可以和CMS配合工作

3)Parallel Scavenge收集器

Parallel Scavenge收集器的主要目标是达到可控制吞吐量。吞吐量就是CPU运行用户代码的时间占CPU消耗时间的比值,即吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集的时间)。-XX:MaxGCPauseMillis=M设置垃圾收集时间尽量在M毫秒完成,减低M值不一定可以提高吞吐量;-XX:GCTimeRatio=N用于设置允许最大的垃圾收集时间占总时间的1/(1+N),默认值为99

4)Serial Old收集器

Serial Old是老年代版本,可以与Parallel Scavenge收集器配合使用,或者作为CMS的后备预案

5)Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,Parallel Scavenge收集器不可以与CMS收集器配合使用,如果使用Serial Old收集器,性能会被拖累,所以可以选择Parallel Old收集器

6)CMS收集器

CMS收集器(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS的工作步骤:初始标记,并发标记,重新标记,并发删除。在初始标记以及重新标记的时候需要暂停用户所有线程,但是耗时非常短。初始标记就是标记直接与GC ROOT关联的对象,并发标记就是标记从GC ROOT可以连通的对象,重新标记就是在并发标记的过程中因为用户线程继续运行导致标记变更的对象的记录,最后并发清除未被标记的对象。

CMS收集器的优点:并发收集,低停顿

CMS收集器的缺点:CMS对CPU比较敏感,默认线程数为(CPU数量+3)/4,无法处理浮动垃圾(垃圾回收过程中用户线程没有停止导致),标记清楚算法会产生空间碎片

7)G1收集器

G1收集器采用标记整理算法,没有碎片;可以精确控制停顿,指定M毫秒的时间片段内,消耗在垃圾搜集上的时间不能超过N毫秒;可以在不牺牲吞吐量的情况下完成低停顿的垃圾回收,原理:把堆分成多个大小固定的独立区域,并跟踪垃圾的堆积程度,在后台维护一个优先级列表

垃圾收集器采用的算法:

1)标记清除算法

会产生大量的空间碎片

2)复制算法

减小了内存,把内存分成2分,一份保持完整,当另一份存储不下的时候启用

3)标记整理算法

4)分代搜集

把堆分成新生代以及老年代,新生代使用复制算法,老年代使用标记整理算法

java虚拟机没有使用引用计数算法,而是使用根搜索算法来查找不可用对象。

引用计数算法:当一个对象被引用,引用计数器加1,当引用失效的时候,引用计数器减1,当引用计数器为0的时候,被认为是可收回对象。

根搜索算法:通过一系列的”GC ROOTS“对象作为起始点,从这些点向下搜索,不能喝GC ROOTS联通的对象便是可回收对象

java中可以作为“GC ROOTS”的对象包括:虚拟机栈(栈帧中的本地变量表)中的引用对象,方法区中的类静态属性引用的对象,方法区中的常量引用对象,本地方法栈中JNI(Native方法)的引用对象。

引用的类型:

强引用:只要强引用还存在,GC永远不会回收内存

软引用:对于软引用关联着的对象,当要发生内存溢出异常之前,把这些对象列入回收范围并进行第二次回收

弱引用:对于弱引用关联着的对象,能下一次GC工作的时候,不管内存是否足够,都会被回收

虚引用:虚引用不会影响关联着的对象的生存时间,也无法通过虚引用来获取对象的实例,虚引用的唯一目的就是这个对象被GC回收时收到一个系统通知

垃圾收集器的执行逻辑:

1)用根搜索算法进行第一次标记

2)对标记的对象进行一次筛选,如果对象覆盖了finalize方法,并且没有执行过,那么有可能在这个finalize方法中再次使用这个对象,这样的话就把该对象从待删除的对象队列中删除。finalize方法只能执行一次,保证对象循环引用的时候不可能永远存在。

3)java虚拟机创建一个低优先级的Finalize线程去执行对象覆盖的finalize方法,垃圾收集器对待删除队列再次进行标记

4)垃圾收集器把两次标记的对象回收了

===========================================================================

 下面我们来看下GC日志:

 1 package com.froest.excel;
 2 
 3 public class Test2 {
 4   private static int _1M = 1024 * 1024;
 5 
 6   /**
 7    * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
 8    * 
 9    * @param args
10    */
11   public static void main(String[] args) {
12     // TODO Auto-generated method stub
13     byte[] alloc1, alloc2, alloc3, alloc4;
14     alloc1 = new byte[2 * _1M];
15     alloc2 = new byte[2 * _1M];
16     alloc3 = new byte[2 * _1M];
17 //    alloc4 = new byte[4 * _1M];//发生Minor GC
18   }
19 }
View Code

相关文章:

  • 2021-11-29
  • 2021-05-01
  • 2022-01-21
  • 2021-12-13
  • 2021-07-01
  • 2021-10-08
  • 2021-07-12
猜你喜欢
  • 2022-12-23
  • 2021-11-11
  • 2021-12-01
  • 2022-01-15
  • 2022-12-23
  • 2022-12-23
  • 2022-01-15
相关资源
相似解决方案