本文已参与「新人创作礼」活动,一起开启掘金创作之路。
[TOC]
什么是JVM
定义
Java Virtual Machine - Java 程序运行环境
优点
- 跨平台
- 自动内存管理,垃圾回收功能
比较
JDK
- JRE
+ 编译工具
JRE
- JVM
+ 基础类库
JVM
内存结构
程序计数器 - Program Counter Register
作用
找到下一条jvm指令的执行地址
从字节码的执行原理来看,单线程的情况下程序计数器是可有可无的。因为即使没有程序计数器的情况下,程序会按照指令顺序执行下去,即使遇到了分支跳转这样的流程也会按照跳转到指定的指令处继续顺序执行下去,是完全能够保证执行顺序的。
但是现实中程序往往是多线程协作完成任务的。JVM的多线程是通过CPU时间片轮转来实现的,某个线程在执行的过程中可能会因为时间片耗尽而挂起。当它再次获取时间片时,需要从挂起的地方继续执行。在JVM中,通过程序计数器来记录程序的字节码执行位置。程序计数器具有线程隔离性,每个线程拥有自己的程序计数器
特点
- 程序计数器具有线程隔离性,每个线程拥有自己的程序计数器,跟线程共存亡
- 程序计数器占用的内存空间非常小,可以忽略不计
- 程序计数器是java虚拟机规范中唯一一个没有规定任何OutofMemeryError的区域
- 程序执行的时候,程序计数器是有值的,其记录的是程序正在执行的字节码的地址
- 执行native本地方法时,程序计数器的值为空。原因是native方法是java通过jni调用本地C/C++库来实现,非java字节码实现,所以无法统计
虚拟机栈 - Java Virtual Machine Stacks
定义
- 每个线程运行时所需要的内存,称为虚拟机栈
- 每个栈由多个栈帧组成,对应着每次方法调用所需要的内存
- 每个线程只有一个活动栈帧,对应着当前正在执行的方法
问题
-
垃圾回收是否涉及栈内存?
不会涉及。栈内存是对应着一个程序执行过程的方法调用,执行过程中,栈帧随着方法的执行结束而弹出,知道程序执行完毕,栈帧全部弹出。不需要垃圾回收来管理栈内存。
-
栈内存分配越大越好吗?
-Xss 属性设置栈内存大小。
机器的物理内存是固定的,栈内存分配越大,线程数越少。分配大了,除了可以使用更多的方法递归,没有其他作用。
-
方法内的局部变量是否线程安全?
不一定
- 方法内部的局部变量没有逃离方法的作用范围时,是线程安全的
- 如果局部变量是基本类型,线程安全。
- 如果局部变量引用的是对象,且是多线程共享的,需要考虑线程安全问题。
栈内存溢出
-
栈帧过多导致栈内存溢出
方法递归如果没有一个合适的退出条件,容易造成栈帧过多导致栈内存溢出
-
栈帧过大导致栈内存溢出(例如:对象嵌套引用时转换成JSON)
线程运行诊断
-
CPU占用过多
- 用
top
命令定位占用高的进程 - 利用
ps H -eo pid, tid, %cpu | grep 进程id
定位哪个线程占用高。 jstack 进程id
定位有问题的代码行数
- 用
-
程序长时间运行没有结果
jstack 进程id
定位有问题的代码行数
本地方法栈 - Native Method Stack
本地方法运行时所需要的内存空间。
堆 - Heap
定义
通过new
关键字创建的对象,都会使用堆内存
特点
- 线程共享,需考虑线程安全问题。
- 有垃圾回收机制
堆内存溢出
-Xmx属性设置对内存大小
一直在创建对象且因为被使用无法回收,造成堆内存溢出。
堆内存诊断
-
jps
- 查看当前系统中有哪些java进程 -
jmap
- 查看对内存占用情况jmap -heap 进程id
-
jconsole
- 图形界面,多功能的检测工具,可以连续检测 -
jvisualvm
方法区 - Method Area
方法区是JVM中所有线程共享的区域。存储每个类的结构:运行时常量池、成员变量、方法数据等。
方法区在虚拟机启动时就创建。
方法区在申请内存时如果内存不足了,会抛出OutOfMemoryError
组成与实现 jdk8以前和jdk8以后方法区的区别,其中最主要的区别是8以后将方法区转移到本地内存中,且常量池分为运行时常量池和字符串常量池;且字符串常量池被留在内存中的堆中。