1、FullGC
什么是fullgc:
fullgc就是指清理堆中的young和old区。一般程序健康情况会只做YoungGC。fullgc情况会导致除了gc线程外的线程停止工作,代码静止,系统会大幅变慢。
触发机制
1、system.gc()的调用,此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。PS:并不是说调用了就一定fullgc。只是增大促发机率。
2、old区不足。old区数据是通过eden ygc过来,或者大对象直接进入。所以1、要尽量让对象再ygc时候被回收,让对象再young区多呆着。2、要创建过大的对象及数组
3、(jdk1.8之后移除永久代,perm gen)持久代 Permanet Generation 满了。Permanet Generation中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出错误信息:java.lang.OutOfMemoryError: PermGen space 。为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。
4、通过ygc后进入老年代的平均大小大于老年代的可用内存。如果发现统计数据说之前ygc的平均晋升大小比目前old gen剩余的空间大,则不会触发ygc而是转为触发full GC。
5、ygc是s区内存不够,old区也放不下,会造成fullgc

2、元空间(metaspace)
是1.8之前的永久代、方法区。用于存放类和方法的常量池和元数据。(ps:1.7之后字符串常量池被移除了方法区,放入了堆)

java什么时候 fullGC 什么时候oldGC_java

永久代是有大小限制的,因此如果加载的类太多,很有可能导致永久代内存溢出,即万恶的 java.lang.OutOfMemoryError:PermGen,为此我们不得不对虚拟机做调优。

那么,Java 8 中 PermGen 为什么被移出 HotSpot JVM 了?

1、由于 PermGen 内存经常会溢出,引发恼人的 java.lang.OutOfMemoryError:PermGen,因此 JVM 的开发者希望这一块内存可以更灵活地被管理,不要再经常出现这样的 OOM

2、移除 PermGen 可以促进 HotSpot JVM 与 JRockit VM 的融合,因为 JRockit 没有永久代。

根据上面的各种原因,PermGen 最终被移除,方法区移至 Metaspace,字符串常量池移至堆区。

准确来说,Perm 区中的字符串常量池被移到了堆内存中是在Java7 之后,Java 8 时,PermGen 被元空间代替,其他内容比如类元信息、字段、静态属性、方法、常量等都移动到元空间区。比如 java/lang/Object类元信息、静态属性 System.out、整形常量 100000等。

元空间和方法区最大

的区别:元空间不在虚拟机中,是直接使用本地内存。

参数;

java什么时候 fullGC 什么时候oldGC_推送_02


3、java虚拟机栈

java什么时候 fullGC 什么时候oldGC_java_03


java什么时候 fullGC 什么时候oldGC_出栈_04


栈是线程私有的,栈对应线程,栈帧理解为方法。一个栈帧包含局部变量表,操作栈,动态链接,方法返回地址四个部分。

public class MainTest {
        public static final Integer CONSTANT=666;
        public int compute() {//一个方法对应一块栈帧内存区域
            int a=3;
            int b=5;
            int c=(a+b)*10;
            return c;
        }
        public static void main(String[] args) {
            MainTest math=new MainTest();
            math.compute();
        }
}

用 javap -c MainTest .class 查看反汇编代码

D:\workspace\test\target\classes>javap -c MainTest.class
Compiled from "MainTest.java"
public class MainTest {
  public static final java.lang.Integer CONSTANT;

  public MainTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int compute();
    Code:
       0: iconst_3			//将int类型的3推送至栈顶
       1: istore_1			//将栈顶int类型数值(上面的3)出栈并存入第二个本地变量
       2: iconst_5			//将int类型的5推送至栈顶
       3: istore_2			//将栈顶int类型数值(上面的5)出栈并存入第三个本地变量
       4: iload_1			//将第二个int型本地变量(上面的3)推送至栈顶
       5: iload_2			//将第三个int型本地变量(上面的5)推送至栈顶
       6: iadd				//将栈顶两int型数值出栈,然后相加并将结果压入栈顶
       7: bipush        10	        //将常量值10推送至栈顶
       9: imul				//将栈顶两int型数值出栈,然后相乘并将结果压入栈顶
      10: istore_3			//将栈顶int类型数值(上面的乘积)出栈并存入第四个本地变量
      11: iload_3			//将第四个int类型本地变量推送至栈顶
      12: ireturn			//从当前方法返回int类型值


 public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class MainTest
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method compute:()I
      12: pop
      13: return

  static {};
    Code:
       0: sipush        666
       3: invokestatic  #5                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       6: putstatic     #6                  // Field CONSTANT:Ljava/lang/Integer;
       9: return
}

下面我们通过图示简单表示一下上面compute方法中指令操作时关于本地变量表、操作数栈的情况:

我们先看下第一行 0: iconst_3 //将int类型的3推送至栈顶,可以看到下图3已经被入栈到操作数栈的栈顶。

java什么时候 fullGC 什么时候oldGC_推送_05

我们再看下第二行 1: istore_1 //将栈顶int类型数值(上面的3)出栈并存入第二个本地变量,将上图中栈顶的3出栈然后存入本地表中第二个位置,如下图所示:

java什么时候 fullGC 什么时候oldGC_java_06

第三行、第四行跟上面的一二行指令类似,第四行指令执行后变成如下所示:

java什么时候 fullGC 什么时候oldGC_java_07

第五行、第六行中 4: iload_1 //将第二个int型本地变量(上面的3)推送至栈顶; 5: iload_2 //将第三个int型本地变量(上面的5)推送至栈顶,即将局部变量表中的3和5依次压入栈顶,如下图所示:

java什么时候 fullGC 什么时候oldGC_出栈_08

然后第七行执行iadd操作,将栈顶的两个int类型数据5和3出栈相加,将得到的和压入栈顶,得到如下结果:

java什么时候 fullGC 什么时候oldGC_java_09

后面的指令操作过程与上面类似,执行完第12行的iload_3指令之后,会得到如下图所示:

java什么时候 fullGC 什么时候oldGC_推送_10


关于局部变量表的信息,还可以通过javap -l 命令查看如下图所示,另外还可以通过Idea中的jclasslib 查看。

LocalVariableTable表示的就是局部变量表的信息:

public int compute();
    LineNumberTable:
      line 8: 0
      line 9: 2
      line 10: 4
      line 11: 11
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      13     0  this   Lcom/wkp/jvm/Math;
          2      11     1     a   I
          4       9     2     b   I
         11       2     3     c   I

动态链接:
Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存人口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

Math math=new Math();
math.compute();//调用实例方法compute()

以上面两行代码为例,解释一下动态连接:math.compute()调用时compute()叫符号,需要通过compute()这个符号去到常量池中去找到对应方法的符号引用,运行时将通过符号引用找到方法的字节码指令的内存地址。
方法的返回地址:
方法执行时有两种退出情况:

1、正常退出,即正常执行到任何方法的返回字节码指令,如 RETURN、 IRETURN、 ARETURN等
2、异常退出

无论何种退出情况,都将返回至方法当前被调用的位置。方法退出的过程相当于弹出当前栈帧,退出可能有三种方式:

1、返回值压入上层调用栈帧
2、异常信息抛给能够处理的栈帧
3、PC 计数器指向方法调用后的下一条指令

4、本地方法栈
理解同上,本地方法栈指的是虚拟机使用到的native方法服务,java虚拟机栈是虚拟机执行java的服务。

5、程序计数器
也是线程私有,每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器用来存放执行指令的偏移量和行号指示器等,线程执行或恢复都要依赖程序计数器。此区域也不会发生内存溢出异常。用来记录线程执行到哪里,下步执行啥。

6、code cache