云计算百科
云计算领域专业知识百科平台

【JVM入门学习 | 01】内存结构 part01

1.程序计数器

        首先,根据以下图片我们可以观察到程序计数器在内存结构中占有比较重要的地位

     

1.1定义

        程序计数器(Program Counter,简称PC)是计算机处理器中的一个重要寄存器,用于存储当前正在执行的指令地址或下一条即将执行的指令地址。其核心功能是确保指令按正确顺序执行,并支持程序流程控制(如跳转、循环等)。

1.2作用   

        下面,我们根据以下图片来分析程序计数器作用

        右侧注释代码为Java源代码,源代码并不会直接执行,而是会编译为左侧的二进制字节码(JVM指令,这些指令对所有操作系统一致),这些指令由由解释器解释成为机器码,然后交由CPU执行

        而程序计数器的作用是记住下一条JVM指令的执行地址(二进制字节码旁边的数字),解释器从程序计数器得到下一条JVM指令的执行地址并去执行。这是非常重要的,如果没有程序计数器,那么解释器就不知道该执行什么命令了。

1.3特点

        1.是线程私有的

        2.不会存在内存溢出的情况(在JVM规范中唯一一个)

2.虚拟机栈

2.1定义

        Java Virtual Machine Stacks (Java 虚拟机栈)
                每个线程运行时所需要的内存,称为虚拟机栈
                每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
                每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

问题辨析

        1. 垃圾回收是否涉及栈内存?  垃圾回收不涉及栈内存 (原因可自行翻阅资料)
        2. 栈内存分配越大越好吗?      不是   (原因可自行翻阅资料)
        3. 方法内的局部变量是否线程安全?
                如果方法内局部变量没有逃离方法的作用范围,它是线程安全的
                如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全

2.2栈内存溢出

        栈内存溢出

                栈帧过多导致栈内存溢出(Stack Overflow Error)

                例如:无线递归导致栈帧过多

public class StackOverflowExample {
public static void recursiveMethod() {
recursiveMethod(); // 无限递归调用
}

public static void main(String[] args) {
recursiveMethod();
}
}

                可能有小伙伴认为以上代码这么愚蠢,我怎么可能出现这种情况呢?当然,所以现在我  们初步了解了栈帧过多导致栈内存溢出,我们看下一种情况:栈内存溢出通常发生在JSON序列化或反序列化过程中,尤其是当对象之间存在循环引用时。

                观察以下代码,我们可以分析出:

                Department(后称部门)和Employee(后称员工)互相引用,现在我们有一个部门对象IT,和一个员工对象John,并且给John设置了部门IT,然后创建了部门下的员工John,通过ObjectMapper类中的writeValueAsString方法看一下转换结果,最后出现了无限递归的报错,有兴趣的小伙伴可以自行尝试。

                分析输出结果:

         { name:"IT",employees=[ { name:"John",department:"{ name:IT , employees:[ {…. } ]  }"} ]} 

                相信看到以上这一串结果,刚才不明白的小伙伴以及豁然开朗了,接下来我们来看如何解决该问题

public class Department {
private String name;
private List<Employee> employees;

// 构造函数、getter和setter
}

public class Employee {
private String name;
private Department department;

// 构造函数、getter和setter
}

ObjectMapper mapper = new ObjectMapper();
Department department = new Department("IT");
Employee employee = new Employee("John", department);
department.setEmployees(Arrays.asList(employee));

// 这会导致栈溢出
String json = mapper.writeValueAsString(department);

解决方案        

        解决以上问题,我们可以使用@JsonIgnore注解,忽略其中一个方向的引用:

public class Employee {
private String name;

@JsonIgnore
private Department department;

// 其他代码
}

   栈帧过大导致栈内存溢出(通常情况下不会出现)

public class StackOverflowByLargeFrame {
// 每次递归调用时在栈帧中分配的大数组(约1MB)
private static void recursiveMethod(int depth) {
byte[] largeArray = new byte[1024 * 1024]; // 1MB 的局部变量
System.out.println("Depth: " + depth + " – Frame size: ~1MB");
recursiveMethod(depth + 1);
}

public static void main(String[] args) {
try {
recursiveMethod(1);
} catch (StackOverflowError e) {
System.err.println("Stack overflow occurred!");
e.printStackTrace();
}
}
}

2.3线程运行诊断

        案例1: cpu 占用过多
          定位
          1.用top命令定位哪个进程对cpu的占用过高
          2.ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
          3.jstack 进程id
                   可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号(根据实际情况来)

        案例2:程序运行很长时间没有结果

           jstack 进程id,可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号(根据实际情况来)

public class InfiniteLoopExample {
public static void main(String[] args) {
// 无限循环导致程序永不结束
while (true) {
// 空循环体
}
}
}

赞(0)
未经允许不得转载:网硕互联帮助中心 » 【JVM入门学习 | 01】内存结构 part01
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!