首先附上一张图,帮助理解:
JVM虚拟机的组成结构:
类加载器:把class文件加载到内存中
运行时数据区:包括堆、方法区、栈、本地方法栈、程序计数器,其实本质就是一个内存模型
字节码执行引擎:字节码class文件其实只是一套指令集规范,需要这个执行引擎来把它转为底层操作系统能识别的底层系统指令
本地库接口:这个是在执行代码时常需调用的本地库接口
下面主要对运行时数据区来做讲解:
一个程序从main方法开始运行,其实最开始划分出来的内存是栈,而栈里面会划分各个栈帧,用来存放运行到各个方法时的各种局部变量、操作数栈、动态链接、方法出口
;
这里我们假设某个方法a里有一条a=3×10的程序,要了解上面这4个到底存放的是什么,我们应该去看对应的字节码文件,然后去看它的一个实际操作过程;
1、运行到a方法时,就会创建一个栈帧用来存这个方法
2、在这个栈帧里面再开辟一个局部变量表,用来存放a
3、开辟一个操作数栈,用来存放3,再往下执行,将10也压入操作数栈,再运行,把3和10进行相乘,再把结果30存入操作数栈,再往下,就会把30从操作数栈取出放到局部变量表中;也就是此时的局部变量表存量a=30,而操作数栈此时是空的;
而栈是怎么找得到a、10、3等等的代码呢?其实靠的就是动态链接,也就是动态链接里存留这些代码所对应的内存地址,因为一开始整个方法是被加载到方法区里的,所以要拿,就要靠动态链接里存储对应的内存地址;
而方法出口其实是存了程序运行的位置,这里其实要区分程序计数器,程序计数器针对的是对应的线程,而这个方法出口找的是返回的位置,比如a方法是通过b方法里的某行程序调用的,当a方法执行完后,它可以准确地回到b方法里调用a方法时的那行程序,而不是又回到b方法的第一行;而程序计数器基本的作用是用来实现多线程,保证cpu能记住切换线程时程序运行的位置;对于程序计数器,这里多说一句,它的这个计数值,其实是由字节码执行引擎来控制的,字节码执行引擎会存在方法区中,既然是它把字节码转为底层系统指令,那计数的任务合情合理就是它执行的;
方法区:放常量、静态变量以及类信息
堆:放实例化出来的对象,这里要注意的是其实栈里面的局部量以及方法区里面的静态变量完全有可能指向的就是一个对象,那这个时候这个对象存在的真正位置其实就是在堆里,栈和方法区里存的不过是这些对象在堆里面的一个引用地址
本地方法栈:存的就是本地方法,对于本地方法,其实比较好理解就是跨语言调用,比如native修饰的方法,当在windows执行到这种方法时,其实调用的是本地某个dll文件里由c++语言编写好的对应方法,而存这些c++程序的内存就是本地方法区,现在基本很少用到本地方法了,各种rpc调用、http调用已经基本抛弃了这种技术了
对于GC,其实就要好好了解堆内存是怎么存放实例化对象的了;
堆结构组成:;
1、分为所谓的年轻代和老年代,默认比例1:2,实例化对象一开始创建后都存在年轻代中;
2、年轻代里分为Eden区和Survivor区,默认比例8:2,Survivor区根据1:1分为Survivor1和Survivor2
一个对象的结构组成:
可以看到,我们平时所说的对象其实不仅仅是实例数据,还包含了对象头,里面记载了对象的各种状态
GC原理:年轻代满脸之后会触发minor gc,从GCRoots根节点去找它底下的引用,一层一层往下找,直到找到最后一个对象没有其他引用,这时候虚拟机会将这整个过程中的所有对象看做是非垃圾对象,这里的GCRoots指的是静态变量、线程栈的本地变量、本地方法区的变量等;
第一次的minor gc会将找到的所有非垃圾对象放到Survivor1中,第二次minor gc会将找到的非垃圾对象放到Survivor2(这里的minor gc是包括Survivor1里存放的非垃圾对象),再依次进行minor gc,将非垃圾对象在Survivor1和Survivor2来回存放跳转;直到跳了15次,也就是对象头里的分代年龄达到15,就会把这个对象放到老年代区,当老年代也满了之后,就会进行一个full gc
我们在进行gc的时候会触发一个STW机制,就是我们在gc的时候线程是停止,原因是防止因线程的运行导致的垃圾判断矛盾,比如我们在gc的时候,线程a在运行时我们刚好检测到它某个方法里的本地变量,这时引用这个变量的对象我们都标记为非垃圾对象,而等这个线程a运行完后,这个存在线程栈里的变量一定就没了,这时候原本标记为非垃圾的对象其实已经是垃圾对象,这就造成了gc的判断冲突;
所有我们调优JVM实际上就是避免系统频繁进行full gc,至于minor gc,因为对象是年轻代,大部分都是垃圾对象,gc时造成的STW时间可以短到忽略不计,可full gc是需要一定时间的,几秒的STW已经会让用户有不好体验了,如果频繁full gc,那基本就gg了;
增加一点,除了分代年龄达到15的对象会存到老年代,如果创建的对象是大对象的话其实也会进入老年代,一次minor gc所形成的非垃圾对象如果大于Survivor容量一半的话,也会被直接放到老年代,所有实际情况中要尽量避免这些情况的出现
Survivor2