jvm学习笔记内存与垃圾回收篇(代码片段)

九死九歌 九死九歌     2023-02-27     558

关键词:

一、JVM与java体系结构

1 虚拟机的结构

2 java的编译

  有句话说java是半编译半解释型语言,因为从*.java到*.class文件的过程是编译。而*.class文件的运行是解释。

  但其实*.class文件的运行过程是半编译半解释的。有些需要反复用到的字节码是直接编译成机器指令来执行的。就好比域名的解析,常用访问的域名解析是直接通过本地域名服务器,不常用的才会自顶向下层层解析。例如for循环中的代码是要重复利用的,就把这段字节码编译成机器指令缓存起来,每次循环都调用缓存区中的机器指令,而不需要进行解释。如图:

3 jvm的指令集

  指令集架构有基于栈的指令集架构和基于寄存器的指令集架构。

  jvm中使用的就是基于栈的指令集架构。它的好处是不需要硬件支持,可移植性高,零地址指令多。但性能不及寄存器式架构(如x86汇编)

  栈式架构必然是大量使用到栈。例如我们将如下代码进行反汇编:

package com.spd.jvm;

public class Test 

	public static void main(String[] args) 
		int i = 2;
		int j = 3;
		int k = 2 + 3;
	


  得到的结果是:

iconst_2	// 将int型数2压栈
istore_1	// 将栈顶int型存入本地变量1
iconst_3	// 将int型数3压栈
istore_2	// 将栈顶int型存入本地变量2
iconst_5	// 将int型数5压栈
istore_3	// 将栈顶int型存入本地变量3
return		// 从当前方法返回void

  而对x86汇编而言是这么写的:

mov ds : [0], 2		; 设ds : [0]为i
mov ds : [4], 3		; 设ds : [4]为j
mov eax, ds : [0]
add eax, ds : [4]
mov ds : [12], ax	; 设ds : [12]为k,k = ax = i + j
ret

  由此可见,确实比起x86汇编更多使用到了栈。另外也能看出来同一功能栈式架构比寄存器式架构需要用更多指令,但是他的指令集更小。

二、类加载子系统

1 类加载子系统的结构

2 类加载子系统的作用

  类加载子系统只负责类的加载,而类能否运行由执行引擎决定。

3 类加载的加载过程

  ① 通过一个类的全限定名获取定义此类的二进制字节流

  ② 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

  ③ 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

4 类加载的链接过程

  ① 验证:验证魔数及版本号。

  ② 准备:为类变量分配内存并设置为他们的初始值。初始化阶段才赋值。(不会分配final修饰的static,因为他们在编译过程就已经分配了。准备阶段只会进行显式初始化。也不会为实例变量分配初始化。)

package com.spd.jvm;

public class Test 

	private static int age = 18;		// prepare: age = 0		-> initial: age = 18
	private static char sex = '男';		// prepare: sex = '\\0'	-> initial: sex = '男'
	public static String name = "spd";	// prepare: name = null -> initial: name = "spd"

	public static void main(String[] args) 
		System.out.println("Hello world!");
	


  ③ 解析:将常量池中的符号引用转换成直接引用。

  符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是他们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。

  直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是一个能简介定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。

5 类加载的初始化过程

  ① <clinit>()方法:

  初始化过程实际上就是执行类构造器方法<clinit>()的过程。

  该方法不等同于类的构造器(类的构造器是<init>()函数),他不需要定义,是javac编译器自动收集类中的所有类变量类中的所有变量的赋值动作和静态代码块中的语句合并而来。因而如果类中没有静态代码块也没有静态变量,那就没有<clinit>()方法。

  有父类,则先执行父类<clinit>()

  虚拟机必须保证一个类的<clinit>()方法在多线程中被同步加锁。

package com.spd.jvm;

public class Test 

	private static int a = 1;

	static 
		a = 2;
		b = 20;
	

	public static int b = 10;

	public static void main(String[] args) 
		System.out.println("Hello world!");
	


  对这段代码反汇编查看字节码中的<clinit>()方法:

iconst_1			// 将int型1压入栈顶
putstatic #5		// 为指定类的静态域赋值,此处#5即为a(不知道需不需要弹栈)
iconst_2			// 将int型2压入栈顶
putstatic #5		// 为指定类的静态域赋值
bipush 20		// 将单字节常量值(-128 ~ 127)压入栈顶,此处为20
putstatic #6		// 为指定类的静态域赋值,此处#6即为b
bipush 10		// 将单字节常量值(-128 ~ 127)压入栈顶,此处为10
putstatic #6		// 为指定类的静态域赋值
return				// 从当前方法返回空

  如上便是<clinit>()方法的作用。那为什么静态代码块中b的赋值语句明明在b的声明语句之前,静态代码块还能给b赋值呢?因为准备阶段就给静态变量分配好了内存空间,赋值的过程是在其后的初始化过程中完成的。

prepare :
  a = 0, b = 0;

initial:
  a = 1;
  static
    a = 2;
    b = 3;
  
  b = 4;

  但是可以在静态代码块中前向赋值,却不可以前向调用。例如如下代码就会报错:

package com.spd.jvm;

public class Test 

	private static int a = 1;

	static 
		a = 2;
		b = 20;
		System.out.println(a);
		System.out.println(b);		// <- 此处报错
	

	public static int b = 10;

	public static void main(String[] args) 
		System.out.println("Hello world!");
	


6 类加载器的分类

  jvm支持两种类型的类加载器,引导类加载器和自定义类加载器。凡是派生于抽象类ClassLoader的类加载器都被划分为自定义类加载器。(扩展类加载器和系统类加载器等)

  引导类加载器、扩展类加载器、系统类加载器。三者并不存在上层下层关系,更不存在继承关系。而是呈现包含关系的。就像文件的层级目录一样。

  另外引导类加载器是用c/c++语言编写的,其他类加载器都是用java写的。

  我们可以用代码演示他们之间的异同。

package com.spd.jvm;

public class Test 
	public static void main(String[] args) 

		// 获取系统类加载器
		ClassLoader sys = ClassLoader.getSystemClassLoader();
		System.out.println(sys);

		// 获取其上层:扩展类加载器
		ClassLoader ext = sys.getParent();
		System.out.println(ext);

		// 试图获取其上层:引导类加载器
		ClassLoader bootstrap = ext.getParent();
		System.out.println(bootstrap);	// 获取不到,打印空

		// 自定义类默认使用系统类加载器
		ClassLoader loader1 = Test.class.getClassLoader();
		System.out.println(loader1);
		System.out.println(loader1 == sys);

		// java核心api中的类是用引导类加载器加载的。
		ClassLoader loader2 = String.class.getClassLoader();
		System.out.println(loader2);

	

jvm之内存与垃圾回收篇执行引擎(代码片段)

执行引擎执行引擎概述执行引擎属于JVM的下层,里面包括解释器、及时编译器、垃圾回收器执行引擎是Java虚拟机核心的组成部分之一。“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理... 查看详情

jvm之内存与垃圾回收篇程序计数器(代码片段)

程序计数器介绍JVM中的程序计数寄存器(ProgramCounterRegister)中,Register的命名源于CPU的寄存器,寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。这里,并非是广义上所指的物理寄存器,或许将其翻译为... 查看详情

jvm垃圾回收篇(重要概念解析)(代码片段)

重要概念解析1.System.gc方法2.内存溢出与泄露3.StopTheWorld4.垃圾回收的并行与并发5.安全点与安全区域1.System.gc方法基本概述默认情况下,通过调用System.gc()或者Runtime.getRuntime().gc()方法会显式触发FullGC的回收,然后对堆空间... 查看详情

jvm垃圾回收篇(重要概念解析)(代码片段)

重要概念解析1.System.gc方法2.内存溢出与泄露3.StopTheWorld4.垃圾回收的并行与并发5.安全点与安全区域1.System.gc方法基本概述默认情况下,通过调用System.gc()或者Runtime.getRuntime().gc()方法会显式触发FullGC的回收,然后对堆空间... 查看详情

jvm之内存与垃圾回收篇虚拟机栈(代码片段)

...样的功能需要更多的指令。有不少Java开发人员一提到Java内存结构,就会非常粗粒度地将JVM中的内存区理解为仅有Java堆(heap)和Java栈(stack)?为什么?首先栈是运行时的单位,而堆是存储的单位。 查看详情

<jvm上篇:内存与垃圾回收篇;11-垃圾回收概述及算法

垃圾回收概述及算法笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机)同步更新:https://gitee.com/vectorx/NOTE_JVMhttps://codechina.csdn.net/qq_35925558/NOTE_JVMhttps://github.com/uxiahnan/NOTE_JVM目录11.垃圾回收概述及算法11... 查看详情

jvm-内存与垃圾回收篇!女朋友看了都想当架构师的超详细保姆级笔记!呕心沥血之作!看完还不会你砍我!(代码片段)

1.JVM与Java体系结构1.1Java虚拟机Java虚拟机是一台执行Java字节码的虚拟计算机,它拥有独立的运行机制,其运行的Java字节码也未必由Java语言编译而成。JVM平台的各种语言可以共享Java虚拟机带来的跨平台性、优秀的垃圾回... 查看详情

jvm-内存与垃圾回收篇!女朋友看了都想当架构师的超详细保姆级笔记!呕心沥血之作!看完还不会你砍我!(代码片段)

1.JVM与Java体系结构1.1Java虚拟机Java虚拟机是一台执行Java字节码的虚拟计算机,它拥有独立的运行机制,其运行的Java字节码也未必由Java语言编译而成。JVM平台的各种语言可以共享Java虚拟机带来的跨平台性、优秀的垃圾回... 查看详情

jvm学习笔记一(垃圾回收算法)

一:垃圾回收机制的原因  java中,当没有对象引用指向原先分配给某个对象的内存时候,该内存就成为了垃圾。JVM的一个系统级线程会自动释放该内存块。垃圾回收意味着程序不再需要的对象是"无用信息",这些信息将被丢弃... 查看详情

jvm垃圾回收机制与内存回收(代码片段)

...etails/72857739垃圾回收机制 GC是垃圾回收机制,java中将内存管理交给垃圾回收机制,这是因为在面向对象编程中一个对象的生命周期往往无法预料,所以我们无法为每个对象指定回收时机。 但是我们可以采用System.gc()和Runt... 查看详情

jvm学习笔记gc——java语言的垃圾回收(代码片段)

一、垃圾回收概述1什么是GC,为何要GC?2早期垃圾回收3Java的垃圾回收机制二、垃圾回收的相关算法1垃圾回收算法的概述  垃圾回收分为标记阶段和清除阶段,标记阶段找垃圾,清除阶段清掉找到的垃圾。  ... 查看详情

jvm--垃圾回收

...,在对JVM有一定了解的基础上,接下来进行JVM垃圾收集的学习垃圾收集器与内存分配策略1.概述  内存的动态分配与内存回收技术已经很成熟了,了解GC和内存分配:一方面为了当出现内存溢出,内存泄漏的时候排查问题,另... 查看详情

我要写整个中文互联网界最牛逼的jvm系列教程|jvm内存与垃圾回收篇:jvm内存与垃圾回收篇概述

前言不错,正是在下,我就是你们亲爱的李阿昀,我李阿昀今天又开了一个坑,这个坑便是从现在开始以极其认真以至到变态的程度写出一份整个中文互联网界最牛逼的JVM系列教程,没有之一,注意,... 查看详情

jvm垃圾回收篇(基本概述)(代码片段)

...对象,这个对象就是需要被回收的垃圾如果不及时对内存中的垃圾进行清理,那么这些垃圾对象所占的内存空间会一直保 查看详情

jvm垃圾回收篇(垃圾回收器基本概述)(代码片段)

...直至垃圾收集工作结束在诸如单CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合,串行回收器的性能表现可以超过并行回收器 查看详情

jvm学习笔记------内存管理和垃圾回收

转自:http://blog.csdn.net/cutesource/article/details/5906705版权声明:本文为博主原创文章,未经博主允许不得转载。JVM内存组成结构JVM栈由堆、栈、本地方法栈、方法区等部分组成,结构图如下所示: 1)堆所有通过new创建的对象的... 查看详情

小林coding阅读笔记:操作系统篇之内存分配与回收(代码片段)

前言学习/导流:小林coding-4.2malloc是如何分配内存的?小林coding-4.3内存满了,会发生什么??学习意义理解linux操作系统如何设计内存分配\\回收的过程为学习JVM的内存管理奠定基础(JVM的内存布局及垃圾... 查看详情

小林coding阅读笔记:操作系统篇之内存分配与回收(代码片段)

前言学习/导流:小林coding-4.2malloc是如何分配内存的?小林coding-4.3内存满了,会发生什么??学习意义理解linux操作系统如何设计内存分配\\回收的过程为学习JVM的内存管理奠定基础(JVM的内存布局及垃圾... 查看详情