jdk

首先看一下jdk的组成,如下图所示,Java程序设计语言、Java虚拟机、Java API类库,统称为jdk. java

jvm

运行时数据区

  • 程序计数器(Program counter register)

    当前线程所执行字节码的行号指示器,线程私有

  • Java虚拟机栈(Java Virtual Machine Stacks)

    每个线程执行的方法栈,每个方法为一个栈帧。 栈帧包含局部变量表、操作数栈、动态链接、方法出口。

  • 本地方法栈(Native Methed Stack)

    native方法栈,作用类似Java虚拟机栈,面向对象不同。

  • Java堆(Java Heap)

    大部分Java对象存放的位置

  • 方法区(Method Area)

    存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

  • 运行时常量池 方法区的一部分,用于保存编译期生成的各种字面量和符号引用,类加载后进入常量池

  • 直接内存 非虚拟机运行时数据区一部分,使用一些native分配的堆外内存不受虚拟机管理

对象的内存布局

  • 对象头(Header)

    HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称它为"MarkWord"。 对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

  • 实例数据(Instance data)

  • 对齐填充(padding)

gc

首先致命三连:What? When? How?

要回收什么

堆中存放着几乎所有的对象,该如何确定哪些对象已"死"呢?

引用计数法

给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;计数器为0的对象就是不能再被使用的,即对象已”死”。

需要说明一下,在主流的JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的循环引用问题。

可达性分析法

此算法的核心思想是通过一系列“根”对象作为起始点,从根出发开始遍历,可以被访问到称为可达对象,即有用的,反之,则是要回收的。

根对象是认为一定有用的东西,有以下4类:

  • 活动栈帧中的局部变量
  • 方法区中类静态属性引用的变量
  • 方法区中常量引用的变量
  • JNI引用的对象

什么时候回收

当新建对象内存分配失败的时候

回收算法

标记清除算法(mark and sweep)

原理

顾名思义,算法分为两个阶段,标记和清除。 在标记阶段,收集器从“根”对象出发,把能访问到的都打上标识。 而在清除阶段,收集器对堆内存从头到尾进行线性遍历,发现某个对象没有标记,则将其回收。 标记和清除阶段,应用程序都将暂停,Stop the world.

缺点

标记清除后存在大量碎片

标记压缩算法(mark and compact)

原理

同样,算法也是两个阶段。 标记阶段和标记清除算法一样。 区别在于压缩阶段,它的目的是把可达对象移动到堆内存的同一块区域中,使其紧凑排列,以减少内存碎片。

缺点

需遍历多次以迁移对象,需要额外的开销,实现复杂

半区复制算法(copying)

原理

之所以叫半区复制,是因为它将堆内存分为两个半区,只用其中一个半区来进行对象内存分配。 当这个半区内存不够时,则开始垃圾收集,将可达对象复制到另一个半区。

效果

该算法目的也是为了解决内存碎片问题,对比标记压缩算法,它不需要遍历多次,节约时间。 但有一个主要缺点就是可用堆内存减小一半,对于大对象,复制比标记代价更高。

分代回收算法

首先一个基础假设,大部分对象只存在很短时间。

然后将内存分为新生代和老生代,新生代是经常需要垃圾回收的(Minor GC), 将新生代再进一步划分,分为Eden、Survior1、Survior2,在新生代用半区复制算法, 每次将eden和一个survior的对象回收后拷贝到另一个survior中,并标记存活次数,因为有前面的基础假设,拷贝的对象一般不会太多。

当存活次数到一定次数后(比如15次),将对象放入老生代中,老生代发生GC的概率更小,在老生代就采用标记压缩算法。

除了新生代和老生代外,还有个持久代(Permanent Generation),java8以后为MetaSpace,功能如下。

> Permanent Generation
1. 放置ClassLoader读进来的class,除系统class外
2. 放置String.intern后的结果
3. 可能出现OutOfMemoryError:PermGen Space

> Meta Space
1. java8 之后使用Meta space,取消Permanent space
2. String.intern的结果放入堆
3. meta space默认不限制,使用系统内存

综上,分带回收分类如下:

- 新生代(1)
  - Eden(8)
  - Survior0(1)
  - Survior1(1)
- 老生代(2)
- 持久代/Meta space

说了这么多,每一块内存到底多大。不要慌,这些都是参数,可以配置的。

-XX:NewRatio=2,老生代/新生代比例,默认2
-XX:SurvivorRatio=8,Eden/Survivor比例,默认8,survior有两块,所以8:1:1
-XX:MaxTenuringThreshold=15,新生代转至老生代的阈值,默认15
-Xms2048m   最小堆
-Xmx2048m   最大堆
-Xmn1024m   新生代
-Xss1M

垃圾收集器

虚拟机所包含的所有收集器如下图所示 gc-collector

Serial

ParNew

Parallel Scavenge

Serial Old

Parallel Old

CMS

G1

内存分配策略

优先在eden上分配

大对象直接进入老年代

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

每存活一次MinorGC,对象年龄增加1,当到达一定年龄(默认15),将转移到老年代,不在survivor间拷来拷去。

对象动态年龄判断

如果Survivor空间中的相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄

空间分配担保

在发生MinorGC之前,虚拟机会先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果条件成立,那么MinorGC可以确保是安全的。

工具

jps(JVM Process Status)

虚拟机进程状况工具,显示指定系统内所有的HotSpot虚拟机进程

选项 作用
-q 只输出LVMID,省略主类名称
-m 输出虚拟机启动时传给main()函数的参数
-l 输出主类的全名,如果是jar包,则jar包路径
-v 输出虚拟机进程启动时JVM参数

jstat(JVM statistics Monitoring Tool)

用于收集HotSpot虚拟机各方面的运行数据

jinfo

显示虚拟机配置信息

jmap

生成虚拟机的内存转储快照(heapdump文件)

jhat(JVM Heap Dump Browser)

用于分析heapdump文件,它会建立一个http服务器,让用户可以在浏览器上查看分析结果

jstack

显示虚拟机线程快照

jconsole

jvisualvm

其它

第三方:Eclipse Memory Analyzer(MAT)
在线服务:gceasy.io,fastthread.io

其它

引用

  • 强引用

    类似Object obj = new Object()引用的对象,只要还存在强引用,垃圾回收器永远不会回收。

  • 软引用

    描述有用、但非必须的对象,在将要发生内存溢出之前回收,如果还不够,才跑出内存溢出异常

  • 弱引用

    同软引用,描述有用、但非必须的对象,只不过在下一次gc就会回收

  • 虚引用

    todo,没理解透

cpu 原子操作 read,write,load,store,assign,use,lock,unlock

Copyright © axboy.cn 2019 all right reserved,powered by Gitbook该文件修订时间: 2020-07-27 03:53:54

results matching ""

    No results matching ""