关于JVM内存管理和垃圾回收

本文详细介绍了JVM的内存划分,包括java堆、方法区、虚拟机栈、本地方法栈和程序计数器。同时,深入讲解了JVM的垃圾回收机制,包括垃圾的判断算法、四种引用类型、垃圾回收算法及典型的垃圾回收器。此外,还概述了java的类加载过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、JVM的内存划分:

https://www.cnblogs.com/lifescolor/p/5481588.html
JVM

线程共享区域:
1、java堆:是jvm内存管理中最大的一块,存放new出来的对象实例
2、方法区:主要存放的是已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

线程私有区:
3、虚拟机栈:虚拟机栈生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从调用到执行完毕的过程中,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

补充:局部变量表中存放了编译期可知的各种基本数据类型、对象的引用类型。局部变量表中需要的内存空间在编译期间完成分
配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间完全是确定的,方法运行期间不会改变局部变量表的大小。

4、本地方法栈:与虚拟机栈很相似,区别在于虚拟机栈是为执行Java方法服务的,而本地方法栈是为本地方法服务的。
5、程序计数器:一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器

补充:如果线程正在执行的是一个java方法,那计数器记录的是正在执行的jvm字节码的指令的地址;若是本地方法,则这个计数器为空。

二、JVM的垃圾回收:

https://blog.youkuaiyun.com/SEU_Calvin/article/details/51892567
https://www.cnblogs.com/1024Community/p/honery.html

2.1、哪些内存需要回收(什么是垃圾):

进行垃圾回收前首先要判断哪些对象是垃圾(可回收的)。介绍两种判断对象是否存活的算法。

2.1.1、引用计数算法:

给每一个对象分配一个引用计数器,每当有地方引用该对象时,该对象的引用计数器+1(a = b,则b对象的计数器+1);当引用失效时(引用超过了生命周期或被设置为一个新值),引用计数器的值-1。当该对象的引用计数器的值为0时,说明该对象可回收。
但是该算法有一个缺点,无法检测出循环引用。

public class ReferenceFindTest {
    public static void main(String[] args) {
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();
          
        object1.object = object2;
        object2.object = object1;
          
        object1 = null;
        object2 = null;
    }
}

如上,循环引用,即使object1和object2都为null,都不会被访问,但是垃圾回收器还是不能回收它们。

2.1.2可达性分析算法:

从GC ROOT节点开始,向下搜索,搜索所走过的路径为引用链,当一个节点到GC ROOT没有任何引用链时,证明该对象不可用(垃圾,可回收)。
在 JAVA中,可以作为GC ROOT的对象有:
(1)虚拟机栈中引用的对象
(2)方法区中类静态属性和常量引用的对象
(3)本地方法栈中Native方法引用的对象

2.2 Java中的四中引用:

引用强度逐渐减弱。

  • 强引用
    强引用不会被GC回收,如Object obj = new Object();

  • 软引用
    描述一些还有用但非必须的对象,如果内存空间不足了,就会回收这些对象。软引用可用来实现内存敏感的高速缓存。

  • 弱引用
    与软引用的区别在于:一旦发现弱引用的对象,无论内存空间是否足够,都会回收它。弱引用的对象能生存到下一次垃圾回收之前,一但被发现就会被回收。

  • 虚引用
    虚引用必须和引用队列(ReferenceQueue)联合使用。

2.3 对象死亡之前的最后一次挣扎:

finalize()方法:
垃圾回收器将对象从内存中清除出去之前做必要的清理工作。
就像一个对象的遗书,把一个对象杀了之前让对象最后做点什么,甚至它能再复活自己。

如下面的代码。先赋值为null, 这个时候GC已经注意到这个s变量了,然后你又手动通知GC过来处理,GC正要收回内存时,发现该对象覆盖了Object的finalize方法,于是GC叹了口气,给了这个对象最后一个活动的机会,然后这个对象竟然有给自己新赋值,拯救了自己。

public class Sub2 {
	static Sub2 s ;
	public static void main(String[] args) throws InterruptedException{
		s = new Sub2();
		s = null;
		System.gc();
		Thread.sleep(1000);
		s.print();
	}
	
	public void finalize() {
		System.out.println("我要被回收了...");
		s = new Sub2();
	}
	
	public void print() {
		System.out.println("我还活着,我还能说话。。。");
	}
}

三、常用的垃圾回收算法

1、复制算法(Copying算法)

复制算法将可用内存按照容量划分为大小相等的两块,每次只使用一块。当其中一块内存用完了,就将这块内存中还存活的对象复制到另一块内存上,然后再将第一块内存上的空间全部清理掉。

复制算法这样不容易产生内存碎片,且运行高效。

但该算法导致可用内存缩减为原来的一半,若存活对象很多,那么复制算法的效率会大大降低。

2、标记-清除算法(Mark-Sweep算法)

分为两个阶段:标记阶段和清除阶段

标记阶段:标记出所有需要回收的对象;
清除阶段:回收被标记的对象占用的空间。

一个严重的问题是:容易产生内存碎片,碎片太多会导致为大对象分配空间时因为空间不足而提前出发GC

3、标记-整理算法(Mark-Compact算法)

首先标记出所有需要回收的对象,完成标记后,将存活的对象移向一端,然后清理掉端边界以外的所有内存(只留下了存活着的对象)。

解决了内存碎片的问题。

4、分代收集算法(Generational Collection)

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。

核心思想:将堆区分为老年代和新生代(根据对象存活的生命周期来划分的);
老年代每次垃圾收集时只有少量的对象需要被回收;
新生代每次垃圾收集时都有大量的对象需要被回收。

大部分垃圾收集器对新生代都采取复制算法,因为新生代每次都要回收大部分对象,复制操作的次数少,所以使用复制算法效率较高。
一般将新生代划分为一块较大的Eden空间和两块较小的Survivor空间(比例8 : 1 : 1),每次使用Eden空间和一块Survivor空间,当进行回收时,将还存活的对象复制到另一块Survivor区中,然后清理掉Eden区和SurvivorA区的空间。进行了第一次GC后,使用的就是Eden区和SurvivorB区了,如此反复循环。

补充:
	当SurvivorB区的空间不足以存放Eden区和SurvivorA区的存活对象时,就将存活对象直接存放至老年代。若是老年代也满		
了,会触发一次Full GC,也就是新生代和老年代都进行回收。(不知是否是正确的知识点)

当对象在新生代躲过一次GC的话,对象年龄加1,默认情况下,对象年龄达到15时,会被移动到老年代中。一般来说,大对象(指需要大量连续存储空间的对象)直接会被分配到老年代中。

在堆区之外的方法区中还有一个持久代,用来存储class文件,静态对象,方法描述等。对持久代的回收主要回收两部分内容:废弃常量和无用的类。(JDK1.8之后持久代被完全移除,换为:Metaspace 元空间)

补充:
	新生代发生的GC也叫Minor GC,发生频率较高。
	老年代内存满时会触发Full GC,发生频率较低。

什么时候会触发GC:
(1)Minor GC:当Eden区满的时候,会触发Minor GC
(2)Full GC:
		|-	调用System.gc()时
		|-	老年代空间不足时
		|-	持久代空间不足时

介绍一些有关堆的JVM常见的配置方式:

-Xss:栈内存的大小
-Xms:初始堆的大小
-Xmx:最大堆的大小
-XX:NewSize=n:设置新生代的大小。
-XX:NewRatio=n:设置老年代和新生代的比例,比如-XX:NewRatio=3,则老年代:新生代=3:1
-XX:SurvivorRatio=n:设置Eden和两个Survivor的比列,比如-XX:SurvivorRatio=3,则Eden:Survivor:Survivor=3:1:1
-XX:MaxPermSize=n:设置持久代的大小

四、典型的垃圾回收器

1、CMS:
一种以获取最短回收停顿时间为目标的收集器,是一种并发收集器,采用标记-整理算法
2、G1:
面向服务端应用的收集器,能充分利用多CPU,多核环境;是一块款并行与并发收集器,并且能建立可预测的停顿时间模型

五、java的类加载过程

  1. 加载: 简单来说,加载就是将class字节码文件从各个来源通过类加载器装载入内存中。
  2. 链接: 分为3部分
      1、验证: 保证加载进来的字节流符合虚拟机规范,不会造成安全错误。
      2、准备: 为类变量分配内存,并且赋予初值。
      3、解析: 将常量池中的符号引用替换为直接引用。
  3. 初始化: 对类变量初始化,是执行类构造器的过程。换句话说,只对static变量进行初始化。
内容概要:本文针对国内加密货币市场预测研究较少的现状,采用BP神经网络构建了CCi30指数预测模型。研究选取2018年3月1日至2019年3月26日共391天的数据作为样本,通过“试凑法”确定最优隐结点数目,建立三层BP神经网络模型对CCi30指数收盘价进行预测。论文详细介绍了数据预处理、模型构建、训练及评估过程,包括数据归一化、特征工程、模型架构设计(如输入层、隐藏层、输出层)、模型编译与训练、模型评估(如RMSE、MAE计算)以及结果可视化。研究表明,该模型在短期内能较准确地预测指数变化趋势。此外,文章还讨论了隐层节点数的优化方法及其对预测性能的影响,并提出了若干改进建议,如引入更多技术指标、优化模型架构、尝试其他时序模型等。 适合人群:对加密货币市场预测感兴趣的研究人员、投资者及具备一定编程基础的数据分析师。 使用场景及目标:①为加密货币市场投资者提供一种新的预测工具方法;②帮助研究人员理解BP神经网络在时间序列预测中的应用;③为后续研究提供改进方向,如数据增强、模型优化、特征工程等。 其他说明:尽管该模型在短期内表现出良好的预测性能,但仍存在一定局限性,如样本量较小、未考虑外部因素影响等。因此,在实际应用中需谨慎对待模型预测结果,并结合其他分析工具共同决策。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值