jvm 性能调优

6、直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError 异常出现,所以我们放到这里一起讲解。在JDK 1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用Native 函数库直接分配堆外内存,然后通过一个存储在Java 堆里面的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java 堆和Native 堆中来回复制数据。显然,本机直接内存的分配不会受到Java 堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括RAM 及SWAP 区或者分页文件)的大小及处理器寻址空间的限制。服务器管理员配置虚拟机参数时,一般会根据实际内存设置-Xmx等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。逻辑内存模型我们已经看到了,那当我们建立一个对象的时候是怎么进行访问的呢?在Java 语言中,对象访问是如何进行的?对象访问在Java 语言中无处不在,是最普通的程序行为,但即使是最简单的访问,也会却涉及Java 栈、Java 堆、方法区这三个最重要内存区域之间的关联关系,如下面的这句代码:Object obj = newObject();假设这句代码出现在方法体中,那“Object obj”这部分的语义将会反映到Java 栈的本地变量表中,作为一个reference 类型数据出现。而“new Object()”这部分的语义将会反映到Java 堆中,形成一块存储了Object 类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在Java 堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。由于reference 类型在Java 虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java 堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄和直接
指针。如果使用句柄访问方式,Java 堆中将会划分出一块内存来作为句柄池,reference
中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的
具体地址信息,如下图所示。
 如果使用直接指针访问方式,
Java 堆对象的布局中就必须考虑如何放置访问类型
数据的相关信息,reference 中直接存储的就是对象地址,如下图所示
 这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是
reference 中存
储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只
会改变句柄中的实例数据指针,而reference 本身不需要被修改。
使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开
销,由于对象的访问在Java 中非常频繁,因此这类开销积少成多后也是一项非常可观的
执行成本。就本书讨论的主要虚拟机Sun HotSpot 而言,它是使用第二种方式进行对象访问的,但从整个软件开发的范围来看,各种语言和框架使用句柄来访问的情况也十分常见。
下面我们来看几个示例
1、Java 堆溢出
下面的程中我们限制Java 堆的大小为20MB,不可扩展(将堆的最小值-Xms 参
数与最大值-Xmx 参数设置为一样即可避免堆自动扩展),通过参数-XX:+HeapDump
OnOutOfMemoryError 可以让虚拟机在出现内存溢出异常时Dump 出当前的内存堆转储
快照以便事后进行分析。
参数设置如下
packagecom.yhj.jvm.memory.heap;
importjava.util.ArrayList;
import java.util.List;
/**
 * @Described:堆溢出测试
 * @VMargs:-verbose:gc -Xms20M -Xmx20M -XX:+PrintGCDetails
 * @author YHJcreate at 2011-11-12 下午07:52:22
 * @FileNmaecom.yhj.jvm.memory.heap.HeapOutOfMemory.java
 */
public classHeapOutOfMemory {
   /**
    * @param args
    * @Author YHJ create at 2011-11-12 下午07:52:18
    */
   public static void main(String[] args) {
      List<TestCase> cases = new ArrayList<TestCase>();
      while(true){
          cases.add(new TestCase());
      }
    }
}
/**
 * @Described:测试用例
 * @author YHJcreate at 2011-11-12 下午07:55:50
 * @FileNmaecom.yhj.jvm.memory.heap.HeapOutOfMemory.java
 */
class TestCase{
   
}
Java 堆内存的OutOfMemoryError异常是实际应用中最常见的内存溢出异常情况。出现Java 堆内
存溢出时,异常堆栈信息“java.lang.OutOfMemoryError”会跟着进一步提示“Java heap
space”。
要解决这个区域的异常,一般的手段是首先通过内存映像分析工具(如Eclipse
Memory Analyzer)对dump 出来的堆转储快照进行分析,重点是确认内存中的对象是
否是必要的,也就是要先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢
出(Memory Overflow)。图2-5 显示了使用Eclipse Memory Analyzer 打开的堆转储快
照文件。
如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots 的引用链。于是就
能找到泄漏对象是通过怎样的路径与GC Roots 相关联并导致垃圾收集器无法自动回收
它们的。掌握了泄漏对象的类型信息,以及GC Roots 引用链的信息,就可以比较准确
地定位出泄漏代码的位置。
如果不存在泄漏,换句话说就是内存中的对象确实都还必须存活着,那就应当检查
虚拟机的堆参数(-Xmx 与-Xms),与机器物理内存对比看是否还可以调大,从代码上
检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期
的内存消耗。
以上是处理Java 堆内存问题的简略思路,处理这些问题所需要的知识、工具与经验
在后面的几次分享中我会做一些额外的分析。
2、java栈溢出
packagecom.yhj.jvm.memory.stack;
/**
 * @Described:栈层级不足探究
 * @VMargs:-Xss128k
 * @author YHJcreate at 2011-11-12 下午08:19:28
 * @FileNmaecom.yhj.jvm.memory.stack.StackOverFlow.java
 */
public classStackOverFlow {
   
   
   private int i ;
   
   public void plus() {
      i++;
      plus();
    }
   /**
    * @param args
    * @Author YHJ create at 2011-11-12 下午08:19:21
    */
   public static void main(String[] args) {
      StackOverFlow stackOverFlow = new StackOverFlow();
      try {
          stackOverFlow.plus();
      } catch (Exception e) {
          System.out.println("Exception:stack length:"+stackOverFlow.i);
          e.printStackTrace();
      } catch (Error e) {
          System.out.println("Error:stack length:"+stackOverFlow.i);
          e.printStackTrace();
      }
    }
}
3、常量池溢出(常量池都有哪些信息,我们在后续的JVM类文件结构中详细描述)
packagecom.yhj.jvm.memory.constant;
importjava.util.ArrayList;
importjava.util.List;
/**
 * @Described:常量池内存溢出探究
 * @VM args :-XX:PermSize=10M -XX:MaxPermSize=10M
 * @author YHJcreate at 2011-10-30 下午04:28:30
 * @FileNmaecom.yhj.jvm.memory.constant.ConstantOutOfMemory.java
 */
public classConstantOutOfMemory {
   /**
    * @param args
    * @throws Exception
    * @Author YHJ create at 2011-10-30 下午04:28:25
    */
   public static void main(String[] args) throws Exception {
      try {
          List<String> strings = new ArrayList<String>();
          int i = 0;
          while(true){
             strings.add(String.valueOf(i++).intern());
          }
      } catch (Exception e) {
          e.printStackTrace();
          throw e;
      }
    }
}
4、方法去溢出
packagecom.yhj.jvm.memory.methodArea;
importjava.lang.reflect.Method;
importnet.sf.cglib.proxy.Enhancer;
importnet.sf.cglib.proxy.MethodInterceptor;
importnet.sf.cglib.proxy.MethodProxy;
/**
 * @Described:方法区溢出测试
 * 使用技术 CBlib
 * @VM args :-XX:PermSize=10M -XX:MaxPermSize=10M
 * @author YHJcreate at 2011-11-12 下午08:47:55
 * @FileNmaecom.yhj.jvm.memory.methodArea.MethodAreaOutOfMemory.java
 */
public classMethodAreaOutOfMemory {
   /**
    * @param args
    * @Author YHJ create at 2011-11-12 下午08:47:51
    */
   public static void main(String[] args) {
      while(true){
          Enhancer enhancer = new Enhancer();
          enhancer.setSuperclass(TestCase.class);
          enhancer.setUseCache(false);
          enhancer.setCallback(new MethodInterceptor() {
             @Override
             public Object intercept(Object arg0, Method arg1, Object[] arg2,
                    MethodProxy arg3) throws Throwable {
                 return arg3.invokeSuper(arg0, arg2);
             }
          });
          enhancer.create();
      }
    }
}
/**
 * @Described:测试用例
 * @author YHJcreate at 2011-11-12 下午08:53:09
 * @FileNmaecom.yhj.jvm.memory.methodArea.MethodAreaOutOfMemory.java
 */
class TestCase{
   
}
5、直接内存溢出
packagecom.yhj.jvm.memory.directoryMemory;
importjava.lang.reflect.Field;
importsun.misc.Unsafe;
/**
 * @Described:直接内存溢出测试
 * @VM args:-Xmx20M -XX:MaxDirectMemorySize=10M
 * @author YHJcreate at 2011-11-12 下午09:06:10
 * @FileNmaecom.yhj.jvm.memory.directoryMemory.DirectoryMemoryOutOfmemory.java
 */
public classDirectoryMemoryOutOfmemory {
   private static final int ONE_MB = 1024*1024;
   private static int count = 1;
   /**
    * @param args
    * @Author YHJ create at 2011-11-12 下午09:05:54
    */
   public static void main(String[] args) {
      try {
          Field field = Unsafe.class.getDeclaredField("theUnsafe");
          field.setAccessible(true);
          Unsafe unsafe = (Unsafe) field.get(null);
          while (true) {
             unsafe.allocateMemory(ONE_MB);
             count++;
          }
      } catch (Exception e) {
          System.out.println("Exception:instance created "+count);
          e.printStackTrace();
      } catch (Error e) {
          System.out.println("Error:instance created "+count);
          e.printStackTrace();
      }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值