JVM内存管理

本文是阅读《深入理解java虚拟机:JVM高级特性与最佳实践》的读书笔记,在此对作者标识感谢。

1、内存区域

根据《java虚拟机规范(第2版)》的规定,虚拟机管理的内存包括以下几个运行时数据区域:

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 方法区

1.1程序计数器

Program Counter Register是一块比较小的空间,作用可以看做当前线程所执行的字节码的行号指示器。属于各线程私有的指示器,在线程切换后找到之前运行的虚拟机字节码位置。如果是Native方法则计数器值为空。此区域没有OutOfMemoryError异常

1.2java虚拟机栈

java Virtual Machine Stacks也是线程私有的。调用方法时创建栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等信息。调用方法的过程伴随着栈帧的入栈和出栈。
这里要注意局部变量表在编译期间完成分配。通常存储编译期间可知的基本数据类型、引用类型和retrunAddress类型(指向一条字节码指令的地址)
这里可能触发:1、请求栈深度大于虚拟机允许的深度,抛出StackOverflowError;2、虚拟机栈可动态拓展时,动态拓展请求无法满足是抛出OutOfMemoryError异常。

1.3本地方法栈

基本与虚拟机栈相似,只不过是Native方法使用的,部分虚拟机将其与java虚拟机栈合并

1.4方法区

Method Area 线程共享内存,主要存储虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码等数据。

  • 运行时常量池(Runtime Constant Pool)
    用于存放编译器生成的各种字面量和符号引用

1.5直接内存

Direct Memory并不是虚拟机运行时数据区
在JDK1.4中加入了NIO类,引入了一种基于通道与缓冲区的I/O方式,可以使用native方法直接分配堆外内存,然后通过存储在java堆里面的directByteBuffer对象来引用。在一些场景会显著提高性能。直接内存不受java堆大小限制,但是受本机内存限制,因此虚拟机参数可能忽略这部分导致OutOfMemoryError。

2、内存回收

垃圾回收(Garbage Collection,GC)。有三个问题:

  • 哪些内存需要回收?
  • 什么时候回收
  • 如何回收

2.1哪些内存需要回收?

即如何判断对象已不再使用,常用有两种算法:

引用计数法

即给对象添加一个引用计数器,当一个地方引用时,计数器加1;当引用失效时,计数器减一;当计数器为0时,对象就不再使用。引用计数算法实现简单,效率高,但是无法解决循环引用的问题

根搜索算法

即通过可作为GC Roots的对象作为起始点,从这些节点向下搜索,搜索走过的路径称为引用链,当一个对象从GC Roots搜索不到,即不可达时证明该对象无引用。
java语言中可作为GC Roots的对象包括:

  • 虚拟机栈(栈帧中的本地变量表)中的引用的对象
  • 方法区的类静态属性引用的对象
  • 方法区的常量引用的对象
  • 本地方法栈中JNI的引用对象
    java对于引用概念的扩展:强引用、软引用、弱引用、虚引用。(这里不是很理解)

2.2什么时候回收?

这个问题书中没有明确的说法,对于几个不同地方的阅读和理解,大致简单的可以理解为:分配内存时不够了即调用GC。具体的呢,首先由年轻代区域不够是触发Minor GC(即新生代垃圾回收算法),当触发Minor GC时同时伴随着一部分数据从新生代向老年代的移动,这个移动的过程中,老年代又不够用了,那么就会触发Major GC/Full GC。一般情况下后者比前者慢10倍以上

2.3如何回收

  1. 标记-清除算法
    如名字一样算法分为两个阶段,先标记需要回收的对象,标记完成后清除这些标记的对象。最基础的收集算法,但存在两个问题:1、标记和清除过程都很耗时,所以效率不高;2、清除后易导致内存碎片化,碎片过多会导致分配大对象时找不到合适位置而提前进行GC
  2. 复制算法
    将内存分为大小相等的两块,每次只用1块,当一块用完了,把存活的搬到另一块,然后把这一块全部清除。
    这个算法实现简单,但是内存浪费过大。而这个算法的改进版是不需要1:1划分区域,因为大量的对象朝生夕死,所以真正移动的对象并不多,所以划分一般(8+1):1来划分,这样就只浪费10%的内存。
  3. 标记整理算法
    先标记,然后往一端移动,清除另一端
  4. 分代回收
    根据不同代的特性,选择不同的算法

2.4分配与回收

1、 对象优先在新生代Eden分配,当Eden空间不足时触发Minor GC。存活对象移动到Survivor去,若放不下,则对象移动到老年代,此时老年代内存不足时触发Major GC
2、 大对象(长字符串和数组)直接进入老年代
3、 长期存活对象移动到老年代,在servivor中对象每次GC之后年龄+1,达到一定阈值后移动到老年代
4.、动态年龄判断,相同年龄的总和大于servivor空间一半时,大于等于的对象直接进入老年区。

posted @ 2018-10-30 16:35  C洋  阅读(106)  评论(0编辑  收藏  举报