在Java高级特性——反射机制(第一篇)中,写了很多反射的实例,可能对于Class的了解还是有点迷糊,那么我们试着从内存角度去分析一下。

Java内存

从上图可以看出,Java将内存分为堆、栈、方法区其中方法区是一种特殊的堆

:堆中通常存放new的对象和数组,可以被所有的线程共享,不会存放别的对象引用。

:存放基本的变量类型(会包含这个基本类型的具体数值)以及引用对象的变量(会存放这个引用在堆里边的具体地址)。

方法区:可以被所有的线程共享,包含了所有的class和static变量。‘

类的加载过程(了解即可)

当程序主动使用某个类时,如果该类还未被加载到内存,系统会通过如下三个步骤对该类进行初始化:

第一步(加载):将类的 .class 文件读入内存,并将这些静态数据转化为方法区运行时的数据结构,然后生成一个代表这个类的java.lang.Class对象。这个过程由类加载器完成。

第二步(链接):将Java类的二进代码合并到JVM的运行环境JRE中,分为下面三个步骤:

        >验证:确保加载类的信息符合JVM规范,并没有安全方面的问题。

        >准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。

        >解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

第三部(初始化):JVM负责对类进行初始化,过程如下:

        >执行类构造器<clinit>()方法的过程,类构造器<clinit>()方法是由编译期自动收集类所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)

        >当初始化一个类的时候,如果发现其父类还没有初始化,则需要先触发其父类先初始化。

·         >虚拟机会保证一个类<clinit>()方法在多线程环境中正确加锁和同步。

举例测试:

package test;

import java.lang.annotation.ElementType;

public class Test{
public static void main(String[] args) {
A a = new A(); System.out.println(a.m); /*
* 1.加载到内存,会产生一个类对应的class对象
* 2.链接:链接结束后,m=0
* 3.初始化
* <clinit>(){
* System.out.println("A类的静态代码块被加载");
m=300;
m=100;
* }
* 最后:m=100
*/ } } class A{
static {
System.out.println("A类的静态代码块被加载");
m=300;
} static int m=100; public A() {
System.out.println("A类的无参构造方法初始化");
} }

运行结果:

类的初始化

什么时候会发生类的初始化?

1.类的主动引用(一定会发生类的初始化)

>当虚拟机启动时,先初始化main方法所在的类。

>new一个类的对象。

>调用类的静态成员(除了final常量)和静态方法。

>使用java.lang.reflect包的方法对类的反射进行调用。

>当初始化一个类,当父类没有被初始化,则会先初始化它的父类。

举例(2-1):

package test;

//测试类什么时候会被初始化
public class Test{
static {
System.out.println("main类被加载");
} public static void main(String[] args) throws ClassNotFoundException { //主动引用
//Son son = new Son(); //反射也会产生主动引用
Class.forName("test.Son"); //两者输出结果均一样
/*
* 结果为:
* main类被加载
* 父类被加载
* 子类被加载
*/
} } class Father{ static int b = 1; static {
System.out.println("父类被加载");
}
} class Son extends Father{
static {
System.out.println("子类被加载");
m = 300;
} static int m = 100; static final int M = 1;
}

打印结果为:

2.类的被动引用(不会发生类的初始化)

>当访问一个静态域时,只有真正声明这个域的类才会被初始化。例如:当通过子类引用父类的静态变量,不会导致子类初始化。

>通过数组定义类引用,不会触发此类的初始化。

>引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)。

举例(改变例2-1的main方法):

public static void main(String[] args) throws ClassNotFoundException {
//不会产生引用的方法
//System.out.println(Son.b); //Son[] sons = new Son[10]; System.out.println(Son.M);
}

执行结果一个一个去测试一下!

类加载器

>类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转化为方法区运行时数据结构,然后在堆中生成一个java.lang.Class对象,作为方法区中类数据的访问入口。

>类缓存:标准的Java SE类可以按要求查找类,但是一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过JVM垃圾回收机制(gc)可以回收这些Class对象。

类加载器的作用

类加载器的作用:把类(class)装载进内存。

JVM规范定义了如下类型的类加载器:

>引导加载器(Bootstap ClassLoader):用C++编写的,是JVM自带的类加载器,负责Java平台的核心库,用来装载核心类库(rt.jar),该加载器无法直接获取。

>扩展类加载器(Extension ClassLoader):负责jre\lib\ext目录下的jar包或 -D java.ext.dirs指定目录的jar包装入工作室。

>系统加载器(System ClassLoader):负责java -classpath 或者-D java.class.path所指目录下的类与jar包装入工作,是最常用的加载器。

 举例获取加载器:

package test;

public class Test{

    public static void main(String[] args) throws ClassNotFoundException {
//获取类加载器
ClassLoader c1 = ClassLoader.getSystemClassLoader();
System.out.println(c1); //获取类加载器的父类加载器-->拓展类加载器
ClassLoader c2 = c1.getParent();
System.out.println(c2); //获取拓展类加载器的父类加载器-->根加载器(C/C++ Tip:获取不到返回NULL)
ClassLoader c3 = c2.getParent();
System.out.println(c3); //测试当前加载器是由哪个类加载的
ClassLoader c4 = Class.forName("test.Test").getClassLoader();
System.out.println(c4); //测试JDK是由哪个加载器加载的
ClassLoader c5 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(c5); //如何获取系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path")); //双亲委派机制 学习链接(https://blog.csdn.net/shy415502155/article/details/88167713) /*
C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;
G:\test\bin
*/
} }

打印结果:

Java高级特性——反射机制(第二篇)的更多相关文章

  1. Java高级特性——反射机制(第一篇)

    ——何为动态语言,何为静态语言?(学习反射知识前,需要了解动态语言和静态语言) 动态语言 >是一类在运行时可以改变其结构的语言,例如新的函数.对象.甚至是代码可以被引进,已有的函数可以被删除或者 ...

  2. Java高级特性——反射机制(第三篇)

    获取类运行时的结构 通过反射获取运行时类的完整结构 Field.Method.Constructor.Superclass.Interface.Annotation >实现的全部接口 >所 ...

  3. Java高级特性——反射机制(完结)——反射与注解

    按照我们的学习进度,在前边我们讲过什么是注解以及注解如何定义,如果忘了,可以先回顾一下https://www.cnblogs.com/hgqin/p/13462051.html. 在学习反射和注解前, ...

  4. Java高级特性——反射

    感谢原文作者:peter_RD_nj 原文链接:https://www.jianshu.com/p/9be58ee20dee 注意:同一个类在JVM中只存在一份字节码对象 概述 定义 JAVA反射机制 ...

  5. java的反射机制(第二篇)

    本文转载自:http://c.biancheng.net/cpp/html/1781.html 要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的,这项工作是由“Class ...

  6. JAVA高级特性反射和注解

    反射: 枚举反射泛型注解.html34.3 KB 反射, 主要是指通过类加载, 动态的访问, 检测和修改类本身状态或行为的一种能力, 并能根据自身行为的状态和结果, 调整或修改应用所描述行为的状态和相 ...

  7. Java高级特性—反射和动态代理

    1).反射 通过反射的方式可以获取class对象中的属性.方法.构造函数等,一下是实例: 2).动态代理 使用场景: 在之前的代码调用阶段,我们用action调用service的方法实现业务即可. 由 ...

  8. Java高级特性 第5节 序列化和、反射机制

    一.序列化 1.序列化概述 在实际开发中,经常需要将对象的信息保存到磁盘中便于检索,但通过前面输入输出流的方法逐一对对象的属性信息进行操作,很繁琐并容易出错,而序列化提供了轻松解决这个问题的快捷方法. ...

  9. 浅说Java中的反射机制(一)

    在学习传智播客李勇老师的JDBC系列时,会出现反射的概念,由于又是第一次见,不免感到陌生.所以再次在博客园找到一篇文章,先记录如下: 引用自java中的反射机制,作者bingoideas.(()为我手 ...

随机推荐

  1. Python execfile() 函数

    描述 execfile() 函数可以用来执行一个文件.每组词 www.cgewang.com 语法 以下是 execfile() 方法的语法: execfile(filename[, globals[ ...

  2. PHP floor() 函数

    实例 向下舍入为最接近的整数: <?phpecho(floor(0.60) . "<br>");echo(floor(0.40) . "<br&g ...

  3. number类型转date类型

    遇到用数字记录日期时,进行查询转换. create or replace function num_to_date(s in number) return dateisbegin return to_ ...

  4. Tarjan 做题总结

    这两天Tarjan复习完后把题做了做.洛谷题单<图的连通性>已经做得差不多了.大部分是Tarjan的题,所以写一篇小总结. T1 [模板] 缩点 不多bb.我已经写过关于Tarjan模板的 ...

  5. Python实现各类验证码识别

    项目地址: https://github.com/kerlomz/captcha_trainer 编译版下载地址: https://github.com/kerlomz/captcha_trainer ...

  6. Docker初探之常用命令

    在正式使用Docker之前,我们先来熟悉下Docker中常用的命令,因为对Docker的操作就如同操作Linux一样,大部分操作通过命令完成. 一.登录 为什么要使用登录? 因为我们使用Docker, ...

  7. 教你看懂Docker和K8S!

    转载于 https://my.oschina.net/jamesview/blog/2994112 2010年,几个搞IT的年轻人,在美国旧金山成立了一家名叫“dotCloud”的公司. 这家公司主要 ...

  8. Bytom侧链Vapor源码浅析-节点出块过程

    Bytom侧链Vapor源码浅析-节点出块过程 在这篇文章中,作者将从Vapor节点的创建开始,进而拓展讲解Vapor节点出块过程中所涉及的源码. 做为Vapor源码解析系列的第一篇,本文首先对Vap ...

  9. java方法与方法的重载

    一 方法 1.方法的概述 在java中,方法就是用来完成解决某件事情或实现某个功能的办法. 方法实现的过程中,会包含很多条语句用于完成某些有意义的功能——通常是处理文本, 控制输入或计算数值.我们可以 ...

  10. C#LeetCode刷题之#641-设计循环双端队列(Design Circular Deque)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4132 访问. 设计实现双端队列. 你的实现需要支持以下操作: M ...