1:jdk体系结构
2:Java为解释性语言,跨平台(一行代码,到处运行):
3:Java虚拟机模型 &执行顺序。类装载子系统->运行时数据区->字节码执行引擎
以下代码为测试代码,通过以下代码详解运行过程
public class Test6 {
public static final int initData = 123;
public static User user = new User();
public int computer(){
int a = 1;
int b = 2;
int c = (a+b) * 10;
return c;
}
public static void main(String[] args) {
Test6 test = new Test6();
test.computer();
System.out.println("test");
}
}
4:虚拟机内部详解
4.1:栈
- 栈中存放着局部变量,Java运行后,虚拟机给每个线程分配一块栈内存
- 栈帧:运行时给每个线程的每个方法分配一块专属内存区域,用于存放每个方法对应的局部变量
4.2:栈帧,其中栈帧具体组成如下:
- 操作数栈:一块临时的内存区域,用于存放操作时的临时中转数据(例如上述代码的10)
- 局部变量表:本栈帧的所有变量,用1,2,3表示存储,(如果是对象时只存引用,具体存放到堆中)
- 动态链接:为了垃圾回收,使用图的遍历(可达性算法,未能达到的进行回收)
- 方法出口:方法执行完时,该返回的栈帧所对应的行号的具体内存区域
4.3 :本地方法栈
- 本地方法栈:native关键字,本地的其他方法(非Java的库函数,例如.dll的动态链接库)
- 解释:Java语言95年面世,95年以前都是使用其他语言,95后为了和其他语言系统交互,引用了本地方法的这个关键字。
4.4 :方法区
- 存放类信息(例如下面进行反汇编的文件),static变量,final变量
4.5 :堆
- 图中分数表示堆内存的具体划分,new出来的对象全部放入eden区(伊甸园区),为了防止放满,进行minor gc回收
- 垃圾对象:使用可达性算法进行回收
- 可达性分析算法:将GC RootS对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象标记为非垃圾对象,其余未标记的都是垃圾对象。
- GC Roots根节点:线程的本地变量,静态变量,本地方法栈的变量等等
- 在Eden区进行算法之后,可达性的所有变量放入from区中(同时对象的分代年龄加一),之后统一对Eden区进行垃圾回收
- 在第二次gc时候,同时对eden和from区的对象进行同样操作,将所有非垃圾对象放入To区。第三次的时候将to和eden区进行垃圾回收,非垃圾放入from区,以此往复,其中每次分代年龄加一。
- 分代年龄>15(值可以设置)时,将对象移至老年代。(bean,线程池中的对象等等)
5 : 使用jVisualvm(jdk自带,命令行输入即可查看)查看运行时Java程序的内存等各种信息情况
- eden或Survivor区满之后进行一次gc,同时进入老年代一批;
- 老年区放慢之后执行full gc(尝试收集一下整个堆的内存,此时会Stop-The-World,简称STW,会中止线程),彻底没有内存后,抛出异常
- jvm调优的目的就是为了减少STW的次数或减少STW的时间
6 :对.class文件进行反编译,进而了解Java虚拟机
- 找到.class文件位置,使用javap命令进行反编译(javap -c Test6.class > Test6.txt)
- 反汇编结果如下,即为Java虚拟机运行的指令码,对照jvm指令手册食用
- 程序计数器:记录当前线程jvm执行到指令码的第几行;
Compiled from "Test6.java"
public class main.Test6 {
public static final int initData;
public static main.User user;
public main.Test6();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int computer();
Code:
0: iconst_1 //将int类型常量1压入操作数栈
1: istore_1 //将int类型的值存入局部变量1(a)
2: iconst_2 //将int类型常量2压入操作数栈
3: istore_2 //将int类型的值存入局部变量2(b)
4: iload_1 //当前程序计数器值为4,表示执行到此第四行(多线程时记录位置),
//从局部变量1中装载int类型值(即为a的值)
5: iload_2
6: iadd //执行int类型的加号(从操作数栈中弹出顶层两个元素进行+,
//结果返回到操作数栈顶)
7: bipush 10 //将常量10压入操作数栈,(现在栈内元素为3和10)
9: imul //执行int类型的乘法(同样为栈顶两个元素,结果返回栈顶)
10: istore_3 //将int类型的值存入局部变量3(c),相当于给c赋值
11: iload_3 //加载出来int类型的3,为了return
12: ireturn
public static void main(java.lang.String[]);
Code:
0: new #2 // class main/Test6
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method computer:()I
12: pop
13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
16: ldc #6 // String test
18: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
21: return
static {};
Code:
0: new #8 // class main/User
3: dup
4: invokespecial #9 // Method main/User."<init>":()V
7: putstatic #10 // Field user:Lmain/User;
10: return
}
7 :jvm调优
- 对象动态年龄判断:一个对象的大小大于等于Survivor区的百分之五十,会直接放入老年区
- 根据业务判断每秒的业务内存占用数进行调优,以亿级流量为例
- 根据以上流程大致判断每秒需要内存数,进行调优(减少STW的次数),如下增加年轻区内存
面试:能否对jvm调优,使其几乎不发生full gc(使那些朝花夕拾的对象尽量在年轻代就被销毁)
答:尽量使年轻代的内存大,则大部分不会加载到老年区,增加minor gc ,减少full gc
每秒60M的内存需要,尽可能使这些内存不放入老年区(将年轻区内存分配的够大,则每次进行minor gc时就可清除上一秒的60M内存)