JVM内存结构
Java虚拟机由 类加载子系统、执行引擎、JVM运行时数据区 构成
运行分析
每个线程都有自己私有的数据区,如下图
方法用入栈的方式调用
代码调试例子
public class HelloWorld {
public int add() {
int a = 1;
int b = 2;
int c = (a + b) * 100;
return c;
}
/**
* 程序入口
* @param args
*/
public static void main(String[] args) {
HelloWorld app = new HelloWorld();
int result = app.add();
System.out.println(result);
}
}
javap
因为.class文件是经过编译的,所以我们无法看懂,我们想看它怎么办呢?通过借助javap帮忙
通过javap -c 加上.class文件名,也可以通过javap -c 文件名.class > 文件名.txt 写入文本文件
虽然借助工具编译之后,我们大概的结构可以看懂了,但是细节还是看不懂,可以去官方网站找到这些指令的含义
iconst_1 将int类型常量1压入栈
iconst_2 将int类型常量2压入栈
istore_1 将int类型值存入局部变量1
istore_2 将int类型值存入局部变量2
istore_3 将int类型值存入局部变量3
iload_1 从局部变量1中装载int类型值
iload_2 从局部变量2中装载int类型值
iload_3 从局部变量3中装载int类型值
iadd 执行int类型的加法
biputsh 将一个8位带符号整数压入栈
imul 执行int类型的乘法
ireturn 从方法中返回int类型的数据
分析add()方法
istore
执行istore_2的时候也和1一样
iload_1
把1在压回栈中; iload_2 把2也压回栈中
iadd
在哪里执行?执行引擎操作 操作数栈 在栈里执行
1和2出栈执行iadd方法相加,获得3,入栈
biputsh
7: bipush 100
9: imul
仔细看的话会发现 中间没有8,因为7中是包含了8,100就是8
把100压入栈
imul
100和3出栈执行imul,结果入栈
istore_3/iload_3
将300赋值给第三个变量,将300压入栈
ireturn
从方法出口返回数值
对象变量也是入栈,它的引用在堆中
程序计数器
程序计数器:指向当前线程所执行的字节码指令的(地址)行号
Code代表的就是行号,程序计数器加1,就执行一条命令
本地方法栈
public static native void sleep(long millis) throws InterruptedException;
如被native修饰的方法,就存储在本地方法栈中
方法区(元空间)
线程共享,存放已经被虚拟机加载进来的类信息,常量、静态变量,JIT编译后的数据代码。java的class文件首先进入的到方法区里面去
对象与类的关系
堆
堆分为两部分,新生代和老年代
新生代又分为三部分 Eden; from; to
对象的创建大部分都是在Eden区
堆结构图
比例
新生代: 老年代 = 1: 2
Eden: from: to = 8: 1: 1
垃圾回收GC
-XX:MaxTenuringThreshold=15超过15次进入老年代
-XX:PretenureSizeThreshold= M设置超过这个值的直接进入老年代
minor gc
当Eden区内存被占满之后就会触发,minor gc
根的可达性判定 gc roots,根据当前已创建的对象进行判定,判定这个对象是否被其他地方调用,如果没有被调用就标记成游离状态(说明是可以被回收的),那么就会被回收掉。
剩下的没被回收的,会进入到from区,并且age+1
当再次进行minor gc的时候,已经进入from区且还被继续调用的age继续+1,没被调用的就会被回收,新进来的和原有的从from区进入to区,to区变成from区,from区变成to区。
当这种from区和to区变换age>=15的时候,就会进入到老年代区
full gc
晋升到老年代的两种条件:
- age >= 15
- 在survivor区中,所有年龄的对象的所占空间的累加和大于survivor空间的一半,大于或等于该年龄的对象,都可以进入老年代。
如同上面所说,age>=15的会进入老年代区,但老年代区也是有空间限制的,当老年代空间满了的时候,就会触发full gc,会出现STW的现象(stop the work)。
面试题
为什么Java需要性能调优?
在有限的空间做无限的事情,如果没有Java性能调优,每创建一个对象都需要占用内存,用完之后没有引用的对象,不被回收,那么它还继续占用内存空间,直到占用了所有内存空间的时候,就会保错,内存不够;有Java性能调优,就会把无用的垃圾,及时回收,释放更多的内存空间,提供给其他使用。
为什么Java需要采用分代回收思想?
让更少的对象进入老年代,减少STW次数,提高性能