当前位置:网站首页>【JVM调优实战100例】02——虚拟机栈与本地方法栈调优五例
【JVM调优实战100例】02——虚拟机栈与本地方法栈调优五例
2022-07-02 17:23:00 【半旧518】
前 言
作者简介:半旧518,长跑型选手,立志坚持写10年博客,专注于java后端
专栏简介:实战案例驱动介绍JVM知识,教你用JVM排除故障、评估代码、优化性能
文章简介:介绍虚拟机栈与本地方法栈、教你排查5个常见的JVM虚拟机栈案例实战
3.虚拟机栈
3.1 虚拟机栈的介绍
栈:线程运行时需要的内存空间,一个栈中包含多个栈帧,栈帧是每个方法运行时需要的内存,一次方法调用就是一个栈帧。栈帧主要是用来存储局部变量,参数与返回地址(结束该方法后执行方法的地址)的。调用一个方法时,方法的栈帧入栈,当该方法执行结束,对应的栈帧(Frame)就会出栈。另外每个线程只能有一个活动栈帧,来对应当前正在执行的方法。
使用idea可以调试获取虚拟机栈信息。左下角的Frames就对应虚拟机栈。

思考
Q1:垃圾回收是否涉及栈内存
A1:垃圾回收不会涉及栈内存,因为栈的栈帧会随着方法调用而入栈,随着方法结束而出栈,无需进行垃圾回收。
Q2:栈内存越大越好吗?
A2:栈的大小可以进行设置。
线程栈越大则可以进行嵌套调用的方法层级越多,但是需要在合理区间,不是越大越好。因为计算机的物理内存是有限的,线程中栈的大小设置的越大,可以容纳的线程数就会越少(每个线程都有自己的栈)。一般采用系统默认的栈内存大小即可。
下图展示了设置栈大小的方法。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2b5iHGgI-1656678163012)(F:/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87/jvm/001.png)]](/img/0f/4fe1271594d9cf1fb35e767ab29363.png)

3.2 方法局部变量线程安全问题
局部变量是方法栈的私有变量,那么方法内的局部变量是不是一定是线程安全的呢?
先看这个例子。
// 多个线程同时执行
static void m1() {
int x = 0;
for (int j = 0; j < 500; j++) {
x++;
}
}
上面的例子是不会有线程安全问题的。因为每个线程都有独立的栈帧,存储独立的x。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0p3I5ZHq-1656678163013)(F:/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87/jvm/2.png)]
再看看下面的例子。
static void m2() {
StringBuilder sb = new StringBuilder();
sb.append("a");
sb.append("b");
sb.append("c");
}
答案依旧不会有安全问题,理由与上面的例子一样。接着看下下面的例子。
static void m3(StringBuilder sb ) {
sb = new StringBuilder();
sb.append("a");
sb.append("b");
sb.append("c");
}
上面例子其实是线程不安全的。因为sb 不是线程私有的。
总结:方法内的局部变量是否是线程安全?
- 如果方法内的局部变量没有逃离方法的作用范围,则是安全的。
- 如果是基本数据类型,则是安全的。
- 如果是对象类型数据,并且逃离了方法的作用范围,则线程不安全。参考代码demo1,不同线程栈的变量中存放的地址不会彼此干扰,但同一地址的值可以被不同的线程所修改。
3.3 虚拟机栈的内存溢出问题
导致栈内存溢出的情况:
- 入栈栈帧过多,如方法递归次数过多。
- 栈帧过大,这种情况很少出现,因为默认的栈帧大小是1M,可以存放空间很充足。
下面就是一个栈内存溢出的例子。
public class Demo02 {
private static int count;
public static void main(String[] args) {
m1();
}
static void m1() {
count ++;
m1();
}
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ySVgEBAZ-1656678163013)(F:/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87/jvm/3.png)]](/img/a4/42f107bf39c5087f8f053e4e2b60bb.png)
值得注意的是,有时候并不是我们自己写的代码导致了栈的内存溢出问题,而是错误使用第三方库的代码时导致了内存溢出问题。
/** * json 数据转换 */
public class Demo03 {
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;
}
}
出现Infinite recursion (StackOverflowError)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D0nhUdKn-1656678163014)(F:/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87/jvm/4.png)]](/img/fb/71d0b65d5b1791eb3ceabe17266a81.png)
解决方法:添加@JsonIgnore注解。
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;
}
}
3.4 虚拟机栈的cpu占用问题
下面分析两个栈相关的案例。
编译运行下面代码。
/** * 演示 cpu 占用过高 */
public class Demo04 {
public static void main(String[] args) {
new Thread(null, () -> {
System.out.println("1...");
while(true) {
}
}, "thread1").start();
new Thread(null, () -> {
System.out.println("2...");
try {
Thread.sleep(1000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread2").start();
new Thread(null, () -> {
System.out.println("3...");
try {
Thread.sleep(1000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread3").start();
}
}
linux下使用nohub让进程在后台运行,将直接返回线程id。
nohub java Demo04 &
使用top来显示cpu被进程占用的情况(笔者环境是windows,直接用的任务管理器,后不再赘述)。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-057EdCHr-1656678163014)(F:/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87/jvm/5.png)]](/img/c2/92b03fe7211c7d9323cd9bb8c64dde.png)
定位到占用过高cpu的进程后,使用ps H -eo pid tid %cpu | grep xxx(进程id)来查看具体是哪个线程导致的问题。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-40Z3VkwN-1656678163014)(F:/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87/jvm/6.png)]](/img/ac/0206cdbc175525b04a7352ed971fd9.png)
最后使用jstack xxx(进程id)查看进程所有线程对应的id及引起问题的源码行数。注意使用第二步得到线程编号是十进制,而jstack中的线程编号是16进制,需要进行必要的进制换算。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-unEamyNf-1656678163015)(F:/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87/jvm/image-20220629205806512.png)]](/img/59/6c776e0607a52962b72fbea2e64c8e.png)
32655换算成16进制就是7f99,因此有问题的线程就是下面的线程。其线程状态是runnable,说明它一直在运行,占用了cpu。并且还可以根据堆栈信息定位到具体的代码行数。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XA2dCBbr-1656678163015)(F:/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87/jvm/image-20220629210212804.png)]](/img/78/aaf7a6478fba1272dc4a522612493e.png)
对应到源代码,我们就排查出了导致cpu占用过高的原因了。
while(true) {
}
3.5 线程死锁的排查
编写如下代码。
/** * 演示线程死锁 */
class A{
};
class B{
};
public class Demo05 {
static A a = new A();
static B b = new B();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (a) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println("我获得了 a 和 b");
}
}
}).start();
Thread.sleep(1000);
new Thread(()->{
synchronized (b) {
synchronized (a) {
System.out.println("我获得了 a 和 b");
}
}
}).start();
}
}
linux下使用nohub让进程在后台运行,将直接返回线程id。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fT5eRoSN-1656678163019)(F:/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87/jvm/image-20220630203443201.png)]](/img/25/ffe8e6e3a846b7ac16b0e5d6b58efe.png)
windows上可以直接使用java运行,在任务管理器中找到该进程,可以看到进行id是15288。(linux环境有很多开发命令,笔者环境是windows,结合了git batsh使用linux的部分命令,后不再赘述)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GQNSSZnU-1656678163019)(F:/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87/jvm/image-20220630203131063.png)]](/img/2b/3fc45b45df49445aee135897861e3a.png)
执行jsatck命令,可以看到如下输出
F:\资料 解密JVM\代码\jvm\src\cn\itcast\jvm\t1\stack>jstack 15288
2022-06-30 20:30:08
Full thread dump Java HotSpot(TM) Client VM (25.301-b09 mixed mode):
...
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x01199894 (object 0x04e9fb40, a A),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x0119c1b4 (object 0x04ea0c28, a B),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at Demo05.lambda$main$1(Demo05.java:28)
- waiting to lock <0x04e9fb40> (a A)
- locked <0x04ea0c28> (a B)
at Demo05$$Lambda$2/1503869.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at Demo05.lambda$main$0(Demo05.java:20)
- waiting to lock <0x04ea0c28> (a B)
- locked <0x04e9fb40> (a A)
at Demo05$$Lambda$1/28568555.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
可以很清楚看到死锁信息被定位了,在Demo05.java:28,20行出现了死锁。再去代码处分析,发现线程1,2出现了互锁。并且这个互酸信息其实也被打印出来了,Thread-1,拥有B等待A,Thread-2,拥有A等待B。
5.本地方法栈
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o64VBVpG-1656678163019)(F:/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87/jvm/image-20220630204811251.png)]](/img/c1/fb4f465303107cfbd53fdfc31cfbc9.png)
本地方法是非java语言(c/c++)编写的直接与计算机操作系统底层API交互的方法,java虚拟机在调用本地方法时,通过本地方法栈给本地方法提供内存空间。
边栏推荐
- 材质UV遮罩的技巧
- Leetcode interview question 16.11 Diving board
- 电商系统中常见的 9 大坑,你踩过没?
- 工业软件讲堂-三维CAD设计软件的核心技术解析----讲坛第二次讲座
- AI开发调试系列第二弹:多机分布式调测探索之旅
- 产品经理应具备的能力
- 迷你高尔夫球场:伦敦休闲旅游好去处
- 300+ documents! This article explains the latest progress of multimodal learning based on transformer
- Google's official response: we have not given up tensorflow and will develop side by side with Jax in the future
- Mini Golf Course: a good place for leisure and tourism in London
猜你喜欢

Distance measurement - Jaccard distance

How to clean up discarded PVs and their corresponding folders
![[daily question] the next day](/img/8a/18329bd9b4a3a4445c8fbbc1ce562b.png)
[daily question] the next day

新加坡暑假旅游攻略:一天玩转新加坡圣淘沙岛

Redis(7)----数据库与过期键

The text editor hopes to mark the wrong sentences in red, and the text editor uses markdown

Thoroughly understand the point cloud processing tutorial based on open3d!

新加坡暑假旅遊攻略:一天玩轉新加坡聖淘沙島

Redis (6) -- object and data structure
![27: Chapter 3: develop Passport Service: 10: [registration / login] interface: after the registration / login is OK, save the user session information (uid, utoken) to redis and cookies; (one main poi](/img/b9/2066a13b160252114c2881007094f8.png)
27: Chapter 3: develop Passport Service: 10: [registration / login] interface: after the registration / login is OK, save the user session information (uid, utoken) to redis and cookies; (one main poi
随机推荐
Meal card hdu2546
How to write controller layer code gracefully?
C语言中函数参数传递的三种方式
Google's official response: we have not given up tensorflow and will develop side by side with Jax in the future
RTE11- 中断解耦功能
Unity learning shader notes [81] simple color adjustment post-processing (brightness, saturation, contrast)
options should NOT have additional properties
消除IBM P750小机上的黄色报警灯[通俗易懂]
Radian to angle, angle to radian in MATLAB
UML class diagram
sql训练2
Redis(6)----对象与数据结构
In early summer, Kaiyuan magic changed an electric mosquito racket with killing sound effect!
Troubleshooting ideas that can solve 80% of faults
R语言使用epiDisplay包的cox.display函数获取cox回归模型汇总统计信息(风险率HR、调整风险率及其置信区间、模型系数的t检验的p值、Wald检验的p值和似然比检验的p值)、汇总统计
How to clean up discarded PVs and their corresponding folders
Esp32-c3 introductory tutorial question ⑩ - error: implicit declaration of function 'ESP_ blufi_ close‘;
在支付宝账户上买基金安全吗
Deep neural network Summary
电商系统中常见的 9 大坑,你踩过没?