jvm(一.基础入门)

JVM总体概述

1.堆——堆是所有线程共享的,主要用来存储对象。是访问数据的入口 其中,堆可分为:年轻代老年代两块区域。使用NewRatio参数来设定比例。对于年轻代,一个Eden区和两个Suvivor区,使用参数SuvivorRatio来设定大小;

2.ava虚拟机栈——线程私有的,主要存放局部变量表,操作数栈,动态链接和方法出口等;

3.程序计数器——同样是线程私有的,记录当前线程的行号指示器,为线程的切换提供保障;

4.方法区——线程共享的,主要存储类信息、常量池、静态变量、JIT编译后的代码等数据。方法区理论上来说是堆的逻辑组成部分;

5.运行时常量池——是方法区的一部分,用于存放编译期生成的各种字面量和符号引用;

Jdk1.6及之前:常量池分配在永久代 。

Jdk1.7:有,但已经逐步“去永久代” 。

Jdk1.8及之后:无永久代,改用元空间代替(java.lang.OutOfMemoryError: PermGen space,这种错误将不会出现在JDK1.8中)。

Classloader类加载器原理

类加载器加载我们的class文件,并且经历过验证、准备、解析,在初始化我们该类。

Class文件读取来源

  1. 本地磁盘文件 java代码编译后的class文件
  2. 通过网络下载的class文件
  3. War、jar 解压的class文件
  4. 从专门的数据库中读取的class文件
  5. 使用java cglib、动态代理生成的代理类class文件

#链接#

验证

验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。
Java是相对C++语言是安全的语言,例如它有C++不具有的数组越界的检查。这本身就是对自身安全的一种保护。
验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一道重要的防线,越是严谨的验证机制越安全。
验证的目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。
其主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证

 四种验证做进一步说明:
1.文件格式验证:主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。
2.元数据验证:对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范。
3.字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。
4.符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。

准备

类准备阶段负责为类的静态变量分配内存,并设置默认初始值。(被final  修饰的直接赋值  底层原理其实就是给一个constantValue的标识)
ConstantValue属性到底是干什么的呢?
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值,只有被static修饰的变量才可以使
用这项属性。非static类型的变量的赋值是在实例构造器方法中进行的;static类型变量赋值分两
种,在类构造其中赋值,或使用ConstantValue属性赋值。
在实际的程序中,我们什么时候才会用到ContstantValue属性呢?
在实际的程序中,只有同时被final和static修饰的字段才有ConstantValue属性,且限于基本类型
和String。编译时Javac将会为该常量生成ConstantValue属性,在类加载的准备阶段虚拟机便会
根据ConstantValue为常量设置相应的值,如果该变量没有被final修饰,或者并非基本类型及字
符串,则选择在类构造器中进行初始化。
为什么ConstantValue的属性值只限于基本类型和string?
因为从常量池中只能引用到基本类型和String类型的字面量

解析

将类的二进制数据中的符号引用替换成直接引用。
说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。
直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。

初始化

初始化阶段是执行类构造器()方法的过程。
或者讲得通俗易懂些
在准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的
主观计划去初始化类变量和其他资源,比如赋值。
在Java中对类变量进行初始值设定有两种方式:
声明类变量是指定初始值
使用静态代码块为类变量指定初始值
按照程序员的逻辑,你必须把静态变量定义在静态代码块的前面。因为两个的执行是会根据代码编写的
顺序来决定的,顺序搞错了可能会影响你的业务代码。
JVM初始化步骤:
假如这个类还没有被加载和连接,则程序先加载并连接该类
假如该类的直接父类还没有被初始化,则先初始化其直接父类
假如类中有初始化语句,则系统依次执行这些初始化语句

使用

主动引用

只有当对类的主动使用的时候才会导致类的初始化,类的主动使用有六种:
  •   创建类的实例,也就是new的方式
    
  •   访问某个类或接口的静态变量,或者对该静态变量赋值
    
  •   调用类的静态方法
    
  •   反射(如 Class.forName(“com.carl.Test”) )
    
  •   初始化某个类的子类,则其父类也会被初始化
    
  •   Java虚拟机启动时被标明为启动类的类( JvmCaseApplication ),直接使用 java.exe 命令来运行某个主类
    

被动引用

  • 引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化。
  • 定义类数组,不会引起类的初始化。
  • 引用类的static final常量,不会引起类的初始化(如果只有static修饰,还是会引起该类初始化的)。
package com.draven;

	public class Test01 {

   	 public static void main(String[] args) {
        System.out.println(Test_01_B.str);
   	 }
	}

	class Test_01_A {
   		 public static String str = "A str";

    static {
        System.out.println("A static Block");
   	 }
	}

	class Test_01_B extends Test_01_A {
    static {
        System.out.println("B Static Block");
   	 }
	}
/*输出结果:A static Block
		A str
	*/
	
	public class Test01 {

    	public static void main(String[] args) {
        	System.out.println(Test_01_B.str);
    	}
	}

	class Test_01_A {

   		 static {
        	System.out.println("A static Block");
   		 }
	}

	class Test_01_B extends Test_01_A {
    	public static String str = "B str";
    		static {
       			 System.out.println("B Static Block");
    				}
		}

/*输出结果:A static Block
        B Static Block
        B str

*/

public class Test01 {
	public static void main(String[] args) {
   		 System.out.println(Test_01_A.str);
	 }
}

class Test_01_A {
	public static final String str = "B str";
		static {
   	 System.out.println("A static Block");
 	}
}

//输出结果是:B str

public class Test01 {

 public static void main(String[] args) {
    System.out.println(Test_01_A.str);
 }
}

class Test_01_A {
public static final String str = UUID.randomUUID().toString();
static {
    System.out.println("A static Block");
 }
}

输出结果:A static Block
88f2da3f-dbde-4807-b845-1d8b7863d16c

**结论:

  • 对于静态字段来说,只有直接定义了该字段的类才会被初始化(类是懒加载的)
  • 常量在编译阶段会存入到调用这个常量的方法所在类的常量池中,本质上,调用类并没有直接引用到定义常量的类,因此并不会触发定义常量的类的初始化
  • 当一个常量的值并非编译期间可以确定的,那么其值就不会被放到调用类的常量池中,这时在程序运行时,会导致主动使用这个常量所在的类,显然会导致这个类被初始化**

注意:
当一个接口在初始化时,并不要求其父接口都完成了初始化,只有真正使用父接口的时候(如引用接口中所定义的常量时),才会初始化
public class Test01 {

public static void main(String[] args) {
    System.out.println(Test_01_A_A.b);
}
}

interface Test_01_A {

int a=1;
}

interface Test_01_A_A extends Test_01_A{
int b=2;
}

因为在接口中定义的属性会默认的被 public static final 修饰,也就是说在编译阶段Test_01_A_A接口的b 会在Test01的常量池中生成

public class Test01 {

public static void main(String[] args) {
    Singletong instance = Singletong.getInstance();
    System.out.println(Singletong.a);
    System.out.println(Singletong.b);
}
}

class Singletong {
public static int a;
private static Singletong singletong = new Singletong();

private Singletong() {
    a++;
    b++;
}

public static int b = 0;

public static Singletong getInstance() {
    return singletong;
}
}

输出结果:1 0

执行解读流程:
执行main方法中Singletong.getInstance();时,因为调用了Singletong类的静态方法,所以会导致Singletong的初始化
但是在初始化执行之前,会有一个加载,链接(验证,准备,解析)的操作
会先把Singletong.class 文件加载内存进行验证等工作,在准备工作时,会给一些静态的属性赋予默认值。
a=0;
singleton=null;
构造器在准备阶段是没有执行的
b=0;
前面提过因为调用了getInstance导致的初始化
初始化操作
a=0;
new Singletong会把实力对象赋值给singleton(执行了构造方法)
a=1;
b=1;
最后执行b初始化的时候
b=0;

####类的加载 ####
类的加载的最终产品是位于内存中的Class对象

Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口

类加载器并不需要等到某个类被“首次主动使用”时再加载它

Jvm 规范允许类加载器在预料某个类将要被使用时就预先加载他,如果在预先加载的过程中遇到.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误

如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

类的初始化时机

主动使用(七种,重要)

  • 创建类的实例
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射(如Class.forName(“com.test.Test"))
  • 初始化一个类的子类-
  • Java虚拟机启动时被标明为启动类的类
  • JDK1.7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF getStatic,REF putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化

重点:

  • 除了上述七种情形,其他使用Java类的方式都被看作是被动使用,不会导致类的初始化
  • 当Java虚拟机初始化一个类时,要求它的所有父类都已经被初化,但是这条规则并不适用于接口。
  • 在初始化一个类时,并不会先初始化它所实现的接口。
  • 在初始化一个接口时,并不会先初始化它的父接口。

因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。

什么是类加载器?

  • 负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例的代码模块。

  • 类加载器除了用于加载类外,还可用于确定类在Java虚拟机中的唯一性。

    一个类在同一个类加载器中具有唯一性(Uniqueness),而不同类加载器中是允许同名类存在的,
    这里的同名是指全限定名相同。但是在整个JVM里,纵然全限定名相同,若类加载器不同,则仍
    然不算作是同一个类,无法通过 instanceOf 、equals 等方式的校验。

类加载器的分类

  • 启动(Bootstrap)类加载器:加载JVM自身工作需要的类,它由JVM自己实现。它会加载$JAVA_HOME/jre/lib下的文件 底层是C语言实现
  • 扩展(Extension)类加载器:它是JVM的一部分,由sun.misc.LauncherExtClassLoader实现,他会加载ExtClassLoader实现,他会加载ExtClassLoader实现,他会加载JAVA_HOME/jre/lib/ext目录中的文件(或由System.getProperty(“java.ext.dirs”)所指定的文件)。 底层是Java实现
  • (应用)AppClassLoader 类加载器:应用类加载器,我们工作中接触最多的也是这个类加载器,它由sun.misc.Launcher $AppClassLoader实现。他加载我们工程目录classpath下的class及jar包 底层是java实现
  • 自定义类加载器: 也就是用户自己定义的类加载器

不管是 扩展(Extension)类加载器还是(应用)AppClassLoader 类加载器,他们俩个都是ClassLoader类的子类,当我们自定义类加载器的时候,也是继承ClassLoader类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值