学习黑马视频:01_什么是jvm_哔哩哔哩_bilibili
一、JVM内存结构
程序计数器
虚拟机栈
本地方法栈
堆
方法区
程序计数器、栈、本地方法栈,都是线程私有的。堆、方法区是线程共享的区域。
1. 虚拟机栈(JVM Stacks)
1)定义
- 每个线程运行时所需要的内存,称为虚拟机栈
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
问题辨析
1. 垃圾回收是否涉及栈内存?
不会。栈内存是方法调用产生的,方法调用结束后会弹出栈。
2. 栈内存分配越大越好吗?
不是。因为物理内存是一定的,栈内存越大,可以支持更多的递归调用,但是可执行的线程数就会越少。
3. 方法内的局部变量是否线程安全
- 如果方法内部的变量没有逃离方法的作用访问,它是线程安全的;(如果是对象,才需要考虑此问题;如果是基本类型变量,是可以保证它是线程安全的)
- 如果是局部变量引用了对象,并逃离了方法的访问,那就要考虑线程安全问题。
总之,如果变量是线程私有的,就不用考虑线程安全问题;如果是共享的,如加了static之后,就需要考虑线程安全问题。
public class main1 {
public static void main(String[] args) {
}
//下面各个方法会不会造成线程安全问题?
//不会
public static void m1() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
//会,可能会有其他线程使用这个对象
public static void m2(StringBuilder sb) {
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
//会,其他线程可能会拿到这个线程的引用
public static StringBuilder m3() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
return sb;
}
}
2)栈内存溢出
Java.lang.stackOverflowError 栈内存溢出
导致栈内存溢出的情况:栈帧过大、过多、或者第三方类库操作,都有可能造成栈内存溢出
设置虚拟机栈内存大小:
package cn.itcast.jvm.t1.stack;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.List;
/**
* json 数据转换
*/
public class Demo1_19 {
public static void main(String[] args) throws JsonProcessingException {
Dept d = new Dept();
d.setName("Market");
Emp e1 = new Emp();
e1.setName("zhang");
e1.setDept(d);
Emp e2 = new Emp();
e2.setName("li");
e2.setDept(d);
d.setEmps(Arrays.asList(e1, e2));
// { name: 'Market', emps: [{ name:'zhang', dept:{ name:'', emps: [ {}]} },] }
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(d));
}
}
class Emp {
private String name;
//使用该注解避免循环调用问题,转换时忽略这个属性————只通过部门去关联员工,员工不再关联部门了
@JsonIgnore
private Dept dept;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
class Dept {
private String name;
private List<Emp> emps;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Emp> getEmps() {
return emps;
}
public void setEmps(List<Emp> emps) {
this.emps = emps;
}
}
3) 线程运行诊断
案例1:CPU占用过高
Linux环境下运行某些程序的时候,可能导致CPU的占用过高,这时需要定位占用CPU过高的线程第一步:用top命令定位哪个进程对cpu的占用过高;
通过top命令可以看到,PID为32655的进程编号占了CPU的99.7%,那如何进一步定位问题呢?
第二步:用ps命令进一步定位是哪个线程引起的cpu占用过高;(如下图,线程32665有问题)
ps H -eo pid,tid,%cpu
ps H -eo pid,tid,%cpu | grep 进程id ,刚才通过top查到的进程号
通过下面这个命令发现占用CPU过高的线程编号为32665,该线程编号为十进制的,换算为十六进制为7f99;
第三步:jstack 进程id
,可以根据线程id找到有问题的线程,进一步定位问题代码的源码行号;
案例2:程序运行很长时间没有结果
2. 本地方法栈(Native Method Stacks)
定义:实际上就是在java虚拟机调用一些本地方法时,需要给这些本地方法提供的内存空间。本地方法运行时,使用的内存就叫本地方法栈。
作用:给本地方法的运行提供内存空间。
本地方法:指那些不是由java代码编写的方法,因为java代码是有一定限制的,有的时候它不能直接与操作系统底层打交道,所以就需要用C或者C++语言编写的本地方法来真正与操作系统打交道。java代码可以间接的通过本地方法来调用到底层的一些功能。
这样的方法多吗?当然!不管是在一些java类库,还是在执行引擎,它们都会去调用这些本地方法。比如,在Object类的方法中,clone()方法是带有native的,这种native方法它是没有方法实现的,方法实现都是通过c或者c++语言编写的,java代码通过间接的去调用c或者c++的方法实现。
3. 堆(Heap)
前面讲的程序计数器、栈、本地方法栈,都是线程私有的。堆、方法区是线程共享的区域。
1)定义
通过new关键字创建的对象,都会使用堆内存。
2)特点
- 堆中的对象是线程共享的,堆中的对象一般都需要考虑线程安全问题(有例外);(前面说的虚拟机栈中的局部变量只要不逃逸出方法的作用范围,都是线程私有的,都是线程安全的);
- 垃圾回收机制;(Heap中不再被引用的对象,就会被当作垃圾进行回收,以释放堆内存)。
3)堆内存溢出
堆空间大小设置:使用-Xmx参数
所以,这里需要注意,当内存足够大时不太容易暴露内存溢出的问题,随着时间的累积有可能会导致内存溢出。但是,有可能你运行了很短的一段时间发现它没问题。所以,排查这种堆内存问题,最好把运行内存设的稍微小一些,这样会比较早的暴露堆内存溢出的问题。
4)堆内存诊断
1. jps 工具
查看当前系统中有哪些 java 进程
2. jmap 工具
查看堆内存占用情况 jmap - heap 进程id
注意:它只能查询某个时刻堆内存的使用情况。如果想连续监测,需要使用下面这个工具。
3. jconsole 工具
图形界面的,多功能的监测工具,可以连续监测
除了监测堆内存,还可以监测CPU,线程。
演示上面几个工具的使用:
1. 演示jmap工具的使用
jmap -heap 进程id ——检查该进程堆内存的占用情况;
2. jconsole 工具的使用