jvm jdk jre的关系
JDK包含了 JVM+JRE+编译工具+基础类库
JRE(java runtime environment [java运行时环境])包含了 JVM+基础类库
JVM组成部分
内存结构
1.程序计数器(寄存器)
作用
java源代码(右侧),不能直接运行,需要编译成左侧的jvm指令,再通过解释器解释成机器码,CPU识别机器码执行
程序计数器的左右就是记住下一条JVM指令执行的地址
CPU运行同时会把下一条JVM指令的地址放入程序计数器,第一条指令执行完成之后,解释器就会到程序计数器里面拿下一条指令的地址,根据地址找到jvm指令
在物理上,实现程序计数器是通过寄存器实现的
特点
-
线层私有
-
不存在内存溢出
2.栈(线程运行需要的内存空间)
2.1定义
-
每个线程运行时需要的内存,称为虚拟机栈
-
每个栈由多个栈帧组成,对应着每次方法调用所占用的空间
-
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
栈:可以理解为子弹夹,想往子弹夹里放子弹,需要从上方压入,称作压栈
当要使用栈数据时,只能先使用上面的数据,叫做弹栈
栈的特点,先进后出
线程运行需要的内存空间
如果有多个线程,就会有多个虚拟机栈,栈内由栈帧组成,一个栈帧对应的一次方法的调用,栈帧可以理解为每个方法运行时需要的内存
栈帧里面存放的方法的参数,局部变量,返回地址
问题辨析
-
垃圾回收是否涉及栈内存?
不需要,栈内存是一次次的方法调用所产生的栈帧内存,而栈帧内存每一次调用完成后都会被弹出栈,也就是会自动的回收掉,不需要垃圾回收来管理栈内存。
-
*栈内存分配越大越好吗?
-
并不是
在运行是可以用 -Xss 指定运用空间,但不是越大越好
栈内存划的越大,反而会让线程数变少栈内存的空间大了,只是方便了更多次的递归调用而不会增快运行效率,一般使用系统默认的栈内存大小就可以了
-
方法内的局部变量是否线程安全?
- 如果方法内局部变量没有逃离方法的作用范围,就是线程安全的
- 如果局部变量引用了对象,并且逃离了方法的作用范围,就需要考虑线程安全的问题
每一个线程对应一个栈,线程内每一次方法调用都会产生一个新的栈帧
判断一个变量是不是线程安全的,要看变量是不是方法的局部变量,有没有逃离方法的作用范围,逃离了作用范围,作为返回值也有可能被其它线程修改
2.2 栈内存溢出
java.lang.StackOverflowError
-
栈帧过多导致栈内存溢出
大部分出现在递归调用次数过多
-
栈帧过大导致栈内存溢出
比较少见,栈帧里面存放的是局部变量,方法参数,int占4b,而栈的默认大小大多为1M,不太可能超过栈内存,但也不排除
2.3 线程运行诊断
案例1:cpu占用过高
定位问题:
-
先用 top命令定位哪个进程对cpu的占用高
-
ps -H -eo pid,tid,%cpu |grep 进程id (用PS命令进一步定位是哪个线程引起的cpu占用过高)
-
jstack 进程id 查看当前进程下所有的java线程,通过第二步查到的线程id查询,第二步的线程id是10进制,第三步jstack输出是16进制,需要先行转换
jstack 可以根据线程id找到有问题的线程,进一步定位问题代码的行数
案例2:程序运行很长时间没有返回结果
可能是出现死锁