怎么看网站有没有做竞价,郑州58同城招聘网最新招聘,琼海网站建设公司,腾讯做网站Java与C之间有一堵由内存动态分配和垃圾收集技术所围成的高墙#xff0c;墙外面的人想进去#xff0c;墙里面的人却想出来。 概述#xff1a; 对于从事C、C程序开发的开发人员来说#xff0c;在内存管理领域#xff0c;他们即是拥有最高权力的皇帝又是执行最基础工作的劳动…Java与C之间有一堵由内存动态分配和垃圾收集技术所围成的高墙墙外面的人想进去墙里面的人却想出来。 概述 对于从事C、C程序开发的开发人员来说在内存管理领域他们即是拥有最高权力的皇帝又是执行最基础工作的劳动人民——拥有每一个对象的“所有权”又担负着每一个对象生命开始到终结的维护责任。 对于Java程序员来说不需要在为每一个new操作去写配对的delete/free不容易出现内容泄漏和内存溢出错误看起来由JVM管理内存一切都很美好。不过也正是因为Java程序员把内存控制的权力交给了JVM一旦出现泄漏和溢出如果不了解JVM是怎样使用内存的那排查错误将会是一件非常困难的事情。 VM运行时数据区域 JVM执行Java程序的过程中会使用到各种数据区域这些区域有各自的用途、创建和销毁时间。根据《Java虚拟机规范第二版》下文称VM Spec的规定JVM包括下列几个运行时数据区域 1.程序计数器Program Counter Register 每一个Java线程都有一个程序计数器来用于保存程序执行到当前方法的哪一个指令对于非Native方法这个区域记录的是正在执行的VM原语的地址如果正在执行的是Natvie方法这个区域则为空undefined。此内存区域是唯一一个在VM Spec中没有规定任何OutOfMemoryError情况的区域。 2.Java虚拟机栈Java Virtual Machine Stacks 与程序计数器一样VM栈的生命周期也是与线程相同。VM栈描述的是Java方法调用的内存模型每个方法被执行的时候都会同时创建一个帧Frame用于存储本地变量表、操作栈、动态链接、方法出入口等信息。每一个方法的调用至完成就意味着一个帧在VM栈中的入栈至出栈的过程。在后文中我们将着重讨论VM栈中本地变量表部分。 经常有人把Java内存简单的区分为堆内存Heap和栈内存Stack实际中的区域远比这种观点复杂这样划分只是说明与变量定义密切相关的内存区域是这两块。其中所指的“堆”后面会专门描述而所指的“栈”就是VM栈中各个帧的本地变量表部分。本地变量表存放了编译期可知的各种标量类型boolean、byte、char、short、int、float、long、double、对象引用不是对象本身仅仅是一个引用指针、方法返回地址等。其中long和double会占用2个本地变量空间32bit其余占用1个。本地变量表在进入方法时进行分配当进入一个方法时这个方法需要在帧中分配多大的本地变量是一件完全确定的事情在方法运行期间不改变本地变量表的大小。 在VM Spec中对这个区域规定了2中异常状况如果线程请求的栈深度大于虚拟机所允许的深度将抛出StackOverflowError异常如果VM栈可以动态扩展VM Spec中允许固定长度的VM栈当扩展时无法申请到足够内存则抛出OutOfMemoryError异常。 3.本地方法栈Native Method Stacks 本地方法栈与VM栈所发挥作用是类似的只不过VM栈为虚拟机运行VM原语服务而本地方法栈是为虚拟机使用到的Native方法服务。它的实现的语言、方式与结构并没有强制规定甚至有的虚拟机譬如Sun Hotspot虚拟机直接就把本地方法栈和VM栈合二为一。和VM栈一样这个区域也会抛出StackOverflowError和OutOfMemoryError异常。 4.Java堆Java Heap 对于绝大多数应用来说Java堆是虚拟机管理最大的一块内存。Java堆是被所有线程共享的在虚拟机启动时创建。Java堆的唯一目的就是存放对象实例绝大部分的对象实例都在这里分配。这一点在VM Spec中的描述是所有的实例以及数组都在堆上分配原文The heap is the runtime data area from which memory for all class instances and arrays is allocated但是在逃逸分析和标量替换优化技术出现后VM Spec的描述就显得并不那么准确了。 Java堆内还有更细致的划分新生代、老年代再细致一点的eden、from survivor、to survivor甚至更细粒度的本地线程分配缓冲TLAB等无论对Java堆如何划分目的都是为了更好的回收内存或者更快的分配内存在本章中我们仅仅针对内存区域的作用进行讨论Java堆中的上述各个区域的细节可参见本文第二章《JVM内存管理深入垃圾收集器与内存分配策略》。 根据VM Spec的要求Java堆可以处于物理上不连续的内存空间它逻辑上是连续的即可就像我们的磁盘空间一样。实现时可以选择实现成固定大小的也可以是可扩展的不过当前所有商业的虚拟机都是按照可扩展来实现的通过-Xmx和-Xms控制。如果在堆中无法分配内存并且堆也无法再扩展时将会抛出OutOfMemoryError异常。 5.方法区Method Area 叫“方法区”可能认识它的人还不太多如果叫永久代Permanent Generation它的粉丝也许就多了。它还有个别名叫做Non-Heap非堆但是VM Spec上则描述方法区为堆的一个逻辑部分原文the method area is logically part of the heap这个名字的问题还真容易令人产生误解我们在这里就不纠结了。 方法区中存放了每个Class的结构信息包括常量池、字段描述、方法描述等等。VM Space描述中对这个区域的限制非常宽松除了和Java堆一样不需要连续的内存也可以选择固定大小或者可扩展外甚至可以选择不实现垃圾收集。相对来说垃圾收集行为在这个区域是相对比较少发生的但并不是某些描述那样永久代不会发生GC至少对当前主流的商业JVM实现来说是如此这里的GC主要是对常量池的回收和对类的卸载虽然回收的“成绩”一般也比较差强人意尤其是类卸载条件相当苛刻。 6.运行时常量池Runtime Constant Pool Class文件中除了有类的版本、字段、方法、接口等描述等信息外还有一项信息是常量表(constant_pool table)用于存放编译期已可知的常量这部分内容将在类加载后进入方法区永久代存放。但是Java语言并不要求常量一定只有编译期预置入Class的常量表的内容才能进入方法区常量池运行期间也可将新内容放入常量池最典型的String.intern()方法。 运行时常量池是方法区的一部分自然受到方法区内存的限制当常量池无法在申请到内存时会抛出OutOfMemoryError异常。 7.本机直接内存Direct Memory 直接内存并不是虚拟机运行时数据区的一部分它根本就是本机内存而不是VM直接管理的区域。但是这部分内存也会导致OutOfMemoryError异常出现因此我们放到这里一起描述。 在JDK1.4中新加入了NIO类引入一种基于渠道与缓冲区的I/O方式它可以通过本机Native函数库直接分配本机内存然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能因为避免了在Java对和本机堆中来回复制数据。 显然本机直接内存的分配不会受到Java堆大小的限制但是即然是内存那肯定还是要受到本机物理内存包括SWAP区或者Windows虚拟内存的限制的一般服务器管理员配置JVM参数时会根据实际内存设置-Xmx等参数信息但经常忽略掉直接内存使得各个内存区域总和大于物理内存限制包括物理的和操作系统级的限制而导致动态扩展时出现OutOfMemoryError异常。 实战OutOfMemoryError 上述区域中除了程序计数器其他在VM Spec中都描述了产生OutOfMemoryError下称OOM的情形那我们就实战模拟一下通过几段简单的代码令对应的区域产生OOM异常以便加深认识同时初步介绍一些与内存相关的虚拟机参数。下文的代码都是基于Sun Hotspot虚拟机1.6版的实现对于不同公司的不同版本的虚拟机参数与程序运行结果可能结果会有所差别。 Java堆 Java堆存放的是对象实例因此只要不断建立对象并且保证GC Roots到对象之间有可达路径即可产生OOM异常。测试中限制Java堆大小为20M不可扩展通过参数-XX:HeapDumpOnOutOfMemoryError让虚拟机在出现OOM异常的时候Dump出内存映像以便分析。关于Dump映像文件分析方面的内容可参见本文第三章《JVM内存管理深入JVM内存异常分析与调优》。 清单1Java堆OOM测试 /** * VM Args-Xms20m -Xmx20m -XX:HeapDumpOnOutOfMemoryError * author zzm */ public class HeapOOM { static class OOMObject { } public static void main(String[] args) { ListOOMObject list new ArrayListOOMObject(); while (true) { list.add(new OOMObject()); } } } 运行结果 java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid3404.hprof ... Heap dump file created [22045981 bytes in 0.663 secs] VM栈和本地方法栈 Hotspot虚拟机并不区分VM栈和本地方法栈因此-Xoss参数实际上是无效的栈容量只由-Xss参数设定。关于VM栈和本地方法栈在VM Spec描述了两种异常StackOverflowError与OutOfMemoryError当栈空间无法继续分配分配时到底是内存太小还是栈太大其实某种意义上是对同一件事情的两种描述而已在笔者的实验中对于单线程应用尝试下面3种方法均无法让虚拟机产生OOM全部尝试结果都是获得SOF异常。 1.使用-Xss参数削减栈内存容量。结果抛出SOF异常时的堆栈深度相应缩小。 2.定义大量的本地变量增大此方法对应帧的长度。结果抛出SOF异常时的堆栈深度相应缩小。 3.创建几个定义很多本地变量的复杂对象打开逃逸分析和标量替换选项使得JIT编译器允许对象拆分后在栈中分配。结果实际效果同第二点。 清单2VM栈和本地方法栈OOM测试仅作为第1点测试程序 /** * VM Args-Xss128k * author zzm */ public class JavaVMStackSOF { private int stackLength 1; public void stackLeak() { stackLength; stackLeak(); } public static void main(String[] args) throws Throwable { JavaVMStackSOF oom new JavaVMStackSOF(); try { oom.stackLeak(); } catch (Throwable e) { System.out.println(stack length: oom.stackLength); throw e; } } } 运行结果 stack length:2402 Exception in thread main java.lang.StackOverflowError at org.fenixsoft.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:20) at org.fenixsoft.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:21) at org.fenixsoft.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:21) 如果在多线程环境下不断建立线程倒是可以产生OOM异常但是基本上这个异常和VM栈空间够不够关系没有直接关系甚至是给每个线程的VM栈分配的内存越多反而越容易产生这个OOM异常。 原因其实很好理解操作系统分配给每个进程的内存是有限制的譬如32位Windows限制为2GJava堆和方法区的大小JVM有参数可以限制最大值那剩余的内存为2G操作系统限制-Xmx最大堆-MaxPermSize最大方法区程序计数器消耗内存很小可以忽略掉那虚拟机进程本身耗费的内存不计算的话剩下的内存就供每一个线程的VM栈和本地方法栈瓜分了那自然每个线程中VM栈分配内存越多就越容易把剩下的内存耗尽。 清单3创建线程导致OOM异常 /** * VM Args-Xss2M 这时候不妨设大些 * author zzm */ public class JavaVMStackOOM { private void dontStop() { while (true) { } } public void stackLeakByThread() { while (true) { Thread thread new Thread(new Runnable() { Override public void run() { dontStop(); } }); thread.start(); } } public static void main(String[] args) throws Throwable { JavaVMStackOOM oom new JavaVMStackOOM(); oom.stackLeakByThread(); } } 特别提示一下如果读者要运行上面这段代码记得要存盘当前工作上述代码执行时有很大令操作系统卡死的风险。 运行结果 Exception in thread main java.lang.OutOfMemoryError: unable to create new native thread 运行时常量池 要在常量池里添加内容最简单的就是使用String.intern()这个Native方法。由于常量池分配在方法区内我们只需要通过-XX:PermSize和-XX:MaxPermSize限制方法区大小即可限制常量池容量。实现代码如下 清单4运行时常量池导致的OOM异常 /** * VM Args-XX:PermSize10M -XX:MaxPermSize10M * author zzm */ public class RuntimeConstantPoolOOM { public static void main(String[] args) { // 使用List保持着常量池引用压制Full GC回收常量池行为 ListString list new ArrayListString(); // 10M的PermSize在integer范围内足够产生OOM了 int i 0; while (true) { list.add(String.valueOf(i).intern()); } } } 运行结果 Exception in thread main java.lang.OutOfMemoryError: PermGen space at java.lang.String.intern(Native Method) at org.fenixsoft.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:18) 方法区 上文讲过方法区用于存放Class相关信息所以这个区域的测试我们借助CGLib直接操作字节码动态生成大量的Class值得注意的是这里我们这个例子中模拟的场景其实经常会在实际应用中出现当前很多主流框架如Spring、Hibernate对类进行增强时都会使用到CGLib这类字节码技术当增强的类越多就需要越大的方法区用于保证动态生成的Class可以加载入内存。 清单5借助CGLib使得方法区出现OOM异常 /** * VM Args -XX:PermSize10M -XX:MaxPermSize10M * author zzm */ public class JavaMethodAreaOOM { public static void main(String[] args) { while (true) { Enhancer enhancer new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invokeSuper(obj, args); } }); enhancer.create(); } } static class OOMObject { } } 运行结果 Caused by: java.lang.OutOfMemoryError: PermGen space at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632) at java.lang.ClassLoader.defineClass(ClassLoader.java:616) ... 8 more 本机直接内存 DirectMemory容量可通过-XX:MaxDirectMemorySize指定不指定的话默认与Java堆-Xmx指定一样下文代码越过了DirectByteBuffer直接通过反射获取Unsafe实例进行内存分配Unsafe类的getUnsafe()方法限制了只有引导类加载器才会返回实例也就是基本上只有rt.jar里面的类的才能使用因为DirectByteBuffer也会抛OOM异常但抛出异常时实际上并没有真正向操作系统申请分配内存而是通过计算得知无法分配既会抛出真正申请分配的方法是unsafe.allocateMemory()。 /** * VM Args-Xmx20M -XX:MaxDirectMemorySize10M * author zzm */ public class DirectMemoryOOM { private static final int _1MB 1024 * 1024; public static void main(String[] args) throws Exception { Field unsafeField Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe (Unsafe) unsafeField.get(null); while (true) { unsafe.allocateMemory(_1MB); } } } 运行结果 Exception in thread main java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at org.fenixsoft.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:20) 转载于:https://www.cnblogs.com/yl2755/archive/2012/03/14/2395418.html