成员应用细节
JVM主要包括三块内存空间,分别是栈内存、堆内存和方法区
Java虚拟机包含类装载器子系统、执行引擎、运行时数据区、本地方法接口和垃圾收集模块。
-
类装载器子系统:根据给定的全限定类名(如: java.lang.Object)来装载class文件到运行时数据区域的方法区中。
-
执行引擎:执行字节码或执行本地方法。
-
运行时数据区∶就是常说的JVM的内存,堆,方法区,虚拟机栈,本地方法栈,程序计数器。
-
本地方法接口:与本地方法库交互,作用就是为了融合不同编程语言为Java所用,它的初衷是融合C/C++程序
首先通过编译器把Java代码转换成字节码,类加载器再把字节码加载到内存中(运行时数据区的方法区内),而字节码文件只是VM的一套指令集规范,不能直接交给底层系统去执行,所以需要特定的命令解析器执行引擎将字节码翻译成底层系统指令,再交给CPI去执行,而这个过程需要调用其他语言的本地库接口来实现整个程序的功能。
方法的代码片以及整个类的代码片断被存储到方法区内存中,在类加载的时候这些代码片段会被载入。方法区与java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
- JDK 8完全废弃了永久代的概念,改用在本地内存中实现的元空间来代替,JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。异常情况:方法区无法满足新的内存分配需求时,将抛出OOM异常
- 运行时常量池是方法区的一部分。并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。
方法执行使用的是栈,该方法需要的内存空间在栈内存中分配,称为压栈。方法执行结束后,该方法所占用的内存空间自动释放,称为弹栈。栈中主要存储的是方法体当中的局部变量
引用和指针的区别:
引用也叫句柄,类似于指针,但是和指针是不同的。指针是一个存放地址的变量,使程序员可以灵活的访问内存,由于可以对指针进行任意的运算操作,所以给程序带来了安全隐患和意想不到的结果。引用继承了指针节省内存的优点,但是限制了对地址的操作,它是安全的。Java中所有的变量都是一个引用,java中没有指针的概念。
Person p=new Person();--调用的是Person类中的无参构造器
class Person{
private String name;
private Job[] jobs;
}
在程序执行过程中使用new运算符创建的java对象,存储在堆内存中。对象内部有实例变量,所以实例变量存储在堆内存当中
变量分类:
-
局部变量,方法体中声明
-
成员变量,方法体外类内声明
- 实例变量,没有static修饰符,各个不同对象相互隔离。
- 静态变量,有static修饰符,这个类的所有对象共享
- 静态变量存储在方法区内存中
-
三块主要内存中变化最频繁的是栈内存,最先有数据的是方法区内存,垃圾回收期主要针对的是堆内存
自动垃圾回收机制GC什么时候会将java对象的内存回收
-
当堆内存中的对象称为垃圾数据的时候会被GC回收
-
什么时候堆内存对象会变成垃圾?
- 引用计数法:为每个对象创建一个引用计数器,有对象引用时计数器+1,引用被释放时计数器-1,当计数器为0时就可以被回收。它有一个缺点就是不能解决循环引用的问题。
- 可达性算法(引用链法)︰从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是可以被回收的。
总结
-
栈内存存储基本类型的变量和对象的引用变量
-
堆内存用于存放由new创建的对象和数组。每new一个对象就在堆内存中开辟一个新的存储空间存储此实例对象
-
Person p = new Person()执行new命令时程序执行两步: a:在堆内存中开辟一段空间,存储new出来的对象; b:在栈内存中添加一个变量p,p中存放的是该对象在堆内存中开始存放处的物理地址
-
p = null;执行此步骤的时候程序只是更改栈内存中的P变量所保存的地址,把地址指向null,而并没有操作堆内存《把p所指向的对象实例清空回收)
-
无论是形参或者实参,执行XXX = null;操作时都是把XXX变量栈中存储的地址改为指向null的地址。不操作堆中的数据。
具体问题
由类创建一个对象,JVM内存中发生了哪些事情?
MyClass mc = new MyClass();以这条语句为例, MyClass mc =使虚拟机栈中生成了一个指向MyClass对象的地址; new MyClass(则在堆中分配了对象mc成员变量的空间。
栈和堆的区别
管理方式:栈自动释放,堆需要GC
空间大小:栈比堆小
碎片相关:栈产生的碎片远小于堆
分配方式:栈支持静态和动态分配,而堆仅仅支持动态分配
效率:栈的效率比堆高
方法的问题
方法的分类:
-
无参无返(没有参数列表,没有返回值)单纯的作为功能代码的聚合使用便于功能复用
-
无参有返(没有参数列表,有返回值)
-
有参无返(有参数列表没有返回值)适用于功能需要根据参数来进行计算的情况,但是计算的最终结果又无需返回处理
-
有参有返(有参数列表,有返回值)适用于功能需要根据参数来进行计算的情况,而且最终的结果需要返回处理
方法的形参和实参:
-
形参:是定义在方法声明上,用于指定该方法需要传递的参数类型的
-
实参:是调用方法时,实际传递的参数值
方法参数传递
-
基本数据类型作为参数传值:传值传的时值的内容,来到另一个方法空间之后,这个值和之前没有任何关系。(如你们拷贝我分享的网盘内容不会改变我原有玩盘内容)
-
引用数据类型作为参数传值:传值传的时对象在堆的地址值,所以了两个内容指向了同一空间是相互影响的。(如你登陆我的网盘拷贝内容改变的话会改变我的网盘内容)
基本数据类型的对象缓存
在不可变类Integer类定义中查看源代码可以发现一个定义
private static class Integercache {}//这实际上就是Integer的cache
Integer num1 = 12;
Integer num2 = 12;
//这块相等,<=127都是真的
System.out.println(num1 == num2);
是因为在Integer中包含有一个缓存池,缓存值为-128到127之间。
-
定义Integer k1=12是先在缓存池中查找12这个对象,如果有则直接使用
-
new Integer(12)一定会引发对象的创建,而不管缓存池中是否包含这个对象
Integer k1 = 12;
Integer k2 = 12;
System.out.print1n(k1 == k2); //true,k1和k2都是引用类型,所以==比较的是地址
Integer k3=new Integer(12);
System.out.println(k1==k3) ; //false
Integer k1 = 129;
Integer k2 = 129;
System.out.println(k1 == k2); //fa1se,因为缓存只缓存-128到127之间的数据
Intcgcr\Long\Short\Bytc中都有缓存池、charactcr中也有缓存池,boolcan只有truc/falsc两个
字符串缓存池
String中包含一个缓存池,当使用某个字符串对象时会首先在缓存池中进行查找,如果存在则直接返回这个对象的地址;如果不存在则会在缓存池中进行创建,创建完成后返回地址
String s1="abc " ;
String s2="abc ";
String s3=new String("abc"); //这里会创建2个对象,一个在缓存池中,一个是new导致的新创建对象
System.out.println(s1==s2) ; //true
System.out.println(s1==s3); //false
注意:如果通过字串拼接所得内容和某个字串内容一致,但是地址不同
装箱/拆箱
值类型自动转换成它对应的类类型-autoboxing、类类型自动转成它所对应的值类型-unboxing
-
装箱。如: Integer a = 9;
-
将一个对像自动转成基本类型就叫拆箱int c = new Integer(9);