内容简介:;程序计数器是线程私有的内存区域,这个区域是Java虚拟机中唯一一个没有限制但是,如果线程中执行的是一个Native方法,那么程序计数器是不会去记录的,所以此时的程序计数器为空。
;
程序计数器是线程私有的内存区域,这个区域是 Java 虚拟机中唯一一个没有限制 OutOfMemoryError
的内存区域。之所以需要它是因为Java的多线程机制是通过轮流切换分配处理器执行时间来实现的,所以会涉及到线程的暂停和重启,而在一个线程中如果正在执行Java方法的话,这个计数器就回去记录当前正在执行的虚拟机字节码,一旦被暂停,恢复只需要从程序计数器记录的为止继续执行就可以。
但是,如果线程中执行的是一个Native方法,那么程序计数器是不会去记录的,所以此时的程序计数器为空。
虚拟机栈Stack
Java虚拟机栈也是线程私有的。一条线程启动就会为它建立一个虚拟机栈。
在线程中每有一个Java方法被调用就会创建一个 “栈帧” 。每个 “栈帧” 会保存执行该方法所需的局部变量表(一般Java程序员喜欢用这个部分来代表栈)、操作数栈、动态链接以及方法出口等信息。
如果一个线程中有过多的 “栈帧” 要入到虚拟机栈中,即短时间内调用了过多的方法,就会造成 -- 栈益处 -- ,即 StackOverflowError 错误。
在这个内存区域中,如果虚拟机需要扩展内存,但没有申请到足够的内存,就会抛出 OutOfMemoryError 错误。
本地方法栈
和虚拟机栈有些类似,但它是为Native方法提供服务的。
Java堆Heap
Java的堆内存是Java虚拟机所管理的内存中最大的一块。它是所有线程所共享的,用于存放对象实例和数组,Java虚拟机的GC主要就发生在这个地方。因此这块区域也叫做"GC堆"。
Java堆的内存可以按照垃圾回收算法【分代回收】分为【新生代区】和【老年区】,进一步的,【新生代区】可以分为【Eden区】、【From Survivor区】和【To Survivor区】。
从内存角度来说,Java堆内存又被划分为线程共享的内存区域和每个线程私有的内存区域。
在Java堆区域中,如果没有内存分配给要创建的实例,并且堆也不能够再扩展,就会抛出 OutOfMemoryError
错误。
回收算法
在 Java8 之后, Heap Segment 真正意义上的是由 Young Generiation 和 Old Generiation 组成的。对象在其中是 标记复制算法 来判定一个对象是否应该被清理掉。
Heap Segment 中发生的GC称为 Major GC ,只会影响 Heap Segment 区。
Young Generiation中的GC变化 — 复制算法
这个区域发生的GC称为 Minor GC
。
- 当对象被创建后,首先会被加入 eden 区。当 eden 区满了之后,就会触发一次GC,存活下来的对象会被复制到 survivor 区。
- 当不为空的 Survivor 区满了,同样会触发一次GC。
- 当短时间内有大量对象创建和释放同样会造成 内存抖动 ,会触发CG。
- 如图所示, survivor 有两个区域,其中一个总是保持为空。
- 现假设两个 Survivor 区分别为S0,S1,并且首次GC时, eden 区中存活的对象被复制到S0中。当再次发生GC时,S0和 eden 中仍然存活的对象就会被复制到空的S1中,此时S0为空;再次发生GC时,S1和 eden 中存活的对象将被复制到S0中,此时S1为空;再次发生GC...就是这样进行的。当一个对象被来回复制转移的次数达到阀值(默认为15次,可以通过使用
-XX:MaxTenuringThreshold
该命令来调整阀值)时,这个对象将被复制到 Old Generiation 区中,此时该对象将会变的相对安全,因为 Old Segment 区的GC频率相对较低。
Old Segment中的GC变化
这个区域发送的GC成为 Full GC
。
- 该区域满了之后会触发一次GC,在该次GC中,一些年龄较大的对象会被清理掉。
- 若多次触发GC后,该区域仍然处于满的状态,则会抛出
OutOfMemoryError
。 - 以两种情况下,新建对象会被直接复制到该区域中:
- 当新建对象所需要的内存大于1/2的单个 survivor 区内存时。比如一些很长的对象;
- 当新建对象被该区中的对象引用时,或者引用了该区域中的对象。
方法区
Java的方法区和Java的堆内存一样是被线程所共有的。它主要存放虚拟机加载的类信息、常量、静态变量、即时编译产生的代码等。
一些地方会将方法区合并到Java堆中一起去说。把它作为“永久代”。这在Hot-Spot虚拟机而言成立,但是一般来说是不成立的。
Java的方法区如果内存不够分配的话,也是会抛出 OutOfMemoryError
错误的。也就是如果加载过多类到方法区的话,可能会造成方法区内存益处。
对象的可到达性
在GC检查对象的是否可以回收时,是根据对象是否可到达引用练顶端的 GC Roots
对象来判断的。 GC Roots
对象一般是虚拟机栈中变量表中引用的对象、类静态属性引用的对象、常量对象、JNI传到底层的对象。就是说,一个对象如果溯源不到这几种类型的对象的话,就认为它是无法到达的,那么它将会在GC时被回收。
新的引用类型
在JDK 1.2之后,Java扩充了4种引用类型定义:
强应用类型
即我们平时通过new关键字创建出来的的对象的引用,只要强引用还存在,那么这些对象就一定不回被回收,即使时抛出 OutOfMemoryError
。什么时候强引用会不存在呢?当一个方法执行完,栈帧中的变量表将会被清理,在该方法中创建使用的临时强引用就会被清理掉,之后,原本它指向的对象就被变的不可到达。
软引用类型
用来描述一些有用但不是必须的对象,即通过 SoftReference
创建的对象,它们将会在原本确定要发生内存溢出前的一次GC中被回收,如果回收完内存还是不够,Java堆就会抛出 OutOfMemoryError
错误。就是说,在触发内存溢出发生前,这些对象是和强引用一样,只要引用还在,就不会被回收。
弱引用类型
用来描述一些不必须的对象,即通过 WeakReference
创建的对象。弱引用对象的生命周期只有一次GC。
虚引用类型
一个对象的存在与否完全不受虚引用的影响,它唯一的用处就是可以用来监测一个对象是否被回收。
方法区中的-运行时常量池
运行时常量池主要存放类中编译时期生成的常量,当然也可以动态的往里面添加。
比如:
"abc".intern(); 复制代码
这个方法首先会检查运行时常量池中是否有这个字符串,有的话取出来用,没有的话生成一个并存到常量池中。
再比如,运行过程中生成通过 static
修饰的String时,也会加入到常量池中。对于String而言, 常量 + 常量
生成的也是常量,但是 常量 + 变量
生成的就是变量了。
关于Dalvik虚拟机
Dalvik虚拟机是Google按照JVM虚拟机规范定制的虚拟机,它更符合移动设备的环境要求。与标准虚拟机不同:
- Dalvik编译生成的是
.dex
文件,这种格式的文件体积更小。而JVM规范的是.class
文件。 - Dalvik虚拟机是基于寄存器的,而JVM规范是基于栈的,所以速度方面会有优势。比如上面的说的标准Java虚拟机中,它的虚拟机栈就为线程的运行提供了服务。而Dalvik虚拟机中,使用寄存器去储存运行指令,同时寄存器也提供了程序计数器。寄存器是处理器的一部分哦!
- Dalvik虚拟机允许在内存中创建都个实例,以隔离不同的应用程序。这样,当一个应用程序在自己的进程中崩溃后,不会影响其它进程的运行。
以上所述就是小编给大家介绍的《【拒绝一问就懵】之你多少要懂点内存回收机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。