本文笔记参考自小滴课堂和传智播客JVM学习教程整合而来
Java Virtual Machine,JAVA程序的运行环境(JAVA二进制字节码的运行环境)
注:我们笔记所使用的的是HotSpot 版本
JVM JRE JDK的区别:
学习顺序如下图:(由简到难)
Program Counter Register
程序计数器用于保存JVM中下一条所要执行的指令的地址
0:getstatic #20// PrintStream out = System.out;1:astore_1// --2:aload_1// out.println(1);3:iconst_1// --4:invokevirtual #26// --5:aload_1// out.println(2);6:iconst_2// --7:invokevirtual #26// --8:aload_1// out.println(3);9:iconst_3// --10:invokevirtual #26// --11:aload_1// out.println(4);12:iconst_4// --13:invokevirtual #26// --14:aload_1// out.println(5);15:iconst_5// --16:invokevirtual #26// --return
Java指令执行流程:
每一条二进制字节码(JVM指令) 通过解释器 转换成机器码 然后 就可以被CPU 执行了!
当解释器 将一条jvm 指令转换成机器码后 其会 向程序计数器 递交 下一条 jvm 指令的执行地址!
程序计数器在硬件层面 其实是通过寄存器 实现的!
所以程序计数器的作用就是:用于保存JVM中下一条所要执行的指令的地址!
Java Virtual Machine Stacks
代码
/** * @Auther: csp1999 * @Date: 2020/11/10/11:36 * @Description: 演示栈帧 */publicclassDemo01{publicstaticvoidmain(String[] args){methodA();}privatestaticvoidmethodA(){methodB(1,2);}privatestaticintmethodB(int a,int b){int c= a+ b;return c;}}
我们打断点来Debug 一下看一下方法执行的流程:
接这往下走,使方法B执行完毕:
然后方法A执行完毕,其对应的栈帧出栈,main方法对应的栈帧为活动栈帧;最后main执行完毕 栈帧出栈,虚拟机栈为空,代码运行结束!
1.垃圾回收是否涉及栈内存?
2.栈内存的分配越大越好吗?
3.方法内的局部变量是否是线程安全的?
从图中得出:局部变量如果是静态的可以被多个线程共享,那么就存在线程安全问题。如果是非静态的只存在于某个方法作用范围内,被线程私有,那么就是线程安全的!
看一个案例:
/** * 局部变量的线程安全问题 */publicclassDemo02{publicstaticvoidmain(String[] args){// main 函数主线程 StringBuilder sb=newStringBuilder(); sb.append(4); sb.append(5); sb.append(6);newThread(()->{// Thread新创建的线程m2(sb);}).start();}publicstaticvoidm1(){// sb 作为方法m1()内部的局部变量,是线程私有的 ---> 线程安全 StringBuilder sb=newStringBuilder(); sb.append(1); sb.append(2); sb.append(3); System.out.println(sb.toString());}publicstaticvoidm2(StringBuilder sb){// sb 作为方法m2()外部的传递来的参数,sb 不在方法m2()的作用范围内// 不是线程私有的 ---> 非线程安全 sb.append(1); sb.append(2); sb.append(3); System.out.println(sb.toString());}publicstatic StringBuilderm3(){// sb 作为方法m3()内部的局部变量,是线程私有的 StringBuilder sb=newStringBuilder();// sb 为引用类型的变量 sb.append(1); sb.append(2); sb.append(3);return sb;// 然而方法m3()将sb返回,sb逃离了方法m3()的作用范围,且sb是引用类型的变量// 其他线程也可以拿到该变量的 ---> 非线程安全// 如果sb是非引用类型,即基本类型(int/char/float...)变量的话,逃离m3()作用范围后,则不会存在线程安全}}
该面试题答案:
Java.lang.stackOverflowError 栈内存溢出
发生原因
举2个案例:
案例1:
/** * 演示栈内存溢出 java.lang.StackOverflowError * -Xss256k 可以通过栈内存参数 设置栈内存大小 */publicclassDemo03{privatestaticint count;publicstaticvoidmain(String[] args){try{method1();}catch(Throwable e){ e.printStackTrace(); System.out.println(count);}}privatestaticvoidmethod1(){ count++;// 统计栈帧个数method1();// 方法无限递归,不断产生栈帧 到虚拟机栈}} 最后输出结果: java.lang.StackOverflowError at com.haust.jvm_study.demo.Demo03.method1(Demo03.java:21)......39317// 栈帧个数,不同的虚拟机大小能存放的栈帧数量不一样
我们可以通过修改参数来指定虚拟机栈内存大小
当我们将虚拟机栈内存缩小到指定的256k的时候再运行Demo03后,会得到其栈内最大栈帧数为:3816 远小于原来的39317!
案例2:
/** * 两个类之间的循环引用问题,导致的栈溢出 * * 解决方案:打断循环,即在员工emp 中忽略其dept属性,放置递归互相调用 */publicclassDemo04{publicstaticvoidmain(String[] args)throws JsonProcessingException{ Dept d=newDept(); d.setName("Market"); Emp e1=newEmp(); e1.setName("csp"); e1.setDept(d); Emp e2=newEmp(); e2.setName("hzw"); e2.setDept(d); d.setEmps(Arrays.asList(e1, e2));// 输出结果:{"name":"Market","emps":[{"name":"csp"},{"name":"hzw"}]} ObjectMapper mapper=newObjectMapper();// 要导入jackson包 System.out.println(mapper.writeValueAsString(d));}}/** * 员工 */classEmp{private String name;@JsonIgnore// 忽略该属性:为啥呢?我们来分析一下!/** * 如果我们不忽略掉员工对象中的部门属性 * System.out.println(mapper.writeValueAsString(d)); * 会出现下面的结果: * { * "name":"Market","emps": * [c * {"name":"csp",dept:{name:'xxx',emps:'...'}}, * ... * ] * } * 也就是说,输出结果中,部门对象dept的json串中包含员工对象emp, * 而员工对象emp 中又包含dept,这样互相包含就无线递归下去,json串越来越长... * 直到栈溢出! */private Dept dept;public StringgetName(){return name;}publicvoidsetName(String name){this.name= name;}public DeptgetDept(){return dept;}publicvoidsetDept(Dept dept){this.dept= dept;}}/** * 部门 */classDept{private String name;private List<Emp> emps;public StringgetName(){return name;}publicvoidsetName(String name){this.name= name;}public List<Emp>getEmps(){return emps;}publicvoidsetEmps(List<Emp> emps){this.emps= emps;}}
案例1:CPU占用过高
Linux环境下运行某些程序的时候,可能导致CPU的占用过高,这时需要定位占用CPU过高的线程
top命令,查看是哪个进程占用CPU过高
ps H -eo pid, tid(线程id), %cpu | grep 刚才通过top查到的进程号 通过ps命令进一步查看具体是哪个线程占用CPU过高!
jstack 进程id 通过查看进程中的线程的nid,刚才通过ps命令看到的tid来对比定位,注意jstack查找出的线程id是16进制的,需要转换
我们可以看到上图中的thread1 线程一直在运行(runnable)中,说明就是它占用了较高的CPU内存;
一些带有native 关键字的方法就是需要JAVA去调用本地的C或者C++方法,因为JAVA有时候没法直接和操作系统底层交互,所以需要用到本地方法!
如图: