Java学习笔记-全栈-Java基础-04-内存分析、类初始化与类加载

在总体上,Jvm包含两个内存区,栈stack,堆heap(堆包含method area)。

一、栈

  • 描述方法执行的内存模型,每个方法被调用都会创建一个栈帧(存储局部变量,操作数,方法出口等)
  • JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)
  • 栈属于线程私有,不能实现线程间的共享
  • 先入后出
  • 栈是由系统自动分配,速度快,是一个连续的内存空间

二、堆

  • 用于存储对象
  • JVM只有一个堆被所有线程共享
  • 堆是一个不连续的内存空间,分配灵活,速度慢

    假设上图程序为Test.java 文件,则在命令行中编译后运行的命令是:java Test;这意味着,一开始直接执行整个类,因此最先构造方法区,将类中相关信息保存在方法区中,然后压main函数栈。

Method area(方法区、静态区)

  • JVM只有一个方法区,被所有线程共享。
  • 方法区实际也是堆,只是用于存储类、常量相关的信息。
  • 用来存放程序中永远是不变或唯一的内容。(类信息(代码)、静态变量、静态方法、字符串常量等)

此时可以解释为什么字符串是不可变对象,当类加载的时候,字符串已经被放在method area中,对于相同字符串内容的对象(如String a="Hello"和String b=“Hello”)实际指向的是在method area中的同一个字符串常量
一般情况下,Method area在类加载时已经确定,若对其操作(修改字符串),自然是无效的,只能创建新的变量。

常量池

  • 全局字符串常量池String Pool
    • 类加载完成后,在堆中生成字符串对象实例,存放字符串常量的引用值。
  • Class文件常量池Class Constant Pool
    • 在编译阶段,存放常量(文本字符串、final常量等)和符号引用。
  • 运行时常量池Runtime Constant Pool
    • 类加载完成后,将每个在Class Constant Pool中的符号引用转存到Runtime Constan Pool(即,每个class都有一个Runtime Constant Pool)。类解析之后,符号引用替换为直接引用,与String Pool引用值保持一致。

三、类加载过程

1. 加载

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。需要类加载器的参与

2. 链接

将Java类的二进制代码合并到JVM的运行状态之中的过程

  • 验证:
    • 确保加载的类信息符合JVM规范,进行安全检测
  • 准备
    • 正式为类变量(static修饰)分配内存并设置变量初始值的阶段,这些内存都将在方法区中进行分配
  • 解析
    • Method area中的符号引用替换为直接引用

3. 初始化(重要

  • 1.执行类构造器<clinit>()方法的过程:由编译器自动收集类中的所有类变量静态语句块
  • 2.初始化一个类时,若其父类还没有进行初始化,则先对其父类发起初始化(继承树回溯初始化
  • 3.JVM会保证类构造器<clinit>()在多线程中被正确加锁和同步
  • 4.当访问一个Java类的静态域时,只有真正声明这个域的类才会被初始化

对于初始化的解释
初始化的过程非常重要,需要明确其中的每一步。

  • 假设代码如下
class Parent {
	static {
		System.out.println("父类被初始化");
	}
}

class Son extends Parent{
	static {
		System.out.println("子类被初始化");
	}
}


public class Test{
	public static void main(String[] args) {
		Son p1 = new Son();
	}
}
  • 输出结果:
父类被初始化
子类被初始化

涉及知识点:
1. 静态语句块被收集
2. 继承树回溯初始化


  • 假设代码如下
package xmlStudy;


class Parent {
	
	static String string="parent";
	
	static {
		System.out.println("父类被初始化");
	}
	
}

class Son extends Parent{
	static {
		System.out.println("子类被初始化");
	}
}

public class Test{
	public static void main(String[] args) {		
		System.out.println(Son.string);
	}
}
  • 输出结果:
父类被初始化
parent
  • 代码分析

我在parent中加了一个静态变量string,然后在main中使用Son指向string,根据4.当访问一个Java类的静态域时,只有真正声明这个域的类才会被初始化,只有父类会被初始化

四、类的引用

1. 类的主动引用

类的主动引用一定会发生类的初始化

  • new一个类对象
  • 调用类的静态域(成员和方法),不包括final常量
  • 使用java.lang.reflect包的方法堆类进行反射调用
  • 虚拟机启动类,如命令行编译后执行 java Test ,则Test类一定会被初始化
  • 继承树回溯初始化,当父类没有被初始化时,优先初始化父类。

2. 类的被动引用

类的被动引用不会发生类的初始化

  • 访问静态域时,真正声明这个域的类才会被初始化(通过子类引用父类的静态变量,不会导致子类初始化,参照上面代码)
  • 通过数组定义类引用,不会导致类的初始化
  • 引用常量不会触发初始化(常量在编译阶段就被放入method area中

五、类加载

1. 树状组合结构

  • 引导类加载器(bootstrap):
    • 用于加载java最底层核心库的内容(jre/lib/rt.jar,sun.boot.class.path),C语言编写
    • 加载扩展类和应用程序类加载器,并指定他们的父类加载器
  • 扩展类加载器(extensions):
    • 用于加载扩展库(jre/ext/*.jar,java.ext.dirs)
    • 由sun.misc.Launcher$ExtClassLoader实现
  • 应用程序类加载器(application)
    • 根据类路径(classpath, java.class.path)加载,一般的应用类都由其完成加载。
    • 由sun.misc.Launcher$AppClassLoader实现
  • 自定义类加载器
    • 通过继承java.lang.ClassLoader实现自定义

除了引导类使用C写的,其他都是java写的(继承Java.class.ClassLoader类)

2. Java.class.ClassLoader类

作用:

  • 根据指定类名称,找到或生成对应的字节码,然后从这些字节码中定义出一个Java实例。
  • 负责加载Java应用所需资源,如配置文件、图像文件等。

3. 类加载器模式:双亲委托***模式

接收到加载类的请求时,先层层上递给父类(直到最高的引导类加载器),若父类无法加载,再往下放一级,重复直到加载成功。

这种模式能够保证核心库的安全,比如,不可能出现用户定义Object类的情况。

但并非所有的类加载器都是这种模式,tomcat服务器的类加载器恰恰相反,由子类加载,子类加载失败再层层委托给父类进行加载。

4. 常见自定义类加载器:

1.文件系统类加载器

2.网络类加载器

3.解密加载器

  • 将代码通过IO流进行加密
  • 通过自定义的类加载器,实现对类的解密加载。

4.线程上下文类加载器:

  • 由于某些API由Boot或Ext加载,而第三方厂商提供的“实现”(如JDBC)却是由App加载器加载,这就导致API与“实现”不匹配的情况(双亲委派机制导致)。这种问题称为API+SPI(service provide interface)问题。线程上下文类加载器用于解决此类问题。
  • 常见的SPI由JDBC、JCE、JNDI、JAXP和JBI等。

5. 类加载器常见问题

  • 一般情况下,保证同一个类关联的其他类都是由当前类的类加载器共同加载
  • 需要动态加载资源时,至少可使用
    • system classloader or application classloader
    • 当前类加载器
    • 当前线程类加载器
      • 每个线程都有一个关联的上下文类加载器,可用其避开双亲委派加载链。
      • 使用new Thread()创建的线程,将自动继承父线程的类加载器。
      • 若不进行更改,程序中的所有线程都将使用系统类加载器作为上下文类加载器。
    • Thread.currentThread().getContextClassLoader()
全部评论

相关推荐

10-19 10:28
已编辑
成都理工大学 后端工程师
团孝子已上线feeling:面了很多家公司,能感受到目前只有小公司+外包喜欢问八股。大厂虽然也问八股,但是是从实习、项目中进行提问,并且大厂会问很深,面试官也会对你的回答进行思考➕追问,所以准备大厂面试前一定要备好相关资料。对于算法,我做的是codetop前100+力扣hot100+力扣高频150,面试中实感hot100就足够,基本上只要是hot100就秒答。对于项目和八股,我做的也是烂大街的星球项目,八股则是看小林和问ai,自己也写了很多技术博客和画了很多思维导图,并且自己也尝试用嘴巴说出来,不只停留于纸面。运气也很重要,必须要让面试官/HR看到简历才行,所以建议投递时间是下午两点。tl:第一岗位9.9&nbsp;投递9.10&nbsp;一面(一面评价:最近见过最强的大三,结束五分钟后约二面,都晚上九点了不下班吗)9.11&nbsp;二面(三道算法a出两道,反问评价:经验不够等横向,我实习生要啥经验)9.21挂(实习时间过短+其他原因,想要一年实习的,为什么不招个正职)第二岗位10.10投递10.11约面(主管打电话,说看到我之前投递记录了想要我挂qa职进去干后端,同意)10.14&nbsp;一面(无八股,主动说确实很强,意愿很强)10.16&nbsp;oc其余,友邦,东软,东华,惠择,用友oc已拒京东测开一面挂(投后端被测开捞)腾讯测试已拒(投后端被测开捞)ps:表扬惠择的主管面,没怎么问技术(可能是一面面试官沟通过了),全程一起讲大道理,解答了心中很多疑惑,也告诉我以面试官角度来看怎么选候选人,如果可以下次一定选惠择
HeaoDng:美团好像可以触发一面通
点赞 评论 收藏
分享
丿南烟丶:黑白模板吧,不要这样花哨的。 主要成就太空了,和获奖融在一起,写一两行就行了。 职业技能不要这样排,就传统的掌握精通什么什么然后举例补充的一些重要技术点。 自我介绍说实话也没啥用,可以删了。 把自己的两个项目方案细节补充上去,为什么这样设计,怎么设计,成果是什么按star法则来写 你要引导面试官来问你的技能和项目,你的获奖和自我介绍别人可能看都不看一眼或者不太在乎,重要的是展示你能干活的能力
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务