当前位置: 首页 > news >正文

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方法@Overridepublic 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("当前对象还存活");}@Overrideprotected 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. 常见的垃圾回收器有哪些?

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Numpy中数组元素的获取
  • springboot3 集成elasticsearch(es)客户端(高亮查询)
  • 走进低代码报表开发(二):高效报表设计新利器
  • linux 中gitee配置
  • 使用patch命令移除sts中的一个container
  • 个人学习笔记7-2:动手学深度学习pytorch版-李沐
  • 基于Spring Boot的小区物业管理系统
  • 深入剖析 Java 中的 AbstractQueuedSynchronizer(AQS)
  • 苹果宣布iOS 18正式版9月17日推送:支持27款iPhone升级
  • C# WPF上位机与西门子PLC通信实现实例解析
  • Android 使用JSON动画:Lottie框架基本使用
  • 学生成绩操作
  • Leetcode面试经典150题-134.加油站
  • 关于Spring Cloud 表达式注入漏洞——分析复现
  • Pyspark下操作dataframe方法(1)
  • 4个实用的微服务测试策略
  • CAP 一致性协议及应用解析
  • Hibernate【inverse和cascade属性】知识要点
  • JavaScript标准库系列——Math对象和Date对象(二)
  • sublime配置文件
  • 收藏好这篇,别再只说“数据劫持”了
  • 微信小程序填坑清单
  • 一些关于Rust在2019年的思考
  • 在Unity中实现一个简单的消息管理器
  • 责任链模式的两种实现
  • Play Store发现SimBad恶意软件,1.5亿Android用户成受害者 ...
  • ​Benvista PhotoZoom Pro 9.0.4新功能介绍
  • # Panda3d 碰撞检测系统介绍
  • #每天一道面试题# 什么是MySQL的回表查询
  • $emit传递多个参数_PPC和MIPS指令集下二进制代码中函数参数个数的识别方法
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (Java数据结构)ArrayList
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (附源码)流浪动物保护平台的设计与实现 毕业设计 161154
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (图文详解)小程序AppID申请以及在Hbuilderx中运行
  • (已解决)什么是vue导航守卫
  • (转)【Hibernate总结系列】使用举例
  • (转)大型网站的系统架构
  • (转载)Linux网络编程入门
  • (转载)深入super,看Python如何解决钻石继承难题
  • ***详解账号泄露:全球约1亿用户已泄露
  • ***原理与防范
  • ../depcomp: line 571: exec: g++: not found
  • .net开发时的诡异问题,button的onclick事件无效
  • /etc/sudoers (root权限管理)
  • @ConfigurationProperties注解对数据的自动封装
  • @FeignClient注解,fallback和fallbackFactory
  • [ IOS ] iOS-控制器View的创建和生命周期
  • [ NOI 2001 ] 食物链
  • [Android] 240204批量生成联系人,短信,通话记录的APK
  • [Android]通过PhoneLookup读取所有电话号码
  • [Angular 基础] - 数据绑定(databinding)