参考文献:
通过一个类的全限定名
来获取描述此类的二进制字节在这里插入代码片
流这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载在这里插入代码片
器”。
BootstrapClassLoader
启动类类加载器
它用来加载<JAVA_HOME>/jre/lib路径
,-Xbootclasspath参数指定的路径以<JAVA_HOME>/jre/classes中的类。BootStrapClassLoader是由c++实现的。
ExtClassLoader
拓展类类加载器
用来加载<JAVA_HOME>/jre/lib/ext
路径以及java.ext.dirs
系统变量指定的类路径下的类。
AppClassLoader
‘ 应用程序类类加载器
主要加载应用程序ClassPath
下的类(包含jar包中的类)。它是java应用程序默认的类加载器
。
用户自定义类加载器
用户根据自定义需求,自由的定制
加载的逻辑,继承AppClassLoader,仅仅覆盖findClass
()即将继续遵守双亲委派
模型。
在虚拟机启动的时候会初始化BootstrapClassLoader,然后在Launcher
类中去加载ExtClassLoader、AppClassLoader,并将AppClassLoader的parent设置为ExtClassLoader,并设置线程上下文类加载器。
Launcher是JRE中用于启动程序入口main()的类
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//加载扩展类类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//加载应用程序类加载器,并设置parent为extClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//设置默认的线程上下文类加载器为AppClassLoader
Thread.currentThread().setContextClassLoader(this.loader);
//此处删除无关代码。。。
}
因为BootstrapClassLoader是由c++实现的,所以并不存在一个Java的类。
双亲委派模型能保证基础类仅加载一次
,不会让jvm中存在重名的类。比如String.class,每次加载都委托给父加载器,最终都是BootstrapClassLoader,都保证java核心类都是BootstrapClassLoader加载的,保证了java的安全与稳定性。
自己实现ClassLoader时只需要继承ClassLoader类,然后覆盖findClass(String name)方法即可完成一个带有双亲委派模型的类加载器。
我们看下ClassLoader#loadClass的代码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 查看是否已经加载过该类,加载过的类会有缓存,是使用native方法实现的
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//父类不为空则先让父类加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//父类是null就是BootstrapClassLoader,使用启动类类加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父类类加载器不能加载该类
}
//如果父类未加载该类
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//让当前类加载器加载
c = findClass(name);
}
}
return c;
}
}
子类只需要实现findClass,关心从哪里加载即可。
因为它和ExtClassLoader都是Launcher的静态类,都是包访问路径权限的。
省略一堆代码,请参考文献。
Test test = (Test)o;
o.printVersion();
Test.class会隐性的被加载当前类的ClassLoader加载,当前Main方法默认的ClassLoader为AppClassLoader,而不是我们自定义的MyClassLoader。
会抛出ClassCastException,因为一个类,就算包路径完全一致,但是加载他们的ClassLoader不一样,那么这两个类也会被认为是两个不同的类。
Hash也称为散列、哈希
。对应的英文就是Hash
。基本原理就是把任意长度
的输入,通过Hash算法转出固定长度
的输出。
Hash的特点:
反向推导
出原始的数据微小变化
会得到完全不同的hash值,相同的数据会得到相同的值执行效率
要高效,长的文本也能快速地计算出哈希值冲突
概率要小LinkedHashMap其实就是可以看成HashMap的基础上,多了一个双向链表来维持顺序
。
String str1 = "abcd";
String str2 = new String("abcd");
System.out.println(str1==str2);//false
第一种方式是在常量池中拿对象,第二种方式是直接在堆内存空间创建一个新的对象。
双引号声明
出来的 String 对象会直接存储在常量池中。String.intern()
是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。String s1 = new String("计算机");
String s2 = s1.intern();
String s3 = "计算机";
System.out.println(s2);//计算机
System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象一个是常量池中的String对象,
System.out.println(s3 == s2);//true,因为两个都是常量池中的String对象
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";//常量池中的对象
String str4 = str1 + str2; //在堆上创建的新的对象
String str5 = "string";//常量池中的对象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的花,可以使用 StringBuilder
或者 StringBuffer
。
String s1 = new String("abc");// 堆内存的地值值
String s2 = "abc";
System.out.println(s1 == s2);// 输出false,因为一个是堆内存,一个是常量池的内存,故两者是不同的。
System.out.println(s1.equals(s2));// 输出true
先有字符串"abc"放入常量池,然后 new 了一份字符串"abc"放入Java堆(字符串常量"abc"在编译期就已经确定放入常量池,而 Java 堆上的"abc"是在运行期初始化阶段才确定
),然后 Java 栈的 str1 指向Java堆上的"abc"。
volatile是JVM(Java Memory Model)中用于保证可见性和有序性的轻量级同步机制。
它主要是有两个作用,一是保证被修饰共享变量的可见性,也就是多个线程操作读写时,能被其他线程感知到,在读取变量时会强制将主内存中的变量值读取到自己的工作内存中,写入变量时又会强制自己的新值刷新回主内存;另外一个重要作用在于阻止指令重排序。我们所熟知的双检测单例中,instance必须要用volatile修饰,原因是new SingleTon时,一般说有三个步骤(字节码):
- 分配一块内存
- 在内存上初始化SingleTon对象
- 把这块内存地址返回值赋值给 instance
但是经过编译器的优化,2、3的顺序有可能是颠倒的,也就是说可能你拿到的instance可能还没有被初始化,访问instance的成员变量就可能发生空指针异常,而volatile可以阻止这种情况的发生。
使用线程分三步骤:
- 创建线程
- 任务执行
- 销毁线程
备注:创建和销毁线程都会消耗系统资源,影响性能。
线程池:
已经提前创建好线程,用的时候直接执行任务,通过阻塞队列保证线程不会结束退出。
使用:
主要涉及到三大参数:
当然还有三个不太重要的参数:
相对线程而言,有以下几点优势:
从0到1实现自己的阻塞队列(上)
从0到1实现自己的阻塞队列(下)
多线程中那些看不见的陷阱
new 申请内存空间(半初始化)
invokespecial 初始化
astore_1 对象和变量建立关联关系,也就是赋值
volatile作用:
注意:创建对象非原子性操作
。
markword
8个字节)—— 锁信息、HashCode、分代年龄等class pointer
4个字节)instance
data
)padding
)length
4字节)- 数组特有句柄,效率偏低(两次访问),但是对于垃圾回收不用频繁修改t
直接指针,效率高(直接访问),但是垃圾回收需要频繁修改t
如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
如果使用直接指针访问,那么 Java 堆对像的布局中就必须考虑如何防止访问类型数据的相关信息,reference 中存储的直接就是对象的地址。
涉及到Age(4个bit,也就是0-15)
JVM的内存空间分为3大部分:
堆内存
堆内存可以划分为新生代
和老年代
,新生代中还可以再次划分为Eden
区、From Survivor区和To Survivor区。
方法区
栈内存
栈内存可以再细分为java虚拟机栈
和本地方法栈
几乎
所有的new对象都是存在heap中(请注意逃逸分析栈上分配)新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio
来指定 )
默认的,Eden : from : to = 8 : 1 : 1
( 可以通过参数 –XX:SurvivorRatio
来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
类信息、常量、静态变量
、是各个线程共享
的内存区域。HotSpot
JVM,区域叫做“永久代(permanent
generation)”。永久代是一片连续的堆空间,在JVM启动之前通过在命令行设置参数-XX:MaxPermSize
来设定永久代最大可分配的内存空间,默认大小是64M
(64位JVM默认是85M)。metadata
)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory。方法区或永生代相关设置
-XX:PermSize=64MB 最小尺寸,初始分配
-XX:MaxPermSize=256MB 最大允许分配尺寸,按需分配
XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled 设置垃圾不回收
默认大小
-server选项下默认MaxPermSize为64m
-client选项下默认MaxPermSize为32m
线程私有
,生命周期与线程相同。创建线程的时候就会创建一个java虚拟机栈。虚拟机执行java程序的时候,每个方法都会创建一个栈帧,栈帧存放在java虚拟机栈中,通过压栈出栈的方式进行方法调用。局部变量表
、操作数栈
、动态连接
、方法出口
等。局部变量存放在java虚拟机栈的局部变量表中
。本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务
。
Xms
设置堆的最小空间大小。Xmx
设置堆的最大空间大小。Xmn
:设置年轻代大小XX:NewSize
设置新生代最小空间大小。XX:MaxNewSize
设置新生代最大空间大小。XX:PermSize
设置永久代最小空间大小。XX:MaxPermSize
设置永久代最大空间大小。Xss
设置每个线程的堆栈大小要说动态代理,必须先聊聊静态代理。
假设现在项目经理有一个需求:在项目现有所有类的方法前后打印日志。
你如何在不修改已有代码
的前提下,完成这个需求?
静态代理的做法:
代理对象 = 增强代码 + 目标对象(原对象)
。有了代理对象后,就不用原对象了程序员要手动为每一个目标类编写对应的代理类。如果当前系统已经有成百上千个类,工作量太大了。
现在我们的努力方向是:如何少写或者不写代理类
,却能完成代理功能?
所谓的Class对象,是Class类的实例,而Class类是描述所有类的,比如Person类,Student类
可以看出,要创建一个实例,最关键的就是得到对应的Class对象
。
那么这里就会抛出一个问题:
能否不写代理类,而直接得到代理Class对象,然后根据它创建代理实例(反射)
。
代理类和目标类理应实现同一组接口。
之所以实现相同接口,是为了尽可能保证代理对象的内部结构和目标对象一致,这样我们对代理对象的操作最终都可以转移到目标对象身上,代理对象只需专注于增强代码的编写
。
但是别忘了,接口是无法创建对象的,怎么办?
JDK提供了java.lang.reflect.InvocationHandler
接口和 java.lang.reflect.Proxy
类。
Proxy有个静态方法:getProxyClass(ClassLoader, interfaces)
,只要你给它传入类加载器和一组接口,它就给你返回代理Class对象。
用通俗的话说,getProxyClass()这个方法,会从你传入的接口Class中,“拷贝”类结构信息到一个新的Class对象中,但新的Class对象带有构造器,是可以创建对象
的。
一旦我们明确接口,完全可以通过接口的Class对象,创建一个代理Class,通过代理Class即可创建代理对象。
所以,按我理解,Proxy.getProxyClass()这个方法的本质就是:以Class造Class
。
根据代理Class的构造器创建对象时,需要传入InvocationHandler。
通过构造器传入一个引用,那么必然有个成员变量去接收
。
代理对象的内部确实有个成员变量invocationHandler,而且代理对象的每个方法内部都会调用handler.invoke()!而且代理对象的每个方法内部都会调用handler.invoke()!
参照一个改进写法:
文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文
文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作 导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释: cwy_init/init_123..._达梦数据库导入导出
文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js
文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...
文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure
文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf