1.java底层原理简析
往往,在现在开发过程中,有很多操作,虽然功能都能去实现,但是在Jvm的内存分配上,是大有不同的,很可能两个不同的实现方式,性能上也会有或多或少差异……
例如:
private Integer name = 4;
private static Integer name = 4;
private final static Integer name 4;
private final Integer name =4;
它们的name对应的值都是4,但是否思考过,究竟有何实际区别呢?
所以接下来,我们进入主题,去讲解究竟有什么区别吧!
1.1 Java里的堆(heap)栈(stack)和方法区(method)
栈区:
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
堆区:
1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身 。
方法区:
1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间。有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。
JVM内存模型可以分为两个部分,如下图所示,堆和方法区是所有线程共有的,而虚拟机栈,本地方法栈和程序计数器则是线程私有的。下面我们就来分析一下这些不同区域的作用。
总结一下:
堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
方法区存着类的信息,常量和静态变量,即类被编译后的数据。
JVM内存模型图:
上述貌似有点难懂,我接下来自己画图来解释一下!
方法区剖析图:
讲了这么多,我们来点干货演示一下!
比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。
示例一:
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2); //true
可以看出str1和str2是指向同一个对象的。
示例二:
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1 == str2); // false
用new的方式是生成不同的对象。每一次生成一个。
示例一和示例二解析:
用第一种方式创建多个”abc”字符串,在内存中 其实只存在一个对象而已。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。
示例三:
String s0="kvill";
String s1="kvill";
String s2="kv" + "ill";
System.out.println( s0==s1 );
System.out.println( s0==s2 );
结果为:true true
示例四:
String s0="kvill";
String s1=new String("kvill");
String s2="kv" + new String("ill");
System.out.println( s0==s1 );
System.out.println( s0==s2 );
System.out.println( s1==s2 );
结果为:false false false
示例三和示例四解析:
因为例子中的 s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而"kv”和"ill”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中” kvill”的一个引用。所以我们得出s0==s1==s2;用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。
小问题:String s = new String(“xyz”);产生几个对象?
答案:1个或者2个。
解析:对于通过new产生一个字符串(假设为”xyz”)时,会先去常量池中查找是否已经有了”xyz”对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个引用常量池中此”xyz”对象的拷贝对象。
static final 和final修饰常量的区别:
static 强调只有一份,final 只是说明是一个常量,final定义的基本类型的值是不可改变的,但是final定义的引用对象的值是可以改变的。
不知道在说什么,来点干货!!
import java.util.Random; /** * 这个例子想说明一下static final 与 final的区别 */ public class Test1 { //47作为随机种子,为的就是产生随机数。 private static Random rand = new Random(47); private final int A = rand.nextInt(30); private static final int B = rand.nextInt(30); public static void main(String[] args) { Test1 test1 = new Test1(); System.out.println("test1 : " + "A=" + test1.A); //5 System.out.println("test1 : " + "B=" + test1.B); //8 Test1 test2 = new Test1(); System.out.println("test2 : " + "A=" + test2.A); //13 System.out.println("test2 : " + "B=" + test2.B); //8 } }
最后讲一下关于引用变量的内存分配!也是我们最为核心关注的内容!
引用类型:是指除了基本的变量类型之外的所有类型(如通过 class 定义的类型)。所有的类型在内存中都会分配一定的存储空间(形参在使用的时候也会分配存储空间,方法调用完成之后,这块存储空间自动消失), 基本的变量类型只有一块存储空间(分配在栈中), 而引用类型有两块存储空间(一块在栈中,一块在堆中)。
接下来我们继续用模型图分析!
引用数据类型内存分配图一:
引用数据类型内存分配图二:
可以看到引用数据类型内存分配图一和图二有所不同,其实图二只是对图一的更深层的剖析,图二这里存在一个句柄的概念。
这里举两个具体实例去作分析
实例一:
实例二:
分析:通过上面的代码和图形示例不难看出,a 和 b 是不同的两个引用,我们使用了两个定义语句来定义它们。但它们的值是一样的,都指向同一个对象"This is a Text!"。但要注意String 对象的值本身
是不可更改的 (像 b = "World"; b = a; 这种情况不是改变了 "World" 这一对象的值,而是改变了它的引用 b 的值使之指向了另一个 String 对象 a)。
这里我描述了两个要点:
(1) 引用是一种数据类型(保存在stack中),保存了对象在内存(heap,堆空间)中的地址,这种类型即不是我们平时所说的简单数据类型也不是类实例(对象);
(2)不同的引用可能指向同一个对象,换句话说,一个对象可以有多个引用,即该类类型的变量。
2.java的反射机制简析
2.1 反射机制的概念:
指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能调用它的任意一个方法.这种动态获取信息,以及动态调用对象方法的功能叫java语言的反射机制.
2.2 反射机制的应用:
生成动态代理,面向切片编程(在调用方法的前后各加栈帧).
2.3 反射机制的原理:
1 首先明确的概念: 一切皆对象----类也是对象.
2 然后知道类中的内容 :modifier constructor field method.
3 其次明白加载: 当Animal.class在硬盘中时,是一个文件,当载入到内存中,可以认为是一个对象,是java.lang.class的对象.
2.4 反射机制简单实例代码:
public class CustomService { //登录 public String login(String name,String pwd){ if("admin".equals(name) && "123".equals(pwd)){ return “success”; } return “fail”; } //退出 public void logout(){ System.out.println("系统已安全退出!"); } } public class ReflectTest { public static void main(String[] args) throws Exception{ //1.获取类 Class c = Class.forName("CustomerService"); //获取某个特定的方法 //通过:方法名+形参列表 Method m = c.getDeclaredMethod("login",String.class,String.class); //通过反射机制执行login方法. Object obj = c.newInstance(); //调用obj对象的m方法,传递"admin""123"参数,方法的执行结果是retValue Object retValue = m.invoke(obj, "admin","123"); System.out.println(retValue); //success } }