最近看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管理內存的可行性,改由自己來管理,當發生內存溢出時排查起來非常困難。
- 內存難以控制
- 使用堆外內存的優點
-
