在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. pandas | DataFrame基础运算以及空值填充

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是pandas数据处理专题的第四篇文章,我们一起来聊聊DataFrame中的索引. 上一篇文章当中我们介绍了DataFrame数据结构当 ...

  2. PHP mb_substr() 函数

    实例 从字符串中返回 "菜鸟": <?php echo mb_substr("菜鸟教程", 0, 2); // 输出:菜鸟 ?> 定义和用法 mb_ ...

  3. 使用Flask开发简单接口(4)--借助Redis实现token验证

    前言 在之前我们已开发了几个接口,并且可以正常使用,那么今天我们将继续完善一下.我们注意到之前的接口,都是不需要进行任何验证就可以使用的,其实我们可以使用 token ,比如设置在修改或删除用户信息的 ...

  4. HTML学习笔记(一)——基础标签及常用编辑器技巧

    HTML 初识html 什么是html? html是超文本标记语言(hyper text markup language) html5的基本结构 <!DOCTYPE html> <! ...

  5. Java入门到实践系列(1)——Java简介

    一.Java的发展历史 Java是由SUN公司的开发人员James Gosling及其领导的一个开发小组与1995年开发并推出的一门高级编程语言.经过二十几年的发展已经成为最受程序员欢迎.使用最为普遍 ...

  6. 基于深度学习的人脸识别系统Win10 环境安装与配置(python+opencv+tensorflow)

    一.需要下载的软件.环境及文件 (由于之前见识短浅,对Anaconda这个工具不了解,所以需要对安装过程做出改变:就是Python3.7.2的下载安装是可选的,因为Anaconda已经为我们解决Pyt ...

  7. 使用QT实现一个简单的登陆对话框(纯代码实现C++)

    使用QT实现一个简单的登陆对话框(纯代码实现C++) 效果展示 使用的QT控件 控件 描述 QLable 标签 QLineEdit 行文本框 QPushButton 按扭 QHBoxLayout 水平 ...

  8. Java单元测试 Junit TestNG之介绍

    Junit是Java中默认的单元测试框架,通过注解的方式去识别测试方法 JUnit4 JUnit4通过注解的方式来识别测试方法.目前支持的主要注解有: @BeforeClass 全局只会执行一次,而且 ...

  9. Python 为什么能支持任意的真值判断?

    本文出自"Python为什么"系列,请查看全部文章 Python 在涉及真值判断(Truth Value Testing)时,语法很简便. 比如,在判断某个对象是否不为 None ...

  10. eclipse中启动tomcat出现错误的解决方法

    前段时间跟着老师做课设,各方面调试都没有问题.近段时间想起来,看看之前写过的代码,翻着翻着就发现启动tomcat出现了错误 错误如下: 错误原因:tomcat路径配置有问题,之前可能配置好了然后由于种 ...