标签:默认 标记 避免 根据 虚拟机启动 卸载 函数 新生代 hot
1,程序计数器(Program Counter Register):程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,可以理解为是当前线程的行号指示器。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令。
每个程序计数器只用来记录一个线程的行号,所以它是线程私有的(一个线程就有一个程序计数器)。
如果程序执行的是一个Java方法,则计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是一个本地(native,由C语言编写完成)方法,则计数器的值为Undefined,由于程序计数器只是记录当前指令地址,所以不存在内存溢出的情况,因此,程序计数器也是所有JVM内存区域中唯一一个没有定义OutOfMemoryError的区域。
2,虚拟机栈(VM Stack):一个线程的每个方法在执行的同时,都会创建一个栈帧(Statck Frame),栈帧中存储的有局部变量表、操作栈、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、动态链接、方法出口等,当方法被调用时,栈帧在VM栈中入栈,当方法执行完成时,栈帧出栈。
局部变量表中存储着方法的相关局部变量,包括各种基本数据类型,对象的引用,返回地址等。在局部变量表中,只有long和double类型会占用2个局部变量空间(Slot,对于32位机器,一个Slot就是32个bit),其它都是1个Slot。需要注意的是,局部变量表是在编译时就已经确定好的,方法运行所需要分配的空间在栈帧中是完全确定的,在方法的生命周期内都不会改变。
虚拟机栈中定义了两种异常,如果线程调用的栈深度大于虚拟机允许的最大深度,则抛出StatckOverFlowError(栈溢出);不过多数Java虚拟机都允许动态扩展虚拟机栈的大小(有少部分是固定长度的),所以线程可以一直申请栈,直到内存不足,此时,会抛出OutOfMemoryError(内存溢出)。
每个线程对应着一个虚拟机栈,因此虚拟机栈也是线程私有的。
3,本地方法栈(Native Method Statck):本地方法栈在 作用、运行机制、异常类型等方面都与虚拟机栈相同,唯一的区别是:虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的,在很多虚拟机中(如Sun的JDK默认的HotSpot虚拟机),会将本地方法栈与虚拟机栈放在一起使用。
本地方法栈也是线程私有的。
4,堆区(Heap):堆区是理解Java GC机制最重要的区域,没有之一。在JVM所管理的内存中,堆区是最大的一块,堆区也是Java GC机制所管理的主要内存区域,堆区由所有线程共享,在虚拟机启动时创建。堆区的存在是为了存储对象实例,原则上讲,所有的对象都在堆区上分配内存(不过现代技术里,也不是这么绝对的,也有栈上直接分配的)。
一般的,根据Java虚拟机规范规定,堆内存需要在逻辑上是连续的(在物理上不需要),在实现时,可以是固定大小的,也可以是可扩展的,目前主流的虚拟机都是可扩展的。如果在执行垃圾回收之后,仍没有足够的内存分配,也不能再扩展,将会抛出OutOfMemoryError:Java heap space异常。
5,方法区(Method Area):在Java虚拟机规范中,将方法区作为堆的一个逻辑部分来对待,但事实上,方法区并不是堆(Non-Heap),方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。
方法区在物理上也不需要是连续的,可以选择固定大小或可扩展大小,并且方法区比堆还多了一个限制:可以选择是否执行垃圾收集。一般的,方法区上执行的垃圾收集是很少的,这也是方法区被称为永久代的原因之一(HotSpot),但这也不代表在方法区上完全没有垃圾收集,其上的垃圾收集主要是针对常量池的内存回收和对已加载类的卸载。
在方法区上进行垃圾收集,条件苛刻而且相当困难,效果也不令人满意,所以一般不做太多考虑,可以留作以后进一步深入研究时使用。
在方法区上定义了OutOfMemoryError:PermGen space异常,在内存不足时抛出。
运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译期就生成的字面常量、符号引用、翻译出来的直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类链接阶段完成翻译);运行时常量池除了存储编译期常量外,也可以存储在运行时间产生的常量(比如String类的intern()方法,作用是String维护了一个常量池,如果调用的字符“abc”已经在常量池中,则返回池中的字符串地址,否则,新建一个常量加入池中,并返回地址)。
6,直接内存(Direct Memory):直接内存并不是JVM管理的内存,可以这样理解,直接内存,就是JVM以外的机器内存,比如,你有4G的内存,JVM占用了1G,则其余的3G就是直接内存,JDK中有一种基于通道(Channel)和缓冲区(Buffer)的内存分配方式,将由C语言实现的native函数库分配在直接内存中,用存储在JVM堆中的DirectByteBuffer来引用。由于直接内存受到本机器内存的限制,所以也可能出现OutOfMemoryError的异常。
这里所说的内存分配,主要指的是在堆上的分配,一般的,对象的内存分配都是在堆上进行,但现代技术也支持将对象拆成标量类型(标量类型即原子类型,表示单个值,可以是基本类型或String等),然后在栈上分配,在栈上分配的很少见,我们这里不考虑。
Java内存分配和回收的机制概括的说,就是:分代分配,分代回收。对象将根据存活的时间被分为:
年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)
年轻代(Young Generation)
对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉(IBM的研究表明,98%的对象都是很快消亡的),这个GC机制被称为Minor GC或叫Young GC。注意,Minor GC并不代表年轻代内存不足,它事实上只表示在Eden区上的GC。
年轻代上的内存分配是这样的,年轻代可以分为3个区域:Eden区(伊甸园)和两个存活区(Survivor 0 、Survivor 1)。内存分配过程为(来源于《成为JavaGC专家part I》,http://www.importnew.com/1993.html)
这种垃圾回收的方式就是著名的“停止-复制(Stop-and-copy)”清理法(将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中)
在Eden区,HotSpot虚拟机使用了两种技术来加快内存分配。分别是bump-the-pointer和TLAB(Thread-Local Allocation Buffers),这两种技术的做法分别是:
(1)由于Eden区是连续的,因此bump-the-pointer技术的核心就是跟踪最后创建的一个对象,在对象创建时,只需要检查最后一个对象后面是否有足够的内存即可,从而大大加快内存分配速度;
(2)而对于TLAB技术是对于多线程而言的,将Eden区分为若干段,每个线程使用独立的一段,避免相互影响。TLAB结合bump-the-pointer技术,将保证每个线程都使用Eden区的一段,并快速的分配内存。
年老代(Old Generation)
对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时,将执行Major GC,也叫 Full GC。
可以使用-XX:+UseAdaptiveSizePolicy开关来控制是否采用动态控制策略,如果动态控制,则动态调整Java堆中各个区域的大小以及进入老年代的年龄。
如果对象比较大(比如长字符串或大数组),Young空间不足,则大对象会直接分配到老年代上(大对象可能触发提前GC,应少用,更应避免使用短命的大对象)。
用-XX:PretenureSizeThreshold来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。
可能存在年老代对象引用新生代对象的情况,如果需要执行Young GC,则可能需要查询整个老年代以确定是否可以清理回收,这显然是低效的。
解决的方法是,年老代中维护一个512 byte的块——”card table“,所有老年代对象引用新生代对象的记录都记录在这里。Young GC时,只要查这里即可,不用再去查全部老年代,因此性能大大提高。
年轻代:
在年轻代中,使用“停止-复制”算法进行清理,将新生代内存分为2部分,1部分 Eden区较大,1部分Survivor比较小,并被划分为两个等量的部分。每次进行清理时,将Eden区和一个Survivor中仍然存活的对象拷贝到 另一个Survivor中,然后清理掉Eden和刚才的Survivor。
由于绝大部分的对象都是短命的,甚至存活不到Survivor中,所以,Eden区与Survivor的比例较大,HotSpot默认是 8:1,即分别占新生代的80%,10%,10%。如果一次回收中,Survivor+Eden中存活下来的内存超过了10%,则需要将一部分对象分配到 老年代。用-XX:SurvivorRatio参数来配置Eden区域Survivor区的容量比值,默认是8,代表Eden:Survivor1:Survivor2=8:1:1。
老年代:
方法区(永久代):
永久代的回收有两种:常量池中的常量,无用的类信息,
常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点:
参考:https://www.cnblogs.com/zhguang/p/3257367.html
标签:默认 标记 避免 根据 虚拟机启动 卸载 函数 新生代 hot
原文地址:https://www.cnblogs.com/wyhb008/p/10593846.html