PhantomReference虚引用

在分析堆外内存回收之前,先了解下PhantomReference虚引用。

PhantomReference需要与ReferenceQueue引用队列结合使用,在GC进行垃圾回收的时候,如果发现一个对象只有虚引用在引用它,则认为该对象需要被回收,会将引用该对象的虚引用加入到与其关联的ReferenceQueue队列中,开发者可以通过ReferenceQueue获取需要被回收的对象,然后做一些清理操作,从队列中获取过的元素会从队列中清除,之后GC就可以对该对象进行回收。

虚引用提供了一种追踪对象垃圾回收状态的机制,让开发者知道哪些对象准备进行回收,在回收之前开发者可以进行一些清理工作,之后GC就可以将对象进行真正的回收。

来看一个虚引用的使用例子:

  1. 创建一个ReferenceQueue队列queue,用于跟踪对象的回收;
  2. 创建一个obj对象,通过new创建的是强引用,只要强引用存在,对象就不会被回收;
  3. 创建一个虚引用PhantomReference,将obj对象和ReferenceQueue队列传入,此时phantomReference里面引用了obj对象,并关联着引用队列queue;
  4. 同样的方式创建另一个obj1对象和虚引用对象phantomReference1;
  5. 将obj置为NULL,此时强引用关系失效;
  6. 调用 System.gc()进行垃圾回收;
  7. 由于obj的强引用关系失效,所以GC认为该对象需要被回收,会将引用该对象的虚引用phantomReference对象放入到与其关联的引用队列queue中;
  8. 通过poll从引用队列queue中获取对象,可以发现会获取到phantomReference对象,poll获取之后会将对象从引用队列中删除,之后会被垃圾回收器回收;
  9. obj1的强引用关系还在,所以从queue中并不会获取到;
  1. public static void main(String[] args) {
  2. // 创建引用队列
  3. ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
  4. // 创建obj对象
  5. Object obj = new Object();
  6. // 创建虚引用,虚引用引用了obj对象,并与queue进行关联
  7. PhantomReference<Object> phantomReference = new PhantomReference<Object>(obj, queue);
  8. // 创建obj1对象
  9. Object obj1 = new Object();
  10. PhantomReference<Object> phantomReference1 = new PhantomReference<Object>(obj1, queue);
  11. // 将obj置为NULL,强引用关系失效
  12. obj = null;
  13. // 垃圾回收
  14. System.gc();
  15. // 从引用队列获取对象
  16. Object o = queue.poll();
  17. if (null != o) {
  18. System.out.println(o.toString());
  19. }
  20. }

输出结果:

  1. java.lang.ref.PhantomReference@277c0f21

Reference实例的几种状态

Active:初始状态,创建一个Reference类型的实例之后处于Active状态,以上面虚引用为例,通过new创建一个PhantomReference虚引用对象之后,虚引用对象就处于Active状态。

Pending:当GC检测到对象的可达性发生变化时,会根据是否关联了引用队列来决定是否将状态更改为Pending或者Inactive,虚引用必须与引用队列结合使用,所以对于虚引用来说,如果它实际引用的对象需要被回收,垃圾回收器会将这个虚引用对象加入到一个Pending列表中,此时处于Pending状态。

同样以上面的的虚引用为例,因为obj的强引用关系失效,GC就会把引用它的虚引用对象放入到pending列表中。

Enqueued:表示引用对象被加入到了引用队列,Reference有一个后台线程去检测是否有处于Pending状态的引用对象,如果有会将引用对象加入到与其关联的引用队列中,此时由Pending转为Enqueued状态表示对象已加入到引用队列中。

Inactive:通过引用队列的poll方法可以从引用队列中获取引用对象,同时引用对象会从队列中移除,此时引用对象处于Inactive状态,之后会被GC回收。

DirectByteBuffer堆外内存回收

DirectByteBuffer的构造函数中,在申请内存之前,先调用了BitsreserveMemory方法回收内存,申请内存之后,调用Cleanercreate方法创建了一个Cleaner对象,并传入了当前对象(DirectByteBuffer)和一个Deallocator类型的对象:

  1. class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer {
  2. private final Cleaner cleaner;
  3. DirectByteBuffer(int cap) { // package-private
  4. super(-1, 0, cap, cap);
  5. boolean pa = VM.isDirectMemoryPageAligned();
  6. int ps = Bits.pageSize();
  7. long size = Math.max(1L, (long)cap + (pa ? ps : 0));
  8. // 清理内存
  9. Bits.reserveMemory(size, cap);
  10. long base = 0;
  11. try {
  12. // 分配内存
  13. base = unsafe.allocateMemory(size);
  14. } catch (OutOfMemoryError x) {
  15. Bits.unreserveMemory(size, cap);
  16. throw x;
  17. }
  18. unsafe.setMemory(base, size, (byte) 0);
  19. if (pa && (base % ps != 0)) {
  20. // Round up to page boundary
  21. address = base + ps - (base & (ps - 1));
  22. } else {
  23. address = base;
  24. }
  25. // 创建Cleader,传入了当前对象和Deallocator
  26. cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
  27. att = null;
  28. }
  29. }

Cleaner从名字上可以看出与清理有关,BitsreserveMemory方法底层也是通过Cleaner来进行清理,所以Cleaner是重点关注的类。

DeallocatorDirectByteBuffer的一个内部类,并且实现了Runnable接口,在run方法中可以看到对内存进行了释放,接下来就去看下在哪里触发Deallocator任务的执行:

  1. class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer {
  2. private static class Deallocator implements Runnable {
  3. // ...
  4. private Deallocator(long address, long size, int capacity) {
  5. assert (address != 0);
  6. this.address = address; // 设置内存地址
  7. this.size = size;
  8. this.capacity = capacity;
  9. }
  10. public void run() {
  11. if (address == 0) {
  12. // Paranoia
  13. return;
  14. }
  15. // 释放内存
  16. unsafe.freeMemory(address);
  17. address = 0;
  18. Bits.unreserveMemory(size, capacity);
  19. }
  20. }
  21. }

Cleaner

Cleaner继承了PhantomReferencePhantomReferenceReference的子类,所以Cleaner是一个虚引用对象

创建Cleaner

虚引用需要与引用队列结合使用,所以在Cleaner中可以看到有一个ReferenceQueue,它是一个静态的变量,所以创建的所有Cleaner对象都会共同使用这个引用队列

在创建Cleaner的create方法中,处理逻辑如下:

  1. 通过构造函数创建了一个Cleaner对象,构造函数中的referent参数为DirectByteBuffer,thunk参数为Deallocator对象,在构造函数中又调用了父类的构造函数完成实例化;
  2. 调用add方法将创建的Cleaner对象加入到链表中,添加到链表的时候使用的是头插法,新加入的节点放在链表的头部,first成员变量是一个静态变量,它指向链表的头结点,创建的Cleaner都会加入到这个链表中;

创建后的Cleaner对象处于Active状态。

  1. public class Cleaner extends PhantomReference<Object>{
  2. // ReferenceQueue队列
  3. private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
  4. // 静态变量,链表的头结点,创建的Cleaner都会加入到这个链表中
  5. static private Cleaner first = null;
  6. // thunk
  7. private final Runnable thunk;
  8. public static Cleaner create(Object ob, Runnable thunk) {
  9. if (thunk == null)
  10. return null;
  11. // 创建一个Cleaner并加入链表
  12. return add(new Cleaner(ob, thunk));
  13. }
  14. private Cleaner(Object referent, Runnable thunk) {
  15. super(referent, dummyQueue); // 调用父类构造函数,传入引用对象和引用队列
  16. this.thunk = thunk; // thunk指向传入的Deallocator
  17. }
  18. private static synchronized Cleaner add(Cleaner cl) {
  19. // 如果头结点不为空
  20. if (first != null) {
  21. // 将新加入的节点作为头结点
  22. cl.next = first;
  23. first.prev = cl;
  24. }
  25. first = cl;
  26. return cl;
  27. }
  28. }

Cleaner调用父类构造函数时,最终会进入到父类Reference中的构造函数中:

referent:指向实际的引用对象,上面创建的是DirectByteBuffer,所以这里指向的是DirectByteBuffer

queue:引用队列,指向Cleaner中的引用队列dummyQueue

  1. public class PhantomReference<T> extends Reference<T> {
  2. // ...
  3. public PhantomReference(T referent, ReferenceQueue<? super T> q) {
  4. super(referent, q); // 调用父类构造函数
  5. }
  6. }
  7. public abstract class Reference<T> {
  8. /* 引用对象 */
  9. private T referent;
  10. // 引用队列
  11. volatile ReferenceQueue<? super T> queue;
  12. Reference(T referent, ReferenceQueue<? super T> queue) {
  13. this.referent = referent;
  14. // 设置引用队列
  15. this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
  16. }
  17. }

启动ReferenceHandler线程

Reference中有一个静态方法,里面创建了一个ReferenceHandler并设置为守护线程,然后启动了该线程,并创建了JavaLangRefAccess对象设置到SharedSecrets中:

  1. public abstract class Reference<T> {
  2. static {
  3. ThreadGroup tg = Thread.currentThread().getThreadGroup();
  4. for (ThreadGroup tgn = tg;
  5. tgn != null;
  6. tg = tgn, tgn = tg.getParent());
  7. // 创建ReferenceHandler
  8. Thread handler = new ReferenceHandler(tg, "Reference Handler");
  9. // 设置优先级为最高
  10. handler.setPriority(Thread.MAX_PRIORITY);
  11. handler.setDaemon(true);
  12. handler.start();
  13. // 这里设置了JavaLangRefAccess
  14. SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
  15. @Override
  16. public boolean tryHandlePendingReference() {
  17. // 调用了tryHandlePending
  18. return tryHandlePending(false);
  19. }
  20. });
  21. }
  22. }

ReferenceHandlerReference的内部类,继承了Thread,在run方法中开启了一个循环,不断的执行tryHandlePending方法,处理Reference中pending列表:

  1. public abstract class Reference<T> {
  2. private static class ReferenceHandler extends Thread {
  3. // ...
  4. ReferenceHandler(ThreadGroup g, String name) {
  5. super(g, name);
  6. }
  7. public void run() {
  8. while (true) {
  9. // 处理pending列表
  10. tryHandlePending(true);
  11. }
  12. }
  13. }
  14. }

Cleaner会启动一个优先级最高的守护线程,不断调用tryHandlePending来检测是否有需要回收的引用对象(还未进行真正的回收),然后进行处理。

处理pending列表

垃圾回收器会将要回收的引用对象放在Referencepending变量中,从数据类型上可以看出pending只是一个Reference类型的对象,并不是一个list,如果有多个需要回收的对象,如何将它们全部放入pending对象中?

可以把pengding看做是一个链表的头结点,假如有引用对象被判定需要回收,如果pengding为空直接放入即可,如果不为空,将使用头插法将新的对象加入到链表中,也就是将新对象的discovered指向pending对象,然后将pending指向当前要回收的这个对象,这样就形成了一个链表,pending指向链表的头结点。

在pending链表中的引用对象处于pending状态。

接下来看tryHandlePending方法的处理逻辑:

  1. 如果pending不为空,表示有需要回收的对象,此时将pengding指向的对象放在临时变量r中,并判断是否是Cleaner类型,如果是将其强制转为Cleaner,记录在临时变量c中,接着更新pending的值为r的discovered,因为discovered中记录了下一个需要被回收的对象,pengding需要指向下一个需要被回收的对象;

    pending如果为NULL,会进入到else的处理逻辑,返回值为参数传入的waitForNotify的值。

  2. 判断Cleaner对象是否为空,如果不为空,调用Cleaner的clean方法进行清理

  3. 获取引用对象关联的引用队列,然后调用enqueue方法将引用对象加入到引用队列中

  4. 返回true;

  1. public abstract class Reference<T> {
  2. // 指向pending列表中的下一个节点
  3. transient private Reference<T> discovered;
  4. // 静态变量pending列表,可以看做是一个链表,pending指向链表的头结点
  5. private static Reference<Object> pending = null;
  6. static boolean tryHandlePending(boolean waitForNotify) {
  7. Reference<Object> r;
  8. Cleaner c;
  9. try {
  10. synchronized (lock) {
  11. // 如果pending不为空
  12. if (pending != null) {
  13. // 获取pending执行的对象
  14. r = pending;
  15. // 如果是Cleaner类型
  16. c = r instanceof Cleaner ? (Cleaner) r : null;
  17. // 将pending指向下一个节点
  18. pending = r.discovered;
  19. // 将discovered置为空
  20. r.discovered = null;
  21. } else {
  22. // 等待
  23. if (waitForNotify) {
  24. lock.wait();
  25. }
  26. return waitForNotify;
  27. }
  28. }
  29. } catch (OutOfMemoryError x) {
  30. Thread.yield();
  31. // retry
  32. return true;
  33. } catch (InterruptedException x) {
  34. // retry
  35. return true;
  36. }
  37. if (c != null) {
  38. // 调用clean方法进行清理
  39. c.clean();
  40. return true;
  41. }
  42. // 获取引用队列
  43. ReferenceQueue<? super Object> q = r.queue;
  44. // 如果队列不为空,将对象加入到引用队列中
  45. if (q != ReferenceQueue.NULL) q.enqueue(r);
  46. // 返回true
  47. return true;
  48. }
  49. }
释放内存

Cleaner的clean方法中,可以看到,调用了thunk的run方法,前面内容可知,thunk指向的是Deallocator对象,所以会执行Deallocator的run方法,Deallocator的run方法前面也已经看过,里面会对DirectByteBuffer的堆外内存进行释放

  1. public class Cleaner extends PhantomReference<Object> {
  2. public void clean() {
  3. if (!remove(this))
  4. return;
  5. try {
  6. // 调用run方法
  7. thunk.run();
  8. } catch (final Throwable x) {
  9. AccessController.doPrivileged(new PrivilegedAction<Void>() {
  10. public Void run() {
  11. if (System.err != null)
  12. new Error("Cleaner terminated abnormally", x)
  13. .printStackTrace();
  14. System.exit(1);
  15. return null;
  16. }});
  17. }
  18. }
  19. }

总结

Cleaner是一个虚引用,它实际引用的对象DirectByteBuffer如果被GC判定为需要回收,会将引用该对象的Cleaner加入到pending列表,ReferenceHandler线程会不断检测pending是否为空,如果不为空,就对其进行处理:

  1. 如果对象类型为Cleaner,就调用Cleaner的clean方法进行清理,Cleaner的clean方法又会调用Deallocator的run方法,里面调用了freeMemory方法对DirectByteBuffer分配的堆外内存进行释放;
  2. 将Cleaner对象加入到与其关联的引用队列中;

引用队列

ReferenceQueue名字听起来是一个队列,实际使用了一个链表,使用头插法将加入的节点串起来,ReferenceQueue中的head变量指向链表的头节点,每个节点是一个Reference类型的对象:

  1. public class ReferenceQueue<T> {
  2. // head为链表头节点
  3. private volatile Reference<? extends T> head = null;
  4. }

Reference中除了discovered变量之外,还有一个next变量,discovered指向的是处于pending状态时pending列表中的下一个元素,next变量指向的是处于Enqueued状态时,引用队列中的下一个元素:

  1. public abstract class Reference<T> {
  2. /* When active: 处于active状态时为NULL
  3. * pending: this
  4. * Enqueued: Enqueued状态时,指向引用队列中的下一个元素
  5. * Inactive: this
  6. */
  7. @SuppressWarnings("rawtypes")
  8. Reference next;
  9. /* When active: active状态时,指向GC维护的一个discovered链表中的下一个元素
  10. * pending: pending状态时,指向pending列表中的下一个元素
  11. * otherwise: 其他情况为NULL
  12. */
  13. transient private Reference<T> discovered; /* used by VM */
  14. }

enqueue入队

进入引用队列中的引用对象处于enqueue状态。

enqueue的处理逻辑如下:

  1. 判断要加入的对象关联的引用队列,对队列进行判断,如果队列为空或者队列等于ReferenceQueue中的空队列ENQUEUED,表示该对象之前已经加入过队列,不能重复操作,返回false,如果未加入过继续下一步;
  2. 将对象所关联的引用队列置为ENQUEUED,它是一个空队列,表示节点已经加入到队列中;
  3. 判断头节点是否为空,如果为空,表示链表还没有节点,将当前对象的next指向自己,如果头结点不为空,将当前对象的next指向头结点,然后更新头结点的值为当前对象(头插法插入链表);
  4. 增加队列的长度,也就是链表的长度;
  1. public class ReferenceQueue<T> {
  2. // 空队列
  3. static ReferenceQueue<Object> ENQUEUED = new Null<>();
  4. // 入队,将节点加入引用队列,队列实际上是一个链表
  5. boolean enqueue(Reference<? extends T> r) {
  6. synchronized (lock) {
  7. // 获取关联的引用队列
  8. ReferenceQueue<?> queue = r.queue;
  9. // 如果为空或者已经添加到过队列
  10. if ((queue == NULL) || (queue == ENQUEUED)) {
  11. return false;
  12. }
  13. assert queue == this;
  14. // 将引用队列置为一个空队列,表示该节点已经入队
  15. r.queue = ENQUEUED;
  16. // 如果头结点为空将下一个节点置为自己,否则将next置为链表的头结点,可以看出同样使用的是头插法将节点插入链表
  17. r.next = (head == null) ? r : head;
  18. // 更新头结点为当前节点
  19. head = r;
  20. // 增加长度
  21. queueLength++;
  22. if (r instanceof FinalReference) {
  23. sun.misc.VM.addFinalRefCount(1);
  24. }
  25. lock.notifyAll();
  26. return true;
  27. }
  28. }
  29. }
poll出队

在调用poll方法从引用队列中获取一个元素并出队的时候,首先对head头结点进行判空,如果为空表示引用队列中没有数据,返回NULL,否则调用reallyPoll从引用队列中获取元素。

出队的处理逻辑如下:

  1. 获取链表中的第一个节点也就是头结点,如果不为空进行下一步;

  2. 如果头节点的下一个节点是自己,表示链表只有一个节点,头结点出队之后链表为空,所以将头结点的值更新为NULL;

    如果头节点的下一个节点不是自己,表示链表中还有其他节点,更新head头节点的值为下一个节点,也就是next指向的对象;

  3. 将需要出队的节点的引用队列置为NULL,next节点置为自己,表示节点已从队列中删除;

  4. 引用队列的长度减一;

  5. 返回要出队的节点;

从出队的逻辑中可以看出,引用队列中的对象是后进先出的,poll出队之后的引用对象处于Inactive状态,表示可以被GC回收掉。

  1. public class ReferenceQueue<T> {
  2. /**
  3. * 从引用队列中获取一个节点,进行出队操作
  4. */
  5. public Reference<? extends T> poll() {
  6. // 如果头结点为空,表示没有数据
  7. if (head == null)
  8. return null;
  9. synchronized (lock) {
  10. return reallyPoll();
  11. }
  12. }
  13. @SuppressWarnings("unchecked")
  14. private Reference<? extends T> reallyPoll() { /* Must hold lock */
  15. // 获取头结点
  16. Reference<? extends T> r = head;
  17. if (r != null) {
  18. // 如果头结点的下一个节点是自己,表示链表只有一个节点,head置为null,否则head值为r的下一个节点,也就是next指向的对象
  19. head = (r.next == r) ?
  20. null :
  21. r.next;
  22. // 将引用队列置为NULL
  23. r.queue = NULL;
  24. // 下一个节点置为自己
  25. r.next = r;
  26. // 长度减一
  27. queueLength--;
  28. if (r instanceof FinalReference) {
  29. sun.misc.VM.addFinalRefCount(-1);
  30. }
  31. // 返回链表中的第一个节点
  32. return r;
  33. }
  34. return null;
  35. }
  36. }

reserveMemory内存清理

最开始在DirectByteBuffer的构造函数中看到申请内存之前会调用Bits的reserveMemory方法,如果没有足够的内存,它会从SharedSecrets获取JavaLangRefAccess对象进行一些处理,由前面的内容可知,Reference中的静态方法启动ReferenceHandler之后,创建了JavaLangRefAccess并设置到SharedSecrets中,所以这里调用JavaLangRefAccesstryHandlePendingReference实际上依旧调用的是Reference中的tryHandlePending方法。

在调用Reference中的tryHandlePending方法处理需要回收的对象之后,调用tryReserveMemory方法判断是否有足够的内存,如果内存依旧不够,会调用` System.gc()触发垃圾回收,然后开启一个循环,处理逻辑如下:

  1. 判断内存是否充足,如果充足直接返回;

  2. 判断睡眠次数是否小于限定的最大值,如果小于继续下一步,否则终止循环;

  3. 调用tryHandlePendingReference处理penging列表中的引用对象,前面在处理pending列表的逻辑中可以知道,如果pending列表不为空,会返回true,tryHandlePendingReference也会返回true,此时意味着清理了一部分对象,所以重新进入到第1步进行检查;

    如果pending列表为空,会返回参数中传入的waitForNotify的值,从JavaLangRefAccess的tryHandlePendingReference中可以看出这里传入的是false,所以会进行如下处理:

    • 通过 Thread.sleep(sleepTime)让当前线程睡眠一段时间,这样可以避免reserveMemory方法一直在占用资源;
    • 对睡眠次数加1;
  4. 如果以上步骤处理之后还没有足够的空间会抛出抛出OutOfMemoryError异常;

reserveMemory方法的作用是保证在申请内存之前有足够的内存,如果没有足够的内存会进行清理,达到指定清理次数之后依旧没有足够的内存空间,将抛出OutOfMemoryError异常。

  1. class Bits {
  2. static void reserveMemory(long size, int cap) {
  3. if (!memoryLimitSet && VM.isBooted()) {
  4. maxMemory = VM.maxDirectMemory();
  5. memoryLimitSet = true;
  6. }
  7. // 是否有足够内存
  8. if (tryReserveMemory(size, cap)) {
  9. return;
  10. }
  11. // 获取JavaLangRefAccess
  12. final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
  13. // 调用tryHandlePendingReference
  14. while (jlra.tryHandlePendingReference()) {
  15. // 判断是否有足够的内存
  16. if (tryReserveMemory(size, cap)) {
  17. return;
  18. }
  19. }
  20. // 调用gc进行垃圾回收
  21. System.gc();
  22. boolean interrupted = false;
  23. try {
  24. long sleepTime = 1;
  25. int sleeps = 0;
  26. // 开启循环
  27. while (true) {
  28. // 是否有足够内存
  29. if (tryReserveMemory(size, cap)) {
  30. return;
  31. }
  32. // 如果次数小于最大限定次数,终止
  33. if (sleeps >= MAX_SLEEPS) {
  34. break;
  35. }
  36. // 再次处理penging列表中的对象
  37. if (!jlra.tryHandlePendingReference()) {
  38. try {
  39. // 睡眠一段时间
  40. Thread.sleep(sleepTime);
  41. sleepTime <<= 1;
  42. sleeps++; // 睡眠次数增加1
  43. } catch (InterruptedException e) {
  44. interrupted = true;
  45. }
  46. }
  47. }
  48. // 抛出OutOfMemoryError异常
  49. throw new OutOfMemoryError("Direct buffer memory");
  50. } finally {
  51. if (interrupted) {
  52. // don't swallow interrupts
  53. Thread.currentThread().interrupt();
  54. }
  55. }
  56. }
  57. }
  58. public abstract class Reference<T> {
  59. static {
  60. // ...
  61. // 这里设置了JavaLangRefAccess
  62. SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
  63. @Override
  64. public boolean tryHandlePendingReference() {
  65. // 调用tryHandlePending,这里waitForNotify参数传入的是false
  66. return tryHandlePending(false);
  67. }
  68. });
  69. }
  70. }

参考

Reference源码解析

一文读懂java中的Reference和引用类型

Java 源码剖析——彻底搞懂 Reference 和 ReferenceQueue

【Java】 DirectByteBuffer堆外内存回收的更多相关文章

  1. Java进程堆外内存(off heap)大小

    一.使用ByteBuffer.allocateDirect分配的off heap内存大小 本机进程 在Jvisualvm中安装 Mbeans插件.然后查看java.nio/BufferPool/dir ...

  2. JAVA使用堆外内存导致swap飙高

    https://github.com/nereuschen/blog/issues/29 堆内内存分析一般用Memory Analyzer Tool http://tivan.iteye.com/bl ...

  3. Java NIO 堆外内存与零拷贝

    一.直接缓存 这个例子的区别就是 ByteBuffer.allocateDirect(512); 进入allocateDirect方法 进入DirectByteBuffer构造函数 Native方法: ...

  4. Netty之Java堆外内存扫盲贴

    Java的堆外内存本来是高贵而神秘的东西,只在一些缓存方案的收费企业版里出现.但自从用了Netty,就变成了天天打交道的事情,毕竟堆外内存能减少IO时的内存复制,不需要堆内存Buffer拷贝一份到直接 ...

  5. Java堆外内存之三:堆外内存回收方法

    一.JVM内存的分配及垃圾回收 对于JVM的内存规则,应该是老生常谈的东西了,这里我就简单的说下: 新生代:一般来说新创建的对象都分配在这里. 年老代:经过几次垃圾回收,新生代的对象就会放在年老代里面 ...

  6. Java堆外内存之六:堆外内存溢出问题排查

    一.堆外内存组成 通常JVM的参数我们会配置 -Xms 堆初始内存 -Xmx 堆最大内存 -XX:+UseG1GC/CMS 垃圾回收器 -XX:+DisableExplicitGC 禁止显示GC -X ...

  7. cassandra 堆外内存管理

    为什么需要堆外内存呢 单有一些大内存对象的时候,JVM进行垃圾回收时需要收集所有的这些对象的内存也.增加了GC压力.因此需要使用堆外内存. java 分配堆外内存 org.apache.cassand ...

  8. JVM源码分析之堆外内存完全解读

    JVM源码分析之堆外内存完全解读   寒泉子 2016-01-15 17:26:16 浏览6837 评论0 阿里技术协会 摘要: 概述 广义的堆外内存 说到堆外内存,那大家肯定想到堆内内存,这也是我们 ...

  9. Netty堆外内存泄漏排查,这一篇全讲清楚了

    上篇文章介绍了Netty内存模型原理,由于Netty在使用不当会导致堆外内存泄漏,网上关于这方面的资料比较少,所以写下这篇文章,专门介绍排查Netty堆外内存相关的知识点,诊断工具,以及排查思路提供参 ...

随机推荐

  1. (转)git使用收集

    由于最近项目开始弃SVN用git,特意整理下git命令.原文链接为http://www.jb51.net/article/55442.htm git branch 查看本地所有分支git status ...

  2. FreeSql 将 Saas 租户方案精简到极致[.NET ORM SAAS]

    什么是多租户 维基百科:"软件多租户是指一种软件架构,在这种软件架构中,软件的一个实例运行在服务器上并且为多个租户服务".一个租户是一组共享该软件实例特定权限的用户.有了多租户架构 ...

  3. CSS(上)

    css sprite是什么,有什么优缺点 概念:将多个小图片拼接到⼀个图⽚中.通过 background-position 和元素尺寸调节需要显示的背景图案. 优点: 减少 HTTP 请求数,极⼤地提 ...

  4. Ubuntu添加非root用户到Docker用户组

    前言 首先平常公司的Linux生产环境为了防止误操作导致灾难性问题,一般都不会给我们开发开放root管理员的账号权限.所以平常在Ubuntu的普通用户登录的时候,要操作Dcoker一般都需要带上sud ...

  5. Luogu1063 能量项链 (区间DP)

    惊恐地发现自己连区间DP都会错2333 #include <iostream> #include <cstdio> #include <cstring> #incl ...

  6. 五,手写SpringMVC框架,过滤器的使用

    8. 过滤器 8.1 编写字符过滤器 CharacterEncodingFilter 复制项目mymvc4,新建项目mymvc5 package com.hy.filter; import java. ...

  7. 第四十三篇:Git知识(基本理论)

    好家伙,最近准备考试,有点忙 首先从版本控制开始 1.版本控制(版本迭代,新的版本) 如果一个项目由多个人去开发,那么总会需要去管理版本 你更一点,我更一点,一冲突,这个项目就炸了 所以需要版本控制. ...

  8. RTMP播放器开发填坑之道

    好多开发者提到,在目前开源播放器如此泛滥的情况下,为什么还需要做自研框架的RTMP播放器,自研和开源播放器,到底好在哪些方面?以下大概聊聊我们的一点经验,感兴趣的,可以关注 github: 1. 低延 ...

  9. KingbaseES R6 集群“双主”故障解决案例

    实际工作中,可能会碰到集群脑裂的情况,在脑裂时,会出现双 primary情况.这时,需要用户介入,人工判断哪个节点的数据最新,减少数据丢失. 一.测试环境信息 操作系统: [kingbase@node ...

  10. 【ajax】发送请求 —— 结合【express】框架 { }

    1.先用 express 框架搭建一个简单的服务器 (1)在文件夹上点击右键,点击"在集成终端中打开" (2)使用"npm i express"命令安装[exp ...