JVM-java虚拟机

目录

jvm组成部分

---------------------------------内存结构-----------------------------------------

1. 程序计数器

1.1 定义

1.2 特点

1.3 作用

1.4 java代码执行流程

2. 虚拟机栈

2.1 定义

​编辑

2.2 栈内存溢出

2.3 线程运行诊断

3. 本地方法栈

4.堆

4.1 定义

4.2 堆内存溢出

4.3 堆内存诊断

5. 方法区

5.1 定义

5.2 组成

5.3 方法区内存溢出

5.4 运行时常量池

5.5 StringTable 特性

5.6 StringTable 位置

5.7 StringTable 垃圾回收

5.8 StringTable 性能调优

6. 直接内存

6.1 定义

6.2 分配和回收原理

----------------------------------垃圾回收----------------------------------------

1. 如何判断对象可以回收

1.1 引用计数法

1.2 可达性分析算法

1.3 四种引用

强引用

软引用(SoftReference)

弱引用(WeakReference)

虚引用(PhantomReference)

2. 垃圾回收算法

2.1 标记清除

2.2 标记整理

2.3 复制

3. 分代垃圾回收

3.1 相关VM参数

3.2 GC分析

3.3 GC分析之大对象

4. 垃圾回收器

4.1串行(Serial)垃圾回收器

4.2并行(Parallel)垃圾回收器(吞吐量优先)

4.3响应时间(CMS)优先垃圾回收器

4.4 G1垃圾回收器

-----------------------类加载与字节码技术------------------------------------

1. 类文件结构

1.1 魔数

1.2 版本

1.3 常量池Constant

5. 类加载器

5.1 启动类加载器

5.2 扩展类加载器

5.3 双亲委派模式

5.3.1 如何打破双亲委派机制

5.4 线程上下文类加载器

5.5 自定义类加载器

5.5.1 什么时候需要自定义类加载器

5.5.2 步骤:

5.5.3 示例

-----------------------------------内存模型---------------------------------------

1. java 内存模型

1.1 原子性

1.2 问题分析

 1.3 解决方法

2. 可见性

2.1 退不出的循环

 2.2 解决方法

2.3 可见性

3. 有序性

3.1 诡异的结果

3.2 解决方法

3.3 有序性理解

 4. CAS 与 原子类

4.1 CAS

4.2 乐观锁与悲观锁

 4.3 原子操作类​​​​​​​

jvm组成部分

---------------------------------内存结构-----------------------------------------

  1. 程序计数器
  2. 虚拟机栈
  3. 本地方法栈
  4. 方法区

1. 程序计数器

1.1 定义

Program Counter Register 程序计数器(寄存器)

1.2 特点

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

不会存在内存溢出,唯一一个不会存在内存溢出的区,堆,栈,方法区等均会出现内存溢出。

1.3 作用

作用是记住下一条jvm指令的执行地址。

1.4 java代码执行流程

java源代码会编译成二进制字节码,二进制字节码会被解释器变成机器码,其中程序计数器的作用是记住下一条指令的执行地址,且程序计时器采用处理速度最快的寄存器,机器码交给CPU执行。

2. 虚拟机栈

2.1 定义

Java Virtual Machine Stacks (Java 虚拟机栈)

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

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

  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。

问题辨析:

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

不会,栈内存由每个栈帧内存组成,栈帧内存是在每个方法调用时入栈被使用,方法调用结束后弹出栈,所以不会被垃圾回收。

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

不是,我们物理内存的大小是一定的,所以当栈内存划分过大时,会时线程数减少,从而降低程序性能。

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

如果局部变量没有逃离该方法的作用范围则是线程安全的。

如果局部变量引用了对象,并且逃离了该方法的作用范围(局部变量的引用是传入的参数或者是返回值),则需要考虑线程安全问题。

package cn.itcast.jvm.t1.stack;
​
/**
 * 局部变量的线程安全问题
 */
public class Demo1_17 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        sb.append(4);
        sb.append(5);
        sb.append(6);
        new Thread(()->{
            m2(sb);
        }).start();
    }
​//线程安全,sb属于方法局部变量,其他线程不能访问到该变量,所以线程是安全的
    public static void m1() {
        StringBuilder sb = new StringBuilder();
        sb.append(1);
        sb.append(2);
        sb.append(3);
        System.out.println(sb.toString());
    }
​//线程不安全,可能被其他线程调用修改传入的参数sb的值
    public static void m2(StringBuilder sb) {
        sb.append(1);
        sb.append(2);
        sb.append(3);
        System.out.println(sb.toString());
    }
​//线程不安全,可能被其他线程调用返回的结果sb
    public static StringBuilder m3() {
        StringBuilder sb = new StringBuilder();
        sb.append(1);
        sb.append(2);
        sb.append(3);
        return sb;
    }
}

2.2 栈内存溢出

  • 栈帧过多导致栈内存溢出。

  • 栈帧过大导致栈内存溢出。(默认1m,不太容易出现)

应用场景:

  • 递归调用没有写结束条件。

package cn.itcast.jvm.t1.stack;

/**
 * 演示栈内存溢出 java.lang.StackOverflowError
 * -Xss256k
 */
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();
    }
}
  • 进行json格式转换的时候对象的循环调用。

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;

/**
 * 演示栈内存溢出 java.lang.StackOverflowError
 * 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:该注解表示在进行json格式转换的时候不会存在循环调用
    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 占用过多(while-true)

定位(linux下)

首先运行死循环java代码        nohup表示代码在后台运行

用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++编写的本地方法,而本地方法栈中就存放的本地方法,如wait(),notify(),notifyAll(),equals(),clone()等。且本地方法由native修饰。

4.堆

4.1 定义

Heap 堆:通过 new 关键字,创建对象都会使用堆内存。

特点

  • 它是线程共享的,堆中对象都需要考虑线程安全的问题。

  • 有垃圾回收机制。

4.2 堆内存溢出

注意:调整堆内存大小参数为 -Xmx 8m。

-Xmx 8m

堆内存溢出代码演示:

package cn.itcast.jvm.t1.heap;
​
import java.util.ArrayList;
import java.util.List;
​
/**
 * 演示堆内存溢出 java.lang.OutOfMemoryError: Java heap space
 * -Xmx8m
 */
public class Demo1_5 {
​
    public static void main(String[] args) {
        int i = 0;
        try {
            List<String> list = new ArrayList<>();
            String a = "hello";
            while (true) {
                list.add(a); // hello, hellohello, hellohellohellohello ...
                a = a + a;  // hellohellohellohello
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(i);
        }
    }
}

4.3 堆内存诊断

代码演示:

package cn.itcast.jvm.t1.heap;
​
/**
 * 演示堆内存
 */
public class Demo1_4 {
​
    public static void main(String[] args) throws InterruptedException {
        System.out.println("1...");
        Thread.sleep(30000);
        byte[] array = new byte[1024 * 1024 * 10]; // 10 Mb
        System.out.println("2...");
        Thread.sleep(20000);
        array = null;
        System.gc();
        System.out.println("3...");
        Thread.sleep(1000000L);
    }
}
​
  1. jps :工具查看当前系统中有哪些 java 进程

jmap 工具:查看堆内存占用情况 jmap - heap 进程id

 jconsole 工具:图形界面的,多功能的监测工具,可以连续监测

jvisualvm工具:具备jconsole不具备的更多功能,

5. 方法区

5.1 定义

JVM规范-方法区定义

5.2 组成

类的相关信息,如成员变量,成员方法,构造方法,运行时常量池等。

5.3 方法区内存溢出

1.8 以前会导致永久代内存溢出.

演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space。

方法区内存大小(1.8之前永久代)参数设置:-XX:MaxPermSize=8m。

package cn.itcast.jvm;
​
​
import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import com.sun.xml.internal.ws.org.objectweb.asm.Opcodes;
​
/**
 * 演示永久代内存溢出  java.lang.OutOfMemoryError: PermGen space
 * -XX:MaxPermSize=8m
 */
public class Demo1_8 extends ClassLoader {
    public static void main(String[] args) {
        int j = 0;
        try {
            Demo1_8 test = new Demo1_8();
            for (int i = 0; i < 20000; i++, j++) {
                ClassWriter cw = new ClassWriter(0);
                cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                byte[] code = cw.toByteArray();
                test.defineClass("Class" + i, code, 0, code.length);
            }
        } finally {
            System.out.println(j);
        }
    }
}
​

1.8 之后会导致元空间内存溢出。

演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace。

方法区内存大小(1.8之后元空间)参数设置:-XX:MaxMetaspaceSize=8m。

package cn.itcast.jvm.t1.metaspace;

import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
 * 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
 * -XX:MaxMetaspaceSize=8m
 */
public class Demo1_8 extends ClassLoader { // 可以用来加载类的二进制字节码
    public static void main(String[] args) {
        int j = 0;
        try {
            Demo1_8 test = new Demo1_8();
            for (int i = 0; i < 26000; i++, j++) {
                // ClassWriter 作用是生成类的二进制字节码
                ClassWriter cw = new ClassWriter(0);
                // 版本号, public, 类名, 包名, 父类, 接口
                cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                // 返回 byte[]
                byte[] code = cw.toByteArray();
                // 执行了类的加载
                test.defineClass("Class" + i, code, 0, code.length); // Class 对象
            }
        } finally {
            System.out.println(j);
        }
    }
}

场景:

spring、mybatis使用动态代理技术时会产生大量的动态代理类可能会造成内存溢出。

5.4 运行时常量池

常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息。我们可以通过javap -v helloword.class指令来进行反编译来获取二进制字节码的相关信息。

package cn.itcast.jvm.t5;

// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址。

package cn.itcast.jvm.t1.stringtable;

// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class Demo1_22 {
    // 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
    // ldc #2 会把 a 符号变为 "a" 字符串对象
    // ldc #3 会把 b 符号变为 "b" 字符串对象
    // ldc #4 会把 ab 符号变为 "ab" 字符串对象

    public static void main(String[] args) {
        String s1 = "a"; // 懒惰的
        String s2 = "b";
        String s3 = "ab";
 // new StringBuilder().append("a").append("b").toString()  new String("ab")
        String s4 = s1 + s2;
//返回false
        System.out.println(s3 == s4);
// javac 在编译期间的优化,结果已经在编译期确定为ab,所以如果常量池中有则不会加入
        String s5 = "a" + "b";  
//返回ture
        System.out.println(s3 == s5);

    }
}

5.5 StringTable 特性

  1. 常量池中的字符串仅是符号,第一次用到时才变为对象。

  2. 利用串池的机制,来避免重复创建字符串对象。

  3. 字符串变量拼接的原理是 StringBuilder (1.8)。

  4. 字符串常量拼接的原理是编译期优化。

  5. 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池。

  6. 运行时常量池的 一部分,储存字符串常量

  • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回。

package cn.itcast.jvm.t1.stringtable;

public class Demo1_23 {

    //  ["ab", "a", "b"]
    public static void main(String[] args) {

        String x = "ab";
        // 堆  new String("a")   new String("b")     new String("ab")
        String s = new String("a") + new String("b");

        // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,不管有没有都会把串池中的对象返回
        String s2 = s.intern(); 

        System.out.println( s2 == x);//返回值为true
        System.out.println( s == x );//返回值为false
    }

}
  • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回。

package cn.itcast.jvm;

public class Demo1_23 {

    // ["a", "b", "ab"]
    public static void main(String[] args) {


        String s = new String("a") + new String("b");

        // 堆  new String("a")   new String("b")  new String("ab")
        String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
        // s 拷贝一份,放入串池

        String x = "ab";
        System.out.println( s2 == x);//返回值true
        System.out.println( s == x );//返回值false
    }

}

面试题:

package cn.itcast.jvm.t1.stringtable;

/**
 * 演示字符串相关面试题
 */
public class Demo1_21 {

    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b"; // ab
        String s4 = s1 + s2;   // new String("ab")
        String s5 = "ab";
        String s6 = s4.intern();

// 问
        System.out.println(s3 == s4); // false
        System.out.println(s3 == s5); // true
        System.out.println(s3 == s6); // true

        String x2 = new String("c") + new String("d"); // new String("cd")
        x2.intern();
        String x1 = "cd";

// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
        System.out.println(x1 == x2);
    }
}

5.6 StringTable 位置

 演示StringTable的位置jdk1.6下:

package cn.itcast.jvm;

import java.util.ArrayList;
import java.util.List;

/**
 * 演示 StringTable 位置
 * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit
 * 在jdk6下设置 -XX:MaxPermSize=10m
 */
public class Demo1_6 {

    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        int i = 0;
        try {
            for (int j = 0; j < 260000; j++) {
                list.add(String.valueOf(j).intern());
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
}

演示StringTable的位置jdk1.8下:

package cn.itcast.jvm.t1.stringtable;

import java.util.ArrayList;
import java.util.List;

/**
 * 演示 StringTable 位置
 * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit
 * 在jdk6下设置 -XX:MaxPermSize=10m
 */
public class Demo1_6 {

    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        int i = 0;
        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值