Java虚拟机运行时数据区运行时数据区划分运行时数据区包括方法区(MethodArea)虚拟机栈(VMStack)本地方法栈(NativeMethodStack)堆(Heap)程序计算器(ProgramCounterRegister)直接内存(DirectMemory)1.方法区(MethodArea)方法区的概念也叫静态区,存放的是被加载的类wait的基本信息、常量、静态变量等。它是各种线程共享的区域。比如我们写Java代码的时候,每一层都可以访问同一个类的静态变量对象。由于使用了反射机制,虚拟机很难推测出哪些类信息不再被使用,因此很难回收这块区域。静态块和非静态块有什么区别?类(Class)和对象(Object)的区别和联系?为什么不能在静态块中使用this和super关键字?为什么java的静态方法可以直接用类名调用?方法区线程间共享区方法区的异常主要是对常量池的回收。值得注意的是,JDK1.7已经将常量池转入堆中。同样,当方法区不能满足内存分配要求时,也会抛出OutOfMemoryError。创建方法区内存溢出。注意JDK1.6及更早版本会导致方法区溢出。原因将在后面解释。执行前可以设置虚拟机的参数-XXpermSize和-XX:MaxPermSize来限制方法区的大小。代码清单如下:publicstaticvoidprintOOM(){Listlist=newArrayList();inti=0;while(true){list.add(String.valueOf(i).intern());}}输出异常结果:Exceptioninthread"main"java.lang.OutOfMemoryError:Javaheapspaceatjava.util.Arrays.copyOf(Arrays.java:2245)atjava.util.Arrays.copyOf(Arrays.java:2219)atjava.util.ArrayList.grow(ArrayList.java:242)atjava.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)atjava.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)atjava.util.ArrayList.add(ArrayList.java:440)atcom.vprisk。knowledgeshare.MethodAreExample.main(MethodAreExample.java:15)atsun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethod)atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)atsun.reflect.DelegatingMethodAccessorImpl.invoke(Accessor:DelegatingMethod3)jaatjava.lang.reflect.Method.invoke(Method.java:606)atcom.intellij.rt.execution.application.AppMain.main(AppMain.java:147)关于Stringintern()intern()的作用:如果常量池中不存在当前字符串,则将其放入常量池中。OOM超出了方法区。解释为什么上面的代码必须在JDK1.6之前运行。前面我们提到JDK1.7之后,我们把常量池放到了堆空间,这就导致了intern()函数的不同作用。代码清单如下:publicstaticvoidtestInternMethod(){Stringstr1=newStringBuilder("hua").append("chao").toString();System.out.println(str1.intern()==str1);Stringstr2=newStringBuilder("ja").append("va").toString();System.out.println(str2.intern()==str2);}在jdk6场景中,输出结果:false,false在jdk7场景中,输出结果:true,false为什么?原因是在JDK1.6中,intern()方法会把第一个遇到的字符串实例复制到常量池中,并返回常量池中字符串的引用,而StringBuilder创建的字符串实例是on堆,所以它不能是相同的引用,并返回false。在JDK1.7中,intern方法不再复制实例,常量池中只保存第一次出现的实例的引用,所以intern()返回的引用与创建的字符串实例相同字符串生成器。为什么str2的比较返回false?这是因为在JVM加载类时,字符串“java”已经存在,不符合“先出现”的原则,所以返回false。方法区的作用方法区存放类信息、常量、静态变量等,是各个线程共享的区域。方法区的使用通过设置虚拟机的参数-XXpermSize和-XX:MaxPermSize来限制方法区的大小。2、虚拟机栈(VMStack)的概念Java方法执行的内存模型:每个方法执行时,会同时创建一个栈帧(StackFrame),用来存放局部变量表、操作栈、动态链接、方法导出等信息。每个方法被调用到执行完成的过程对应一个栈帧。在虚拟机栈中,局部变量表存储了各种基本数据类型(boolean、boolean、byte、char、short、int、float、long、double)、对象引用(Objectreference)和字节码指令地址(returnAddress类型)。操作栈操作数栈也常被称为操作栈,是一种后进先出(LIFO)的栈。和局部变量表一样,操作数栈的最大深度在编译时也会写入到Code属性的max_stacks数据项中。操作数栈的每个元素都可以是任何Java数据类型,包括long和double。32位数据类型占用的栈容量为1,64位数据类型占用的栈容量为2。在方法执行的任何时候,操作数栈深度都不会超过max_stacks数据项中设置的最大值.当一个方法刚开始执行时,这个方法的操作数栈是空的。在方法执行过程中,各种字节码指令会向操作数栈写入和提取内容,即入栈和出栈操作。比如在做算术运算时,通过操作数栈进行,或者调用其他方法时,通过操作数栈来传递参数。比如整数加法字节码指令iadd,运行时要求最靠近操作数栈顶的两个元素已经存入了两个int值。这条指令执行时,将两个Int值相加相加,然后将相加结果压入栈中。操作数栈中元素的数据类型必须严格匹配字节码指令的顺序。在编译程序代码时,编译器必须严格保证这一点,在类验证阶段的数据流分析中必须再次验证。.以上面的iadd指令为例。该指令用于整数加法。执行时,最靠近栈顶的两个元素的数据类型必须是int类型,不能有long和float。使用iadd命令进行匹配。加上情况。动态链接每个栈帧都包含对运行时常量池中栈帧所属方法的引用。保留此引用以支持方法调用期间的动态链接。我们知道Class文件的常量池中有大量的符号引用,而字节码中的方法调用指令就是以指向常量池中方法的符号引用作为参数。其中一些符号引用会在类加载阶段或首次使用时转换为直接引用。这种转换称为静态解析。另一部分会在每次运行时转化为直接引用,这部分称为动态链接。虚拟机栈的特点线程私有生命周期与线程相同虚拟机栈的异常之一是StackOverflowError如果当前线程请求的栈深度大于虚拟机允许的深度,就会出现这个异常抛出。例如,反复递归一个函数本身,最终会导致堆栈溢出错误(StackOverflowError)。代码清单如下:publicclassStackOverflowErrorDemo{publicstaticvoidmain(String[]args){printStackOverflowError();}publicstaticvoidprintStackOverflowError(){printStackOverflowError();}}输出异常结果:Exceptioninthread"main"java.lang.StackOverflowErrorstacklength:9482atcom.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)atcom.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)atcom.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)atcom.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)atcom.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)atcom.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22))atcom.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)atcom.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)atcom.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)大,或者虚拟机栈容量太小。当无法分配内存时,虚拟机会抛出StackOverflowError异常。一种是OOM异常。当虚拟机栈支持动态扩展时,申请不到足够的内存会抛出。OOM异常。代码清单如下:true){}}}.start();}}}谨慎使用这个例子...这个例子通过不断创建线程来产生内存溢出异常。但是这种方式产生的内存溢出异常与栈空间是否足够大没有关系,或者准确的说,这种情况下,分配给每个线程栈的内存越大,越容易产生内存溢出异常。原因是操作系统分配给每个进程的内存是有限制的,比如32位Windows限制为2GB。.虚拟机栈的作用是存放局部变量、操作栈、动态链接、方法出口。对于32位jvm使用的虚拟机堆栈,默认大小为256kb,而对于64位jvm,默认大小为512kb。-Xss设置虚拟机堆栈的最大大小。但是,如果设置太大,会影响可以创建的线程数。3.本地方法栈(NativeMethodStack)的概念本地方法栈和虚拟机栈起着非常相似的作用。它们的区别在于虚拟机栈是为执行Java代码方法服务的,而native方法栈是为Native方法服务的。和虚拟机栈一样,native方法栈也会抛出StackOverflowError和OutOfMemoryError异常。原生方法栈的特点Threadprivate为原生方法服务原生方法栈的异常和虚拟机栈一样,原生方法栈也会抛出StackOverflowError和OutOfMemoryError异常。本地方法栈的作用与java外部环境交互有时java应用程序需要与java外部环境进行交互。这是nativemethods存在的主要原因,你可以想到java需要和一些底层系统如操作系统或一些硬件交换信息的情况。native方法就是这样一种通信机制:它为我们提供了一个非常简洁的接口,我们不需要了解java应用程序之外的繁琐细节。与操作系统交互JVM支持java语言本身和运行时库。它是Java程序赖以生存的平台。它由一个解释器(解释字节码)和一些连接到本机代码的库组成。但是,它毕竟不是一个完整的系统,往往依赖于一些底层(underneath)系统的支持。这些底层系统通常是功能强大的操作系统。通过使用nativemethods,我们可以使用java来实现jre和底层系统的交互,甚至JVM的一些部分是用C写的。另外,如果我们想使用java语言本身的操作系统的一些特性不提供,我们还需要使用native方法。Sun的JavaSun的解释器是用C实现的,这使得它可以像一些普通的C一样与外界交互。jre大部分是用java实现的,它也通过一些native方法与外界交互。例如:类java.lang.Thread的setPriority()方法在java中实现,但它实现的是调用类中的本地方法setPriority0()。这个native方法是用C实现的,植入到JVM内部。在Windows95平台上,这个本机方法最终将调用Win32SetPriority()API。这是JVM直接提供的本地方法的具体实现,更多的时候是本地方法由外部动态链接库(externaldynamiclinklibrary)提供,然后由JVM调用。4.Java堆(Heap)Java堆的概念Java堆可以说是虚拟机中最大的一块内存。它是所有线程共享的内存区域,几乎所有的实例对象都存储在这个区域。当然,随着JIT编译器的发展,所有对象在堆上的分配也逐渐变得不那么“绝对”了。Java堆是垃圾收集器管理的主要区域。由于当前的收集器基本都是采用分代收集算法,所以所有的Java堆都可以细分为:新生代和老年代。详细来说,新生代分为:EdenspaceFromSurvivorToSurvivor根据Java虚拟机规范的规定:Java堆可以在物理上不连续的内存空间,只要逻辑上连续即可,就像我们的与磁盘空间相同。在实现的时候,可以实现为固定大小,也可以是可扩展的,但是目前主流的虚拟机都是按照可扩展的来实现的。Java堆的特点线程间的共享区是在虚拟机启动时创建的。它是虚拟机中最大的一块内存。几乎所有的实例对象都存放在这个区域。当堆不能再扩大时,会抛出Java堆异常。抛出OutOfMemoryError。Java堆的唯一目的是存储对象实例。几乎所有的对象实例都在java堆中分配内存。Java堆的使用由-Xmx和-Xms控制。5.ProgramCounterRegister的概念与PC寄存器类似,程序计数器是一个线程私有区域,每个线程都有自己的程序计数器。将其视为当前线程正在执行的字节码的行号指示器。程序计算器的特点私有线程占用的内存空间小。该内存区域是Java虚拟机规范中唯一没有指定任何OOM(OutOfMemoryError)的区域。程序计算器除外。这个内存区域在Java虚拟机规范中是唯一的。该区域没有任何OOM(OutOfMemoryError)情况的规定。程序计算器的功能信号指示器:在多线程切换时,需要恢复每个线程当前执行位置,通过程序计数器中的值找到要执行指令的字节如果线程正在执行Java方法,计数器记录正在执行的虚拟机的字节码指令地址;如果线程正在执行Native方法,则计数器的值为空(未定义)。程序计算器的使用由-Xmx和-Xms控制6.直接内存(DirectMemory)什么是直接内存和非直接内存?根据官方文档的描述:一个字节缓冲区要么是直接的,要么是非直接的。给定直接字节缓冲区,Java虚拟机将尽最大努力直接在其上执行本机I/O操作。也就是说,它将尝试避免在每次调用底层操作系统的本机I/O操作之一之前(或之后)将缓冲区的内容复制到(或从)中间缓冲区。bytebyffer可以有两种类型,一种是基于直接内存(即非堆内存);另一种是非直接内存(即堆内存)。直接内存(DirectMemory)既不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区,但这部分内存被频繁使用,也可能导致OutOfMemoryError异常。对于直接内存,JVM在IO操作上会有更高的性能,因为它直接作用于本地系统的IO操作。如果堆内存需要进行IO操作,会先复制到直接内存中,再进行本地IO处理。从数据流向来看,非直接内存的作用链:本地IO-->直接内存-->非直接内存-->直接内存-->本地IO以及直接内存的作用链:本地IO-->directmemory-->LocalIO显然,在做IO处理的时候,比如网络发送大量数据的时候,directmemory的效率会更高。可以通过调用此类的allocateDirect工厂方法来创建直接字节缓冲区。此方法返回的缓冲区通常比非直接缓冲区具有更高的分配和释放成本。直接缓冲区的内容可能驻留在正常的垃圾收集堆之外,因此它们对应用程序的内存占用(内存占用)的影响可能并不明显。因此,建议直接缓冲区主要分配给受底层系统本地I/O操作影响的大型、长期缓冲区。通常,最好仅在直接缓冲区在程序性能方面产生可测量的增益时才分配它们。但是由于直接内存是使用allocateDirect创建的,所以比申请普通堆内存需要更高的性能。但是,它不占用应用程序的堆内存。所以,当你有很多数据需要缓存,而且它的生命周期比较长的时候,那么使用直接内存是一个不错的选择。但是,如果这种选择不会带来显着的性能提升,建议使用堆内存。在JDK1.4的NIO中,ByteBuffer有一个方法:publicstaticByteBufferallocateDirect(intcapacity){returnnewDirectByteBuffer(capacity);}DirectByteBuffer(intcap){...protectedstaticfinalUnsafeunsafe=Bits.unsafe();unsafe.allocateMemory(size);...}publicfinalclassUnsafe{...publicnativelongallocateMemory(longvar1);...}另外直接受机器总内存(包括RAM和SWAP区或分页文件)大小和处理器寻址空间限制。服务器管理员在配置虚拟机参数时,一般会根据实际内存设置-Xmx等参数信息,但往往会忽略直接内存,使得各个内存区域的总和大于物理内存限制(包括物理和操作系统级别的限制)),导致动态扩容时出现OutOfMemoryError异常。直接内存的特点不受Java堆大小的限制。它既不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区。它不占用应用程序的内存。它在IO操作上有更高的性能,因为它直接作用于本地系统的IO操作,比申请普通堆内存需要更高的性能。动态扩展直接内存时会出现OutOfMemoryError异常。直接内存的作用是基于通道(Channel)和缓冲区(Buffer)的I/O方式。它可以使用Native函数库直接在堆外分配内存,然后通过一个内部的DirectByteBuffer对象作为对这块内存的引用来操作存储在Java堆中。这在某些场景下可以显着提高性能,因为它避免了在Java堆和Native堆之间来回复制数据。直接内存的使用XX:MaxDirectMemorySize=10M直接内存使用场景如IO处理,比如网络发送大量数据时,直接内存会有更高的效率。