- 讲到栈帧的时候不了解JVM指令的话也不会知道栈帧是怎么用的
- jvm指令集200多条,常用的就那几条,只说常用的和一些难的
一道面试题
- 正确答案是8
- 假如变成++i答案是多少???
public static void main(String[] args) {
int i = 8;
i = i++;
//i = ++i;
System.out.println(i);
}
参考资料:JVMS
- 如果觉得某些书或者某些博客说的有问题的话就去找oracle上的英文文档去看(权威)
详细区域
PC(Program Counter)
- 指令寄存器,存放指令位置
- 虚拟机的运行类似于这样的循环
- 从PC取值
- 找指令
- 找完指令之后执行
- 执行完之后去取下一条
- 什么时候结束,什么时候为止
- 每一个java虚拟机线程都有他自己的PC寄存器—->记录下一个指令在哪
Heap堆内存
JVM stacks
- 每一个线程对应一个栈
- 每一个方法对应一个栈帧
- JVM虚拟机里所管理的栈
- 栈里面装的是一个一个的栈帧Frame
- 重点
- 每一个JVM线程都有一个私人的JVM stacks,这个栈和线程一起被创建出来
- 栈里面装的是栈帧
method area
- 方法区
- 里面装的各种各样的class与常量池(runtime constant pool)内容
- 有很多细节
- 重点
- 方法区是所有线程所共享的
- 里面装的是每一个class里面的结构
- 容易和永久区、元空间混淆
- 方法区是抽象的概念,是一个jvm规范
- 永久区和元空间是具体的jvm实现,是具体的方法区的实现,是不同版本的method area的实现
- 在jdk1.8之前他是永久区,1.8之后就变成了元空间
Run-Time Constant Pool
- 字节码文件中有一个常量池,在字节码文件运行的时候这个常量池就扔在Run-Time Constant Pool中
- A run-time constant pool is a per-class or per-interface run-time representation of the constant pool table in a class file
- 运行时常量池是每类或每个接口在运行时类文件中常量池表的表示
Native method stacks
- 本地方法C/C++
- java调用了jni方法,即调用了java虚拟机内部的某些用C/C++写的方法
- 等同于java自身的栈,一般我们牵扯不到,没办法调优,没办法管理他,一般不去管理他
Direct Memory
- java1.4之后增加的内容:直接内存
- NIO的内容
- 一般情况下,所有的内存都归JVM直接管理,为了增加IO效率,在1.4之后增加了直接内存
- 从JVM内部可以访问操作系统管理的内存
- 用户空间可以直接访问内核空间的一些内存—->这些内存一般是在写网络程序的时候,写IO的时候
- 不归JVM虚拟机直接管,归操作系统管
- 原来的IO中,要访问网络传过来的数据的时候:现在内核空间开辟一块内存,将传过来的数据放到内核空间中去,当jvm要用的时候直接将内核空间的这块内存拷贝到jvm空间中去,中间有拷贝的过程,来一个数据拷贝一份,这样效率比较低
- jdk1.4之后添加了NIO,可以直接使用内核空间的内存了,省了拷贝的过程了,直接就可以访问内核空间里的网络上传过来的这块缓存区的内容了
- JSU Copy零拷贝(zero copy—->epoll和poll)的意思就是增加了Direct Memory,可以直接去访问内核空间的那块内存
线程共享区域
- 线程不能共用一个PC,因为线程会进行切换,假如没有自己的PC,那么切回去的时候之前线程执行到的位置会丢失,就不能再继续往下执行了
几点
- 非静态方法中的局部变量表中有this,而静态没有
- 并且也是没有父类的super,因为调用父类方法,直接通过一个连接,直接就指向父类的常量池里面去了(所以没有super)
- 面试最好的方法是那台电脑给个任务让你干活,在规定时间内能干就来,不能干就走
- invokevirtual是自带多态的,调用invokevirtual的时候要从栈中弹出对象,弹出什么对象,就调用什么对象的方法
- invokestatic,java内部实现复杂,不同方法可以进行不同的优化;对于二进制码这个级别,调静态方法必须使用这条指令,这条指令的优化方式是虚拟机的事
- invokespecial是用于那些可以直接定位,不需要多态的方法,比如构造方法、private修饰的方法(final方法不是invokespecial的,而是invokevirtual的)
- invokeinterface表示多态情况下,用接口类型调用方法!!!
- (最难的指令)invokedynamic(1.7之前是没有的,1.7才刚加进来)—-> lambda表达式或者反射或者其他动态语言:scala、kotlin……或者CGLIB或者ASM或者动态产生的class会用到的指令 的时候会有这条指令,因为java支持动态语言了—->意味着可以动态产生很多class,在运行时产生的class,而不是之前写好的并且编译好的,运行时才产生了很多class
- 后面跟两个参数:bootstrapmethod(记录了一系列的方法:可能是动态产生的类的方法)和方法的签名
- 函数式接口,该接口中只有一个方法,所以任何一个具体的方法都可以认为是这个接口的一个对象
- 将函数的具体实现赋值给接口类型,其实n就是一个函数指针(函数指针的概念),这个函数指针指向了这个函数—->java的内部实现是内部产生了一个class并且产生了一个class的对象
- 用lambda表达式的时候会产生一个lambda的内部类,然后还有一个内部类—->内部类的内部类(每有一个lambda表达式就会有**内部类)—->lambda表达式的语法的问题(内部类的匿名内部类**)
- jvm没有纯粹的函数
- 接口不加static也可以,老师刚开始写的有问题(**因为是函数式接口**)
- 每次产生一个新的内部类
- 在循环中用lambda表达式或者反射的情况下,就会产生很多class,会放到methodArea(1.8之前的永久代有一个巨大的bug—->**FGC不回收???**)(1.8之后的metaSpace会触发FGC会自动清理,并且字符串常量池位于堆中了)
- 小于1.8时会产生OOM,大于1.8时会触发清理,清理不掉也会产生OOM!!!