虚拟机栈:保存执行方法时的局部变量、操作数栈、动态连接、方法返回地址,方法执行时入栈,方法执行完出栈,出栈就相当于清空数据。【不需要进行GC】
备注:
1、Java 字节码指令的操作数存放在操作数栈中,当执行某条带 n 个操作数的指令时,就从栈顶取 n 个操作数,然后把指令的计算结果(如果有的话)入栈。因此,当我们说 JVM 执行引擎是基于栈的时候,其中的“栈”指的就是操作数栈
2、在一个class文件中,一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于方法区中的运行时常量池,Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态连接(Dynamic Linking)。
本地方法栈:本地方法栈为虚拟机执行本地方法时服务。【不需要进行GC】
程序计数器:可以把它看作当前线程执行的字节码的行号指示器,java虚拟机的多线程是通过线程轮流切换并分配处理器的时间来完成的,一个处理器只会执行一个线程,如果这个线程被分配的时间片执行完了(线程挂起),处理器会切换到另一个线程执行,当下次到执行被挂起的线程(唤醒线程)时,怎么知道上次执行到哪里,通过程序计数器的行号即可知道。【不需要进行GC】
本地内存:也叫堆外内存,主要存储类的信息,常量(final),静态变量(static)。【在java8以后这区域不需要进行GC】
堆:这是GC发生的区域,对象实例和数组都是在堆上分配的,GC主要对这两类数据进行回收。
1、引用计数法
2、可达性算法
1、标记清除算法
2、复制算法
3、标记整理法
4、分代收集算法(整合前3种算法,综合这些算法的优点,避免它们的缺点)
JDK8的堆内存结构:
可以看到,堆内存采用了分代结构,包括新生代和老年代。
新生代又分为:Eden区,From Survivor区(简称S0),To Survivor区(简称S1区),三者的默认比例为8:1:1。
另外,新生代和老年代的默认比例为1:2。
1、大部分对象在很短的时间内都会被回收,对象一般分配在 Eden 区
2、当 Eden 区将满时,触发 Minor GC,
3、当触发下一次 Minor GC 时,会把 Eden 区的存活对象和 S0(或S1) 中的存活对象(S0 或 S1 中的存活对象经过每次 Minor GC 都可能被回收)一起移到 S1(Eden 和 S0 的存活对象年龄+1), 同时清空 Eden 和 S0 的空间
若再触发下一次 Minor GC,则重复上一步,只不过此时变成了 从 Eden,S1 区将存活对象复制到 S0 区,每次垃圾回收, S0, S1 角色互换,都是从 Eden ,S0(或S1) 将存活对象移动到 S1(或S0)。也就是说在 Eden 区的垃圾回收我们采用的是复制算法,因为在 Eden 区分配的对象大部分在 Minor GC 后都消亡了,只剩下极少部分存活对象(这也是为啥 Eden:S0:S1 默认为 8:1:1 的原因),S0,S1 区域也比较小,所以最大限度地降低了复制算法造成的对象频繁拷贝带来的开销。
4、当对象的年龄达到了我们设定的阈值,则会从S0(或S1)晋升到老年代
如果老年代满了,会触发 Full GC, Full GC 会同时回收新生代和老年代(即对整个堆进行GC),它会导致 Stop The World(简称 STW),造成挺大的性能开销。
什么是 STW ?所谓的 STW, 即在 GC(minor GC 或 Full GC)期间,只有垃圾回收器线程在工作,其他工作线程则被挂起。
一般 Full GC 会导致工作线程停顿时间过长(因为Full GC 会清理整个堆中的不可用对象,一般要花较长的时间),如果在此 server 收到了很多请求,则会被拒绝服务!所以我们要尽量减少 Full GC(Minor GC 也会造成 STW,但只会触发轻微的 STW,因为 Eden 区的对象大部分都被回收了,只有极少数存活对象会通过复制算法转移到 S0 或 S1 区,所以相对还好)。
现在我们应该明白把新生代设置成 Eden, S0,S1区或者给对象设置年龄阈值或者默认把新生代与老年代的空间大小设置成 1:2 都是为了尽可能地避免对象过早地进入老年代,尽可能晚地触发 Full GC。想想新生代如果只设置 Eden 会发生什么,后果就是每经过一次 Minor GC,存活对象会过早地进入老年代,那么老年代很快就会装满,很快会触发 Full GC,而对象其实在经过两三次的 Minor GC 后大部分都会消亡,所以有了 S0,S1的缓冲,只有少数的对象会进入老年代,老年代大小也就不会这么快地增长,也就避免了过早地触发 Full GC。
#######################################################################################
jdk8之前的划分:
根据之前的规律,就可以用来提升 JVM 的效率了。方法是,把堆分成几个部分(就是所谓的分代),分别是新生代、老年代,以及永生代
图片中的垃圾收集器如果存在连线,则代表它们之间可以配合使用
如果是运行在桌面环境处于 Client 模式的,则用 Serial + Serial Old 收集器绰绰有余,如果需要响应时间快,用户体验好的,则用 ParNew + CMS 的搭配模式,即使是号称是驾驭一切的 G1,也需要根据吞吐量等要求适当调整相应的 JVM 参数