堆内内存与堆外内存
最近看Spark的 StorageLevel(存储级别) 源码的时候 看到有 useOffHeap 这个标签, 觉得有必要挖掘一下
堆内内存(on-heap memory)
-
堆内内存是java程序员在日常工作中解除比较多的, 可以在jvm参数中使用-Xms, -Xmx 等参数来设置堆的大小和最大值
-
堆内内存 = 年轻代 + 老年代 + 持久代
-
年轻代 (Young Generation)
-
存放的是新生成的对象
-
年轻代的目标是尽可能快速的收集掉那些生命周期短的对象
-
Eden
-
大部分对象在Eden区中生成
-
当Eden区满时, 依然存活的对象将被复制到Survivor区, 当一个Survivor 区满时, 此区的存活对象将被复制到另外一个Survivor区
-
-
Survivor(通常2个)
-
当两个 Survivor 区 都满时, 从第一个Survivor 区 被复制过来 且 依旧存活的 对象会被复制到 老年区(Tenured)
-
Survivor 的两个区是对称的, 没有先后关系, 所有同一个区中可能同时存在从Eden复制过来的对象 和 从前一个 Survivor 复制过来的对象。
-
Survivor 区可以根据需要配置多个, 从而增加对象在年轻代的存在时间, 减少被放到老年代的可能。
-
-
-
老年代 (Old Generation)
- 存放了在年轻代中经历了N次垃圾回收后仍存活的对象, 是一些生命周期较长的对象
-
持久代 (Permanent Generation)
-
存放静态文件, 如静态类和方法等。持久代对垃圾回收没有显著影响, 但是有些应用可能动态生成或者调用一些class, 比如Hibernate, Mybatis 等, 此时需要设置一个较大的持久代空间来存放这些运行过程中新增的类。
-
设置持久代大小参数: -XX:MaxPermSize=
Perm => Permanent
-
-
垃圾回收(GC)
- Scavenge GC
- 一般当新对象生成并且在Eden申请空间失败时就会触发Scavenger GC, 对Eden区域进行GC, 清除非存活对象, 并且把尚存或的对象移动到Survivor区, 然后整理两个Survivor区。
- 该方式的GC是对年轻代的Eden区进行,不会影响到年老代。
- 由于大部分对象是从Eden区开始的, 同时Eden区分配的内存不会很大, 所以Eden区的GC会很频繁。
- Full GC
- 对整个堆进行整理, 包括Young, Tenured 和Permanent。
- 所消耗的时间较长, 所以要尽量减少 Full GC 的次数
- d导致 Full GC 的可能原因:
- 老年代(Tenured) 被写满
- 持久代(Permanent) 被写满
- System.gc() 被显示调用
- 上一次GC之后Heap 的各域分配策略动态变化
- 常用垃圾回收算法
- Reference Counting (引用计数算法)
- Mark-Sweep (标记清除法)
- Coping (复制法)
- Mark-Compact (标记压缩法)
- Generational Collecting (分代收集法)
- Region (分区法)
- GC Roots Tracing (可达性算法)
- Scavenge GC
堆外内存(off-heap memory)
-
定义
- 堆外内存就是把内存对象分配在Java虚拟机的堆以外的内存
-
java.nio.DirectByteBuffer
-
Java 开发者经常用 java.nio.DirectByteBuffer 对象进行堆外内存的管理和使用, 该类会在创建对象时就分配堆外内存。
-
JDK1.8 取消了方法区, 由MetaSpace(元空间)代替。-XX:MaxPermSize由 -XX:MetaspaceSize, -XX:MaxMetaspaceSize 等代替
-
对堆外内存的申请主要是通过成员变量unsafe来操作
package java.nio; import java.io.FileDescriptor; import sun.misc.Cleaner; import sun.misc.Unsafe; import sun.misc.VM; import sun.nio.ch.DirectBuffer; class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer { // Cached unsafe-access object 缓存的unsafe获取对象 protected static final Unsafe unsafe = Bits.unsafe(); // Cached array base offset 缓存的数组基本偏移量 private static final long arrayBaseOffset = (long)unsafe.arrayBaseOffset(byte[].class); // Cached unaligned-access capability 缓存的内存不对齐访问 protected static final boolean unaligned = Bits.unaligned(); // Base address, used in all indexing calculations 基本地址, 应用于所有参数运算 // NOTE: moved up to Buffer.java for speed in JNI GetDirectBufferAddress // protected long address; // 基本地址呗移植到java 缓冲区 以 提升 JNI(Java Native Interface java 本地接口) 获取直接的缓存地址的 速度 // An object attached to this buffer. If this buffer is a view of another // buffer then we use this field to keep a reference to that buffer to // ensure that its memory isn't freed before we are done with it. // 一个依附在 缓冲区的对象。 如果该Buffer是另一个buffer的视图, 我们需要使用该域来维持一个对于该缓冲区的记录来确保它的内存不在我们完成任务之前被释放 private final Object att; public Object attachment() { return att; } // 重分配器(实现了Runnable接口, 是个线程) private static class Deallocator implements Runnable { private static Unsafe unsafe = Unsafe.getUnsafe(); // 地址 private long address; // 大小 private long size; // 容量 private int capacity; private Deallocator(long address, long size, int capacity) { assert (address != 0); this.address = address; this.size = size; this.capacity = capacity; } public void run() { if (address == 0) { // Paranoia 妄想... 如果没有地址就别想运行 return; } // 释放传入地址的内存 unsafe.freeMemory(address); // 降地址设为0 address = 0; // 去除保留内存 Bits.unreserveMemory(size, capacity); } } // 清理器 private final Cleaner cleaner; public Cleaner cleaner() { return cleaner; } // Primary constructor 主要的构造器 // DirectByteBuffer(int cap) { // package-private 私有包 super(-1, 0, cap, cap); // 是否为页对齐的内存 boolean pa = VM.isDirectMemoryPageAligned(); // 获取页尺寸 int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); // 保留内存 Bits.reserveMemory(size, cap); long base = 0; try { // 分配内存 base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) { // 如果内存溢出就去除保留内存 Bits.unreserveMemory(size, cap); throw x; } // 设置内存 unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary 向上取整至页面边缘 address = base + ps - (base & (ps - 1)); } else { address = base; } cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; } // Invoked to construct a direct ByteBuffer referring to the block of // memory. A given arbitrary object may also be attached to the buffer. // 请求构造一个直接的指向内存块的字节缓冲区。 一个已得的任意对象也可能依附于该缓冲区 DirectByteBuffer(long addr, int cap, Object ob) { super(-1, 0, cap, cap); address = addr; cleaner = null; att = ob; } // Invoked only by JNI: NewDirectByteBuffer(void*, long) // 只被java本地接口请求 private DirectByteBuffer(long addr, int cap) { super(-1, 0, cap, cap); address = addr; cleaner = null; att = null; } // For memory-mapped buffers -- invoked by FileChannelImpl via reflection // 内存匹配的 缓冲区, 由NIO中 文件管道的 实现类 通过反射 请求 protected DirectByteBuffer(int cap, long addr, FileDescriptor fd, Runnable unmapper) { super(-1, 0, cap, cap, fd); address = addr; cleaner = Cleaner.create(this, unmapper); att = null; } // For duplicates and slices 去重和切片 // DirectByteBuffer(DirectBuffer db, // package-private 私有包 int mark, int pos, int lim, int cap, int off) { super(mark, pos, lim, cap); address = db.address() + off; cleaner = null; att = db; } // 切片 public ByteBuffer slice() { // 当前位置 int pos = this.position(); // 限制界限 int lim = this.limit(); // 断言当前位置小于等于界限 assert (pos <= lim); // 如果越界了就返回0 int rem = (pos <= lim ? lim - pos : 0); // 不明白向左位移0位干嘛, 该位置还是该位置..., 获取偏移量 int off = (pos << 0); // 断言偏移量 >= 0 assert (off >= 0); return new DirectByteBuffer(this, -1, 0, rem, rem, off); } // 去重 public ByteBuffer duplicate() { return new DirectByteBuffer(this, this.markValue(), this.position(), this.limit(), this.capacity(), 0); } // 设置为只读缓冲区 public ByteBuffer asReadOnlyBuffer() { return new DirectByteBufferR(this, this.markValue(), this.position(), this.limit(), this.capacity(), 0); } public long address() { return address; } private long ix(int i) { return address + ((long)i << 0); } public byte get() { return ((unsafe.getByte(ix(nextGetIndex())))); } public byte get(int i) { return ((unsafe.getByte(ix(checkIndex(i))))); } // 获取字节缓冲区 public ByteBuffer get(byte[] dst, int offset, int length) { if (((long)length << 0) > Bits.JNI_COPY_TO_ARRAY_THRESHOLD) { checkBounds(offset, length, dst.length); int pos = position(); int lim = limit(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); if (length > rem) throw new BufferUnderflowException(); Bits.copyToArray(ix(pos), dst, arrayBaseOffset, (long)offset << 0, (long)length << 0); position(pos + length); } else { super.get(dst, offset, length); } return this; } public ByteBuffer put(byte x) { unsafe.putByte(ix(nextPutIndex()), ((x))); return this; } public ByteBuffer put(int i, byte x) { unsafe.putByte(ix(checkIndex(i)), ((x))); return this; } public ByteBuffer put(ByteBuffer src) { if (src instanceof DirectByteBuffer) { if (src == this) throw new IllegalArgumentException(); DirectByteBuffer sb = (DirectByteBuffer)src; int spos = sb.position(); int slim = sb.limit(); assert (spos <= slim); int srem = (spos <= slim ? slim - spos : 0); int pos = position(); int lim = limit(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); if (srem > rem) throw new BufferOverflowException(); unsafe.copyMemory(sb.ix(spos), ix(pos), (long)srem << 0); sb.position(spos + srem); position(pos + srem); } else if (src.hb != null) { int spos = src.position(); int slim = src.limit(); assert (spos <= slim); int srem = (spos <= slim ? slim - spos : 0); put(src.hb, src.offset + spos, srem); src.position(spos + srem); } else { super.put(src); } return this; } public ByteBuffer put(byte[] src, int offset, int length) { if (((long)length << 0) > Bits.JNI_COPY_FROM_ARRAY_THRESHOLD) { checkBounds(offset, length, src.length); int pos = position(); int lim = limit(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); if (length > rem) throw new BufferOverflowException(); Bits.copyFromArray(src, arrayBaseOffset, (long)offset << 0, ix(pos), (long)length << 0); position(pos + length); } else { super.put(src, offset, length); } return this; } // 压缩 public ByteBuffer compact() { int pos = position(); int lim = limit(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); unsafe.copyMemory(ix(pos), ix(0), (long)rem << 0); position(rem); limit(capacity()); discardMark(); return this; } // 是否直接 public boolean isDirect() { return true; } // 是否只读 public boolean isReadOnly() { return false; } byte _get(int i) { // package-private return unsafe.getByte(address + i); } void _put(int i, byte b) { // package-private unsafe.putByte(address + i, b); } private char getChar(long a) { if (unaligned) { char x = unsafe.getChar(a); return (nativeByteOrder ? x : Bits.swap(x)); } return Bits.getChar(a, bigEndian); } public char getChar() { return getChar(ix(nextGetIndex((1 << 1)))); } public char getChar(int i) { return getChar(ix(checkIndex(i, (1 << 1)))); } private ByteBuffer putChar(long a, char x) { if (unaligned) { char y = (x); unsafe.putChar(a, (nativeByteOrder ? y : Bits.swap(y))); } else { Bits.putChar(a, x, bigEndian); } return this; } public ByteBuffer putChar(char x) { putChar(ix(nextPutIndex((1 << 1))), x); return this; } public ByteBuffer putChar(int i, char x) { putChar(ix(checkIndex(i, (1 << 1))), x); return this; } // 设置为char型的缓冲区 public CharBuffer asCharBuffer() { int off = this.position(); int lim = this.limit(); assert (off <= lim); int rem = (off <= lim ? lim - off : 0); int size = rem >> 1; if (!unaligned && ((address + off) % (1 << 1) != 0)) { return (bigEndian ? (CharBuffer)(new ByteBufferAsCharBufferB(this, -1, 0, size, size, off)) : (CharBuffer)(new ByteBufferAsCharBufferL(this, -1, 0, size, size, off))); } else { return (nativeByteOrder ? (CharBuffer)(new DirectCharBufferU(this, -1, 0, size, size, off)) : (CharBuffer)(new DirectCharBufferS(this, -1, 0, size, size, off))); } } private short getShort(long a) { if (unaligned) { short x = unsafe.getShort(a); return (nativeByteOrder ? x : Bits.swap(x)); } return Bits.getShort(a, bigEndian); } public short getShort() { return getShort(ix(nextGetIndex((1 << 1)))); } public short getShort(int i) { return getShort(ix(checkIndex(i, (1 << 1)))); } private ByteBuffer putShort(long a, short x) { if (unaligned) { short y = (x); unsafe.putShort(a, (nativeByteOrder ? y : Bits.swap(y))); } else { Bits.putShort(a, x, bigEndian); } return this; } public ByteBuffer putShort(short x) { putShort(ix(nextPutIndex((1 << 1))), x); return this; } public ByteBuffer putShort(int i, short x) { putShort(ix(checkIndex(i, (1 << 1))), x); return this; } // 设置为short类型的缓冲区 public ShortBuffer asShortBuffer() { int off = this.position(); int lim = this.limit(); assert (off <= lim); int rem = (off <= lim ? lim - off : 0); int size = rem >> 1; if (!unaligned && ((address + off) % (1 << 1) != 0)) { return (bigEndian ? (ShortBuffer)(new ByteBufferAsShortBufferB(this, -1, 0, size, size, off)) : (ShortBuffer)(new ByteBufferAsShortBufferL(this, -1, 0, size, size, off))); } else { return (nativeByteOrder ? (ShortBuffer)(new DirectShortBufferU(this, -1, 0, size, size, off)) : (ShortBuffer)(new DirectShortBufferS(this, -1, 0, size, size, off))); } } private int getInt(long a) { if (unaligned) { int x = unsafe.getInt(a); return (nativeByteOrder ? x : Bits.swap(x)); } return Bits.getInt(a, bigEndian); } public int getInt() { return getInt(ix(nextGetIndex((1 << 2)))); } public int getInt(int i) { return getInt(ix(checkIndex(i, (1 << 2)))); } private ByteBuffer putInt(long a, int x) { if (unaligned) { int y = (x); unsafe.putInt(a, (nativeByteOrder ? y : Bits.swap(y))); } else { Bits.putInt(a, x, bigEndian); } return this; } public ByteBuffer putInt(int x) { putInt(ix(nextPutIndex((1 << 2))), x); return this; } public ByteBuffer putInt(int i, int x) { putInt(ix(checkIndex(i, (1 << 2))), x); return this; } // 设置为Int类型的缓冲区 public IntBuffer asIntBuffer() { int off = this.position(); int lim = this.limit(); assert (off <= lim); int rem = (off <= lim ? lim - off : 0); int size = rem >> 2; if (!unaligned && ((address + off) % (1 << 2) != 0)) { return (bigEndian ? (IntBuffer)(new ByteBufferAsIntBufferB(this, -1, 0, size, size, off)) : (IntBuffer)(new ByteBufferAsIntBufferL(this, -1, 0, size, size, off))); } else { return (nativeByteOrder ? (IntBuffer)(new DirectIntBufferU(this, -1, 0, size, size, off)) : (IntBuffer)(new DirectIntBufferS(this, -1, 0, size, size, off))); } } private long getLong(long a) { if (unaligned) { long x = unsafe.getLong(a); return (nativeByteOrder ? x : Bits.swap(x)); } return Bits.getLong(a, bigEndian); } public long getLong() { return getLong(ix(nextGetIndex((1 << 3)))); } public long getLong(int i) { return getLong(ix(checkIndex(i, (1 << 3)))); } private ByteBuffer putLong(long a, long x) { if (unaligned) { long y = (x); unsafe.putLong(a, (nativeByteOrder ? y : Bits.swap(y))); } else { Bits.putLong(a, x, bigEndian); } return this; } public ByteBuffer putLong(long x) { putLong(ix(nextPutIndex((1 << 3))), x); return this; } public ByteBuffer putLong(int i, long x) { putLong(ix(checkIndex(i, (1 << 3))), x); return this; } // 设置为long类型的缓冲区 public LongBuffer asLongBuffer() { int off = this.position(); int lim = this.limit(); assert (off <= lim); int rem = (off <= lim ? lim - off : 0); int size = rem >> 3; if (!unaligned && ((address + off) % (1 << 3) != 0)) { return (bigEndian ? (LongBuffer)(new ByteBufferAsLongBufferB(this, -1, 0, size, size, off)) : (LongBuffer)(new ByteBufferAsLongBufferL(this, -1, 0, size, size, off))); } else { return (nativeByteOrder ? (LongBuffer)(new DirectLongBufferU(this, -1, 0, size, size, off)) : (LongBuffer)(new DirectLongBufferS(this, -1, 0, size, size, off))); } } private float getFloat(long a) { if (unaligned) { int x = unsafe.getInt(a); return Float.intBitsToFloat(nativeByteOrder ? x : Bits.swap(x)); } return Bits.getFloat(a, bigEndian); } public float getFloat() { return getFloat(ix(nextGetIndex((1 << 2)))); } public float getFloat(int i) { return getFloat(ix(checkIndex(i, (1 << 2)))); } private ByteBuffer putFloat(long a, float x) { if (unaligned) { int y = Float.floatToRawIntBits(x); unsafe.putInt(a, (nativeByteOrder ? y : Bits.swap(y))); } else { Bits.putFloat(a, x, bigEndian); } return this; } public ByteBuffer putFloat(float x) { putFloat(ix(nextPutIndex((1 << 2))), x); return this; } public ByteBuffer putFloat(int i, float x) { putFloat(ix(checkIndex(i, (1 << 2))), x); return this; } // 设置为Float类型的缓冲区 public FloatBuffer asFloatBuffer() { int off = this.position(); int lim = this.limit(); assert (off <= lim); int rem = (off <= lim ? lim - off : 0); int size = rem >> 2; if (!unaligned && ((address + off) % (1 << 2) != 0)) { return (bigEndian ? (FloatBuffer)(new ByteBufferAsFloatBufferB(this, -1, 0, size, size, off)) : (FloatBuffer)(new ByteBufferAsFloatBufferL(this, -1, 0, size, size, off))); } else { return (nativeByteOrder ? (FloatBuffer)(new DirectFloatBufferU(this, -1, 0, size, size, off)) : (FloatBuffer)(new DirectFloatBufferS(this, -1, 0, size, size, off))); } } private double getDouble(long a) { if (unaligned) { long x = unsafe.getLong(a); return Double.longBitsToDouble(nativeByteOrder ? x : Bits.swap(x)); } return Bits.getDouble(a, bigEndian); } public double getDouble() { return getDouble(ix(nextGetIndex((1 << 3)))); } public double getDouble(int i) { return getDouble(ix(checkIndex(i, (1 << 3)))); } private ByteBuffer putDouble(long a, double x) { if (unaligned) { long y = Double.doubleToRawLongBits(x); unsafe.putLong(a, (nativeByteOrder ? y : Bits.swap(y))); } else { Bits.putDouble(a, x, bigEndian); } return this; } public ByteBuffer putDouble(double x) { putDouble(ix(nextPutIndex((1 << 3))), x); return this; } public ByteBuffer putDouble(int i, double x) { putDouble(ix(checkIndex(i, (1 << 3))), x); return this; } // 设置为double类型的缓冲区 public DoubleBuffer asDoubleBuffer() { int off = this.position(); int lim = this.limit(); assert (off <= lim); int rem = (off <= lim ? lim - off : 0); int size = rem >> 3; if (!unaligned && ((address + off) % (1 << 3) != 0)) { return (bigEndian ? (DoubleBuffer)(new ByteBufferAsDoubleBufferB(this, -1, 0, size, size, off)) : (DoubleBuffer)(new ByteBufferAsDoubleBufferL(this, -1, 0, size, size, off))); } else { return (nativeByteOrder ? (DoubleBuffer)(new DirectDoubleBufferU(this, -1, 0, size, size, off)) : (DoubleBuffer)(new DirectDoubleBufferS(this, -1, 0, size, size, off))); } } }
- 使用堆外内存的优点
- 减少了垃圾回收机制(GC 会暂停其他的工作)
- 加快了复制的速度
- 堆内在flush到远程时, 会先复制到直接内存(非堆内存), 然后再发送。
- 而堆外内存(本身就是物理机内存)几乎省略了该步骤
- 使用堆外内存的缺点
- 内存难以控制
- 使用了堆外内存就间接失去了JVM管理内存的可行性,改由自己来管理,当发生内存溢出时排查起来非常困难。
- 内存难以控制
- 使用堆外内存的优点
-