JVM虚拟机 - 基础篇

一、初始JVM

1. JVM是什么

2. JVM的三大核心功能是什么?

3. 常见的JVM虚拟机有哪些?

二、字节码文件详解

1. Java虚拟机的组成

2. 字节码文件的组成

(1)基本信息

Magic魔数

主副版本号

(2)常量池

(3)字段

(4)方法

操作数栈是用来存放临时数据的内容,是一个栈式的结构,先进后出。

局部变量是存放方法中的局部变量,包含方法的参数、方法中定义的局部变量,在编译期就已经可以确定方法有多少个局部变量。

1、iconst_0,将常量0放入操作数栈。此时栈上只有0。

2、istore_1会从操作数栈中,将栈顶的元素弹出来,此时0会被弹出,放入局部变量表的1号位置。局部变量表中的1号位置,在编译时就已经确定是局部变量i使用的位置。完成了对局部变量i的赋值操作。

3、iload_1将局部变量表1号位置的数据放入操作数栈中,此时栈中会放入0。

4、iconst_1会将常量1放入操作数栈中。

5、iadd会将操作数栈顶部的两个数据相加,现在操作数栈上有两个数0和1,相加之后结果为1放入操作数栈中,此时栈上只有一个数也就是相加的结果1。

6、istore_2从操作数栈中将1弹出,并放入局部变量表的2号位置,2号位置是j在使用。完成了对局部变量j的赋值操作。

7、return语句执行,方法结束并返回。

同理,同学们可以自行分析下i++和++i的字节码指令执行的步骤。

i++的字节码指令如下,其中iinc 1 by 1指令指的是将局部变量表1号位置增加1,其实就实现了i++的操作。

而++i只是对两个字节码指令的顺序进行了更改:

(5)属性

3. 类的生命周期

加载 -> 连接(验证、准备、解析) -> 初始化 -> 使用 -> 卸载

(1)加载阶段

(2)连接阶段

(2-1)验证

(2-2)准备

(2-3)解析

(3)初始化

4. 类加载器

(1)类加载器的分类

(2)双亲委派机制

向上查找是否加载过

向下尝试加载

面试题 - 类的双亲委派机制是什么?

(3)打破双亲委派机制

(3-1)自定义类加载器

自定义类加载器默认的父类加载器:应用程序类加载器

package classloader.broken;//package com.itheima.jvm.chapter02.classloader.broken;

import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.ProtectionDomain;
import java.util.regex.Matcher;

/**
 * 打破双亲委派机制 - 自定义类加载器
 */

public class BreakClassLoader1 extends ClassLoader {

    private String basePath;
    private final static String FILE_EXT = ".class";

    //设置加载目录
    public void setBasePath(String basePath) {
        this.basePath = basePath;
    }

    //使用commons io 从指定目录下加载文件
    private byte[] loadClassData(String name)  {
        try {
            String tempName = name.replaceAll("\\.", Matcher.quoteReplacement(File.separator));
            FileInputStream fis = new FileInputStream(basePath + tempName + FILE_EXT);
            try {
                return IOUtils.toByteArray(fis);
            } finally {
                IOUtils.closeQuietly(fis);
            }

        } catch (Exception e) {
            System.out.println("自定义类加载器加载失败,错误原因:" + e.getMessage());
            return null;
        }
    }

    //重写loadClass方法
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        //如果是java包下,还是走双亲委派机制
        if(name.startsWith("java.")){
            return super.loadClass(name);
        }
        //从磁盘中指定目录下加载
        byte[] data = loadClassData(name);
        //调用虚拟机底层方法,方法区和堆区创建对象
        return defineClass(name, data, 0, data.length);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
        //第一个自定义类加载器对象
        BreakClassLoader1 classLoader1 = new BreakClassLoader1();
        classLoader1.setBasePath("D:\\lib\\");

        Class<?> clazz1 = classLoader1.loadClass("com.itheima.my.A");
         //第二个自定义类加载器对象
        BreakClassLoader1 classLoader2 = new BreakClassLoader1();
        classLoader2.setBasePath("D:\\lib\\");

        Class<?> clazz2 = classLoader2.loadClass("com.itheima.my.A");

        System.out.println(clazz1 == clazz2);

        Thread.currentThread().setContextClassLoader(classLoader1);

        System.out.println(Thread.currentThread().getContextClassLoader());

        System.in.read();
     }
}

(3-2)线程上下文类加载器

总结:

(4)JDK8之后的类加载器

(5)总结

1. 类加载器的作用是什么?

2. 有几种类加载器?

3. 什么是双亲委派机制?

4. 怎么打破双亲委派机制?

三、JVM的内存区域

运行时数据区 - 总览

1. 程序计数器

2. 栈

(1)Java虚拟机栈

栈帧的组成

操作数栈

帧数据

帧数据主要包含动态链接、方法出口、异常表的引用。

动态链接:

方法出口:

异常表:

栈内存溢出:

(2)本地方法栈

3. 堆

4. 方法区

(1)类的元信息

(2)运行时常量池

(3)字符串常量池

字符串常量池存放在Java堆内存(heap)中。

静态变量存储在哪里?

5. 直接内存

总结:

1. 运行时数据区分为哪几个部分,每一部分的作用是什么?

程序计数器:每个线程会通过程序计数器记录当前要执行的字节码指令的地址,程序计数器可以控制程序指令的进行,实现分支、跳转、异常等逻辑。

Java虚拟机栈:采用栈的数据结构赖管理方法调用中的基本数据(局部变量、操作数、帧数据),每一个方法的调用使用一个栈帧来保存。

本地方法栈:本地方法栈存储的是native本地方法的栈帧。

堆:存放中的是创建出来的对象,这也是最容易产生内存溢出的位置。

方法区:主要存放的是类的元信息,同时还保存了常量池。

2. 不同JDK版本之间运行时数据区域的区别是什么?JDK6

(在JDK8中,类的元信息和运行时常量池存放在元空间中,使用本地内存。字符串常量池存放在Java堆内存中。)

四、JVM的垃圾回收

C/C++的内存管理

Java的内存管理

1. 方法区的回收

2. 堆回收

(1)引用计数法和可达性分析法

如何判断堆上的对象可以回收?

引用计数法

可达性分析算法

哪些对象被称之为GC Root对象呢?

查看GC Root对象

(2)五种对象引用

软引用

代码:

/**
 * 软引用案例3 - 引用队列使用
 */
public class SoftReferenceDemo3 {

    public static void main(String[] args) throws IOException {

        ArrayList<SoftReference> softReferences = new ArrayList<>();
        ReferenceQueue<byte[]> queues = new ReferenceQueue<byte[]>();
        for (int i = 0; i < 10; i++) {
            byte[] bytes = new byte[1024 * 1024 * 100];
            SoftReference studentRef = new SoftReference<byte[]>(bytes,queues);
            softReferences.add(studentRef);
        }

        SoftReference<byte[]> ref = null;
        int count = 0;
        while ((ref = (SoftReference<byte[]>) queues.poll()) != null) {
            count++;
        }
        System.out.println(count);

    }
}

代码:

package chapter04.soft;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
/**
 * 软引用案例4 - 学生信息的缓存
 */
public class StudentCache {

    private static StudentCache cache = new StudentCache();

    public static void main(String[] args) {
        for (int i = 0; ; i++) {
            StudentCache.getInstance().cacheStudent(new Student(i, String.valueOf(i)));
        }
    }

    private Map<Integer, StudentRef> StudentRefs;// 用于Cache内容的存储
    private ReferenceQueue<Student> q;// 垃圾Reference的队列

    // 继承SoftReference,使得每一个实例都具有可识别的标识。
    // 并且该标识与其在HashMap内的key相同。
    private class StudentRef extends SoftReference<Student> {
        private Integer _key = null;

        public StudentRef(Student em, ReferenceQueue<Student> q) {
            super(em, q);
            _key = em.getId();
        }
    }

    // 构建一个缓存器实例
    private StudentCache() {
        StudentRefs = new HashMap<Integer, StudentRef>();
        q = new ReferenceQueue<Student>();
    }

    // 取得缓存器实例
    public static StudentCache getInstance() {
        return cache;
    }

    // 以软引用的方式对一个Student对象的实例进行引用并保存该引用
    private void cacheStudent(Student em) {
        cleanCache();// 清除垃圾引用
        StudentRef ref = new StudentRef(em, q);
        StudentRefs.put(em.getId(), ref);
        System.out.println(StudentRefs.size());
    }

    // 依据所指定的ID号,重新获取相应Student对象的实例
    public Student getStudent(Integer id) {
        Student em = null;
// 缓存中是否有该Student实例的软引用,如果有,从软引用中取得。
        if (StudentRefs.containsKey(id)) {
            StudentRef ref = StudentRefs.get(id);
            em = ref.get();
        }
// 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,
// 并保存对这个新建实例的软引用
        if (em == null) {
            em = new Student(id, String.valueOf(id));
            System.out.println("Retrieve From StudentInfoCenter. ID=" + id);
            this.cacheStudent(em);
        }
        return em;
    }

    // 清除那些所软引用的Student对象已经被回收的StudentRef对象
    private void cleanCache() {
        StudentRef ref = null;
        while ((ref = (StudentRef) q.poll()) != null) {
            StudentRefs.remove(ref._key);
        }
    }

//    // 清除Cache内的全部内容
//    public void clearCache() {
//        cleanCache();
//        StudentRefs.clear();
//        //System.gc();
//        //System.runFinalization();
//    }
}

class Student {
    int id;
    String name;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

弱引用

package chapter04.weak;

import java.io.IOException;
import java.lang.ref.WeakReference;

/**
 * 弱引用案例 - 基本使用
 */
public class WeakReferenceDemo2 {
    public static void main(String[] args) throws IOException {

        byte[] bytes = new byte[1024 * 1024 * 100];
        WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes);
        bytes = null;
        System.out.println(weakReference.get());

        System.gc();

        System.out.println(weakReference.get());
    }
}

虚引用和终结器引用

终结器引用案例

package chapter04.finalreference;

/**
 * 终结器引用案例
 */
public class FinalizeReferenceDemo {
    public static FinalizeReferenceDemo reference = null;

    public void alive() {
        System.out.println("当前对象还存活");
    }

    @Override
    protected void finalize() throws Throwable {
        try{
            System.out.println("finalize()执行了...");
            //设置强引用自救
            reference = this;
        }finally {
            super.finalize();
        }
    }

    public static void main(String[] args) throws Throwable {
        reference = new FinalizeReferenceDemo();
       test();
       test();
    }

    private static void test() throws InterruptedException {
        reference = null;
        //回收对象
        System.gc();
        //执行finalize方法的优先级比较低,休眠500ms等待一下
        Thread.sleep(500);
        if (reference != null) {
            reference.alive();
        } else {
            System.out.println("对象已被回收");
        }
    }
}

(3)垃圾回收算法

垃圾回收算法的评价标准

标记—清除算法

复制算法

标记—整理算法

分代垃圾回收算法

为什么分代GC算法要把堆分成年轻代和老年代?

(4)垃圾回收器

①年轻代 - Serial垃圾回收器 -> 复制算法

老年代 - SerialOld垃圾回收器 -> 标记整理算法

②年轻代 - ParNew垃圾回收器 -> 复制算法

老年代 - CMS(Concurrent Mark Sweep)垃圾回收器 -> 标记清除算法

③年轻代 - Parallel Scavenge垃圾回收器 -> 复制算法

老年代 - Paralell Old垃圾回收器 -> 标记整理算法

④G1垃圾回收器

总结:

1. Java中有哪几块内存需要进行垃圾回收?

线程不共享:跟随线程的生命周期随着线程回收而回收;

方法区:一般不需要回收,JSP等技术会通过回收类加载器去回收方法区中的类

堆:由垃圾回收器进行回收

2. 有哪几种常见的引用类型?

3. 有哪几种常见的垃圾回收算法?

4. 常见的垃圾回收器有哪些?

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值