JVM第一天 -(一)内存结构

1. 程序计数器

2. 虚拟机栈

3. 本地方法栈

4. 堆

5. 方法区

1. 程序计数器

1.1定义

Program Counter Register 程序计数器(寄存器) 物理上是通过寄存器实现的

作用

● 记住下一条jvm指令的执行地址

特点

● 是线程私有的,每个线程都有自己的程序计数器

● 不会存在内存溢出

1.2作用

        例如下面代码:getstatic指令交给解释器,解释器将其变成机器码,再交给CPU运行。与此同时,把下一条指令的地址(这里是astore_1的地址3),放入程序计数器。第一条指令执行完以后,解释器从程序计数器里根据3取到下一条指令,再重复刚才的过程。这就是程序计数器 记住下一条jvm指令的执行地址。

jvm指令                    #数字(表示引用常量池中的#数字项)                            java源代码

(即下面一行行的代码)

(前面的数字可以看作是指令的执行地址)

0: getstatic      #20        // PrintStream out = System.out;
3: astore_1                  // --
4: aload_1                   // out.println(1);
5: iconst_1                  // --
6: invokevirtual #26         // --
9: aload_1                   // out.println(2);
10: iconst_2                 // --
11: invokevirtual #26        // --
14: aload_1                  // out.println(3);
15: iconst_3                 // --
16: invokevirtual #26        // --
19: aload_1                  // out.println(4);
20: iconst_4                 // --
21: invokevirtual #26        // --
24: aload_1                  // out.println(5);
25: iconst_5                 // --
26: invokevirtual #26        // --
29: return

 第一条jvm指令执行:

第二条jvm指令执行:

解释器把每条指令解释成机器码,机器码再到CPU上运行(CPU只认识机器码。)

2.虚拟机栈

2.1 定义

Java Virtual Machine Stacks (Java 虚拟机栈)

■ 每个线程运行时所需要的内存,称为虚拟机栈

■ 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存

■ 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法(栈顶部的方法)

问题辨析

1. 垃圾回收是否涉及栈内存?

        不涉及,栈帧内存在每一次方法调用完成后,都会弹出栈,被自动回收掉。

2. 栈内存分配越大越好吗?

       不是,栈的内存越大,线程数越少。如物理内存大小一定,若一个线程用1MB内存,总共的物理内存有500MB,理论上有500个线程可以同时运行。如果为每个线程的栈内存设置为2MB内存,则理论上只能有250个线程同时运行。

        栈内存大了,只是可以进行更多次的方法递归调用。

3. 方法内的局部变量是否线程安全?

        ■ 如果方法内局部变量没有逃离方法的作用范围,它是线程安全的(线程私有)

        ■ 如果是局部变量引用了对象,并逃离方法的作用范围(作为返回值返回),需要考虑线程安全(多个线程共享)



/**
 * 局部变量的线程安全问题
 */
public class Demo1_17 {
    public static void main(String[] args) {
		//主线程里创建了一个对象,并进行操作。
        StringBuilder sb = new StringBuilder();
        sb.append(4);
        sb.append(5);
        sb.append(6);
		//创建新线程,将同一个对象传给了m2方法
		//则两个线程使用的是同一个对象,对象不再是线程私有因此m2是非线程安全的
        new Thread(()->{
            m2(sb);
        }).start();
    }

	//线程安全
    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.2 栈内存溢出

● 栈帧过多导致栈内存溢出

1.如递归调用没有出口,一直进行方法调用,产生栈帧。



/**
 * 演示栈内存溢出 java.lang.StackOverflowError
 * -Xss256k   指定占内存大小为256KB
 */
public class Demo1_2 {
    private static int count;

    public static void main(String[] args) {
        try {
            method1();
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(count);
        }
    }

    private static void method1() {
        count++;
        method1();
    }
}



2.第三方库导致的栈内存溢出

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;
    }
}

● 栈帧过大导致栈内存溢出

2.3 线程运行诊断

案例1:cpu占用过多

package cn.itcast.jvm.t1.stack;

/**
 * 演示 cpu 占用过高
 */
public class Demo1_16 {

    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();
    }
}

定位

■ 用top命令定位哪个进程对cpu的占用过高

■ ps H -eo pid,tid,%cpu | grep 进程id(用ps命令进一步定位是哪个线程引起的cpu占用过高)

■ jstack 进程id(有问题的进程)

        ○ 可以根据进程id找到有问题的线程,进一步定位到问题代码的源码行号

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

可能产生死锁

package cn.itcast.jvm.t1.stack;

/**
 * 演示线程死锁
 */
class A{};
class B{};
public class Demo1_3 {
    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();
    }

}

3. 本地方法栈

作用:

        给本地方法的执行提供内存空间。

本地方法:

        就是不是由java代码编写的代码,使用C或C++编写的本地方法与操作系统底层打交道,本地方法使用的内存就是本地方法栈。本地方法如Object的clone方法等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值