【 java 虚拟机知识 第一篇 】

【 java 虚拟机知识 第一篇 】

目录

1.内存模型

1.1.JVM内存模型的介绍

1.2.堆和栈的区别

1.3.栈的存储细节

1.4.堆的部分

1.5.程序计数器的作用

1.6.方法区的内容

1.7.字符串池

1.8.引用类型

1.9.内存泄漏与内存溢出

1.10.会出现内存溢出的结构


1.内存模型

1.1.JVM内存模型的介绍

内存模型主要分为五个部分:虚拟机栈,本地方法栈,堆,方法区(永久代或元空间),程序计数器,当然还有一部分是直接内存。

虚拟机栈:每个线程各有一个,线程独有,当执行方法(除了本地方法native修饰的方法)之前,会创建一个栈帧,栈帧里面包含局部变量表和操作数栈和动态链接和方法出口等信息,而每个栈帧就是存入栈中

本地方法栈:每个线程各有一个,线程独有,当执行本地方法时类似于虚拟机栈,一样会创建栈帧,存入对应信息

程序计数器:每个线程各有一个,线程独有,它的作用是记录当前线程下一次执行的二进制字节码指令地址,如果执行的是本地方法那么它会记录为定值(null)

:所有线程共享,堆的回收由垃圾回收机制管理,堆中主要存入对象实例信息,类信息,数组信息,堆是JVM内存中最大的一个

永久代:在jdk1.7及以前是方法区的实现,使用的是jvm内存,独立于堆,主要存入类信息,静态变量信息,符号引用等信息

元空间:在jdk1.8及以后是方法区的实现,使用的是本地内存,主要存入类信息,静态变量信息,符号引用等信息

直接内存:该内存属于操作系统,由NIO引入,操作系统和Java程序都可以进行操作,实现共享

----

常量池:属于class文件的一部分,主要存储字面量,符号引用

----

运行时常量池:属于方法区,其实就是将常量池中的符号引用替换成了直接引用,其余一样

1.2.堆和栈的区别

五个点:用途,生命周期,存储速度,存储空间,可见性

用途:栈主要存储方法返回地址,方法参数,临时变量,每次方法执行之前会创建栈帧,而堆存储对象的实例信息,类实例信息,数组信息

生命周期:栈的生命周期可见,每次方法执行完栈帧就会移除(弹出),而堆中的数据需要由垃圾回收器回收,回收时间不确定

存储速度:栈的速度更快,栈保持"先进后出"的原则,操作简单快,而堆需要对对象进行内存分配和垃圾回收,并且垃圾回收器本身运行也会损耗性能,速度慢

存储空间:栈的空间相对于堆的空间小,栈的空间小且固定,由操作系统管理,而堆的空间是jvm中最大的,由jvm管理

可见性:栈是每个线程都有的,而堆是所有线程共享的

1.3.栈的存储细节

如果执行方法时,里面创建了基本类型,那么基本类型的数据会存入栈中,如果创建了引用类型,会将地址存入栈,其实例数据存入堆中

1.4.堆的部分

堆主要分为两部分:新生代,老年代,它的比例:1:2

新生代:新生代分为两个区:伊甸园区和幸存者区,而幸存者区又平均分为S0和S1区,伊甸园区与S0与S1之间的比例:8:1:1,每次新创建的对象实例都会先存入伊甸园区,它们主要使用的垃圾回收算法是复制算法,当伊甸园区的内存使用完时,会使用可达性分析算法,标记不可存活的对象(没有被引用的对象)将存活对象复制移入S0或S1中,这个过程叫Minor GC,如果这次移入的是S0,那么下次就会将伊甸园区和S0中的对象移入S1中,循环反复,每经历一次Minor GC过程就会给对象年龄加一,直到大于等于15时,会认为该对象生命周期长,移入老年代中

细节:其实新创建的对象不会直接存入伊甸园区,如果多线程情况下同时进行存入对象(线程竞争压力大)会导致性能的损失,因此会给每个线程从伊甸园区中先申请一块TLAB区域,先将对象存入该区,如果该区内存使用完,会重写申请或直接存入伊甸园区

老年代:老年代就是存储生命周期长的对象(不经常回收的对象),主要使用的垃圾回收算法为标记清除算法或标记整理算法,看场景出发,其中老年代还包含一个大对象区

大对象区:主要存储的就是新创建的大对象比如说大数组,会直接将该对象存入大对象区中,不在存入新生代

可达性分析算法:从GC Root出发找对应引用对象,如果一个对象没有被直接引用或间接引用,那么会被标记,GC Root可以是java的核心库中的类,本地方法使用的类,还未结束的线程使用的类,使用了锁的类

标记清除算法:对引用的对象进行标记,然后进行清除(不是真正的清除,而是记录其对象的起始地址和结束地址到一个地址表中,下次要添加新对象时会先从表中找,找到一个适合大小的就会进行覆盖),清除:记录地址,新对象进行覆盖,好处:速度快,缺点:内存碎片化严重(内存不连续了,本来可以存入的对象存入不了)

标记整理算法:同理进行标记,然后再对可存活对象进行整理,最后清除,好处:避免了内存碎片化问题,缺点:速度慢

复制算法:将内存空间分为两份,一份存对象from,一份为空to,当要回收时,复制可存活对象移入为空的内存空间to中(移入既整理),然后对存对象的空间from整体清除,然后名称from和to换过来

为什么会有大对象区:因为伊甸园区的内存空间本身就不大,如果你直接创建一个大于它空间的对象,会出现问题,还有就是即使没有超过伊甸园区的空间,但是其对象依旧很大,频繁的复制移动很影响性能

1.5.程序计数器的作用

简单来说:线程1执行到某个地方时,线程2抢到了执行权,那么等到线程1执行时是不是需要知道上次执行到哪里了,所以程序计数器就是记录执行到哪里的,并且每次线程都需要有一个来记录

1.6.方法区的内容

方法区主要包含:类信息,静态变量信息,运行时常量池,即时编译器的缓存数据

1.7.字符串池

在jdk1.6及以前字符串池属于永久代,jdk1.7字符串池移入堆中但是还是属于永久代的,jdk1.8及以后还是存入堆中,但是不属于元空间了(1.7以前是永久代,1.8以后是元空间)

细节:String s1 = "a";它的过程是:先去字符串池中找,看是否能找到该字符,找到了直接复用池中地址,没有找到会先在堆中创建一个String对象,jdk1.6它会将数据复制一份重新创建一个新的对象存入池中,jdk1.7会将其地址复用给池中

String s2 = new("b");同理

String s3 = "a" + "b";常量进行相加,与new("ab")基本一致

String s4 = s1 + s2;变量相加,底层使用的是new StringBuilder.append("a").append("b").toString(),如果池中存在"ab",它也不会复用,而是直接创建,如果池中不存在,而不会将新创建的对象存入池中

1.8.引用类型

引用类型:强引用,软引用,弱引用,虚引用,(终结器引用

强引用:比如new就是,只要有强引用指向对象,那么该对象永远不会被回收

软引用:如果出现内存溢出的情况,再下次GC时会对其回收

弱引用:每次进行GC过程都会进行回收

虚引用:每次进行GC过程都会进行回收

细节:这些都是对象,等级依次递减

软引用:创建一个软引用对象时你可以指定引用队列,如果不指定会导致软引用为null一个空壳,比如说出现了GC Root强引用软引用对象,导致软引用对象无法被回收,你想要其对象被回收,可以使用引用队列,简单来说就是出现了这种情况,将软引用对象存入队列中,下次GC会扫描队列进行回收,当然这是特殊情况,总结来说:软引用可以使用引用队列也可以不使用

public class SoftRefDemo { public static void main(String[] args) throws InterruptedException { // 1. 创建引用队列 ReferenceQueue<Object> queue = new ReferenceQueue<>(); // 2. 创建大对象(确保能被GC回收) byte[] data = new byte[10 * 1024 * 1024]; // 10MB // 3. 创建软引用并关联队列 SoftReference<Object> softRef = new SoftReference<>(data, queue); // 4. 移除强引用(只保留软引用) data = null; System.out.println("GC前: "); System.out.println(" softRef.get() = " + softRef.get()); System.out.println(" queue.poll() = " + queue.poll()); // 5. 强制GC(模拟内存不足) System.gc(); Thread.sleep(1000); // 给GC时间 System.out.println("\nGC后: "); System.out.println(" softRef.get() = " + softRef.get()); System.out.println(" queue.poll() = " + queue.poll()); } }
GC前: softRef.get() = [B@15db9742 queue.poll() = null GC后: softRef.get() = null queue.poll() = java.lang.ref.SoftReference@6d06d69c

弱引用:与软引用相同

WeakHashMap<Key, Value> map = new WeakHashMap<>(); Key key = new Key(); map.put(key, new Value()); // 移除强引用 key = null; System.gc(); // GC后Entry自动被移除 System.out.println(map.size()); // 输出: 0

虚引用:最好的例子就是直接内存:它就是使用了虚引用,直接内存就是从操作系统中申请了一块空间来使用,因此GC是不能对其进行回收的,如果当强引用消失只剩下虚引用,那么会将虚引用对象存入引用队列中,等队列来执行本地方法释放直接内存

public class PhantomRefDemo { public static void main(String[] args) { // 1. 创建引用队列 ReferenceQueue<Object> queue = new ReferenceQueue<>(); // 2. 创建虚引用 Object obj = new Object(); PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue); // 3. 模拟直接内存分配 ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024 * 1024); // 1MB System.out.println("GC前:"); System.out.println(" phantomRef.get() = " + phantomRef.get()); // null System.out.println(" queue.poll() = " + queue.poll()); // null // 4. 移除强引用(触发回收条件) obj = null; directBuffer = null; // 释放DirectByteBuffer强引用 // 5. 强制GC(实际应用中会自动触发) System.gc(); try { Thread.sleep(500); } catch (Exception e) {} System.out.println("\nGC后:"); System.out.println(" phantomRef.get() = " + phantomRef.get()); // null System.out.println(" queue.poll() = " + queue.poll()); // 返回phantomRef对象 // 6. 实际效果:DirectByteBuffer分配的1MB堆外内存已被释放 } }

终结器引用:在所有父类Object中有一个终结器方法finalize()方法,如果重写该方法,那么执行GC之前会先执行该方法,当没强引用指向了,而这个对象还重写了finalize()方法,那么会将这个终结器引用对象加入队列中,下次GC时会先由队列来执行finalize()方法,但是指定执行的队列是一个优先级不高的队列,会导致资源释放缓慢

public class ResourceHolder { // 重写finalize方法(不推荐!) @Override protected void finalize() throws Throwable { releaseResources(); // 释放资源 super.finalize(); } }

1.9.内存泄漏与内存溢出

内存泄漏:就是说没有被引用的对象没有被回收,导致可用内存空间减少

比如:

  • 静态集合没有释放:一直存在
  • 线程未释放:线程应该执行完了,但是没有释放
  • 事件监听:事件源都不存在了,还在监听

例子:使用对应的文件流,字节流,但是没有释放该流,就会导致内存泄漏

解决:释放流

内存溢出:就是说内存不足了

比如:

  • 一直创建新对象
  • 持久引用:集合一直添加但是没有被清除
  • 递归

例子:ThreadLocal,每个线程都有一个ThreadLocal,本质就是每个线程存在一个ThreadLocalMap对象,key(弱引用)存入的是TreadLocal的实例,value(强引用)为自己指定的Object对象,如果没有使用该TreadLocal了,也就是说没有强引用指向TreadLocalMap对象,那么其中的key就会被设置为null,那如果该线程一直不结束,导致key不能被回收,随着key为null的情况增多就会导致内存溢出

解决:使用TreadLocal.recome();

1.10.会出现内存溢出的结构

会出现该问题的内存结构:堆,栈,元空间,直接空间

Read more

为什么要学习 PostgreSQL?我们能从中收获什么?

更多内容请见: 《深入掌握PostgreSQL数据库》 - 专栏介绍和目录 文章目录 * 一、PostgreSQL 是什么?—— 不止是“开源 Oracle” * 二、为什么要学习 PostgreSQL?五大核心理由 * 理由 1:技术先进性 —— 拥抱现代数据库的“瑞士军刀” * 理由 2:开源自由与商业友好 —— 无厂商锁定的安心之选 * 理由 3:云原生时代的“宠儿” —— 云厂商全力押注 * 理由 4:AI 与大数据时代的“新基石” —— 原生支持智能应用 * 理由 5:职业竞争力的“加速器” —— 市场需求激增 * 三、我们能从 PostgreSQL 中收获什么? * 收获 1:扎实的数据库理论与实践能力 * 收获 2:现代化应用架构设计思维

By Ne0inhk
Flutter 组件 vietqr_gen 适配鸿蒙 HarmonyOS 实战:标准聚合支付,构建金融级二维码生成与跨境支付治理架构

Flutter 组件 vietqr_gen 适配鸿蒙 HarmonyOS 实战:标准聚合支付,构建金融级二维码生成与跨境支付治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 vietqr_gen 适配鸿蒙 HarmonyOS 实战:标准聚合支付,构建金融级二维码生成与跨境支付治理架构 前言 在鸿蒙(OpenHarmony)生态迈向全场景商业化、涉及跨境数字化金融、智能收银终端及分布式聚合支付的背景下,如何生成符合国际 EMVCo 标准且具备高可靠校验机制的支付二维码,已成为决定金融类应用“交易确定性”的核心环节。在鸿蒙设备这类强调内核级安全防护与高精度金融计算的环境下,如果应用依然依赖简单的字符串拼接来构造具有复杂 TLV(Tag-Length-Value)结构的支付密令,由于由于字节统计误差或 CRC 校验逻辑漏洞,极易由于由于扫码解析失败导致资金结算链路的中断。 我们需要一种能够自动化 TLV 封装、支持标准银行目录映射且具备高精度 CRC16 校验的金融级生成方案。 vietqr_gen 为 Flutter 开发者引入了标准化的聚合支付二维码生成协议。它不仅支持对收款账号、金额及备注的结构化打包,更

By Ne0inhk
毕业设计:基于python的django + vue3网上鲜花商城(源码)

毕业设计:基于python的django + vue3网上鲜花商城(源码)

一、项目背景 随着互联网技术的飞速发展与消费模式的深刻变革,电子商务已深度融入国民经济的各个领域,成为推动消费增长的重要引擎。据国家统计局数据显示,2023年全国网上零售额达15.42万亿元,同比增长11%,网络购物已成为居民消费的主要渠道之一。在这一背景下,传统鲜花零售业态正经历着前所未有的数字化转型——消费者对鲜花的需求已从节日礼仪、庆典活动等特定场景,逐步延伸至日常家居装饰、情感表达、悦己消费等多元领域,“每周一花”订阅模式、即时配送等新型消费习惯逐渐养成,鲜花电商市场迎来爆发式增长。 然而,当前鲜花电商领域仍存在诸多痛点亟待解决。从消费端来看,现有平台多依附于综合性电商巨头,鲜花品类混杂于海量商品中,缺乏专注垂直领域的精细化运营,消费者难以获得沉浸式、场景化的购花体验;传统花店受限于地域辐射范围,消费者可选择品种有限,价格透明度低,且售后服务体系不完善。从供给端来看,许多中小型花店虽有意拓展线上渠道,但自建电商平台成本高昂,技术门槛高,缺乏集商品管理、订单处理、营销推广于一体的轻量化解决方案。特别是在后疫情时代,消费者的即时性需求与品质追求同步提升,对购花流程的便捷性、配送

By Ne0inhk
MySQL 表约束核心指南:从基础约束到外键关联(含实战案例)

MySQL 表约束核心指南:从基础约束到外键关联(含实战案例)

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 表约束核心概念 * 二. 基础约束:NULL/NOT NULL 与 DEFAULT * 2.1 空属性约束(NULL/NOT NULL) * 2.2 默认值约束(DEFAULT) * 2.3 列描述(COMMENT) * 2.4 零填充约束(ZEROFILL) * 三. 核心约束:主键、自增长与唯一键 * 3.1 主键约束(PRIMARY KEY) * 3.

By Ne0inhk