java知识总结——java平台与内存管理
说明
本文仅仅为了博主面试时方便查阅与梳理相关知识,如果有错误与不到位的地方,欢迎大佬指点改正,谢谢!
阅读书籍:《java程序员面试笔试宝典》
一、java是平台独立语言
1、平台独立性语言
平台独立性是指可以在一个平台上编写和编译程序,而在其他平台上运行。
保证java具有平台独立性的机制为“中间码”和“java虚拟机”。java程序被编译后不是生成能在硬件平台上可执行的代码,而是生成了一个“中间码”。不同的硬件平台上会装有不同的JVM,由JVM负责把“中间码”翻译成硬件平台能执行的代码。
由此可见,JVM不具有平台独立性,而是与硬件相关的。
2、解释执行的步骤
解释执行过程分三步进行:代码的装入、代码的校验和代码的执行。装入代码的工作由“类加载器”完成。被装入的代码由字节码校验器进行检查。
3、java字节码的执行方式
分两种方式:即时编译方式与解释执行方式。
即时编译方式指的是解释器先将字节码编译成机器码,然后再执行该机器码。
解释执行方式指的是解释器通过每次解释并执行一小段代码来完成java字节码程序的所有操作。通常采用的是解释执行方式。
C/C++语言没有跨平台的特性,但是有更高的执行效率。
二、java平台与其他语言
java平台是一个纯软件的平台,这个平台可以运行在一些基于硬件的平台之上。java平台主要包含两个模块:JVM与java API。
每当一个java程序运行时,都会有一个对应的JVM实例,只有当程序运行结束后,这个JVM才会退出。JVM实例通过调研类的main()方法来启动一个java程序,而这个main()方法必须是公有的、静态的且返回值为void的方法,该方法接收一个字符串数组的参数,只有同时满足这些条件才可以作为程序的入口。
三、JVM加载class文件的原理机制
类(class)只有被加载到JVM中后才能运行。当运行指定程序时,JVM会将编译生成的.class文件按照需求和一定的规则加载到内存中,并组织成为一个完整的java应用程序。这个加载过程是由类加载器来完成的,具体来说,就是由ClassLoader和它的子类来实现的。类加载器本身也是一个类,其实质是把类文件从硬盘读取到内存中。
类的加载方式分为隐式加载和显式加载两种。
隐式加载指的是程序在使用new等方式创建对象时,会隐式地调用类的加载器把对应的类加载到JVM中。
显示加载指的是通过直接调用class.forName()方法来把所需的类加载到JVM中。
任何一个工程项目都是由许多个类组成的,当程序启动时,只把需要的类加载到JVM中,其他类只有被使用到的时候才会被加载,采用这种方法,一方面可以加快加载速度,另外一方面可以节约程序运行过程中对内存的开销。
在java语言中,每个类或接口都对应一个.class文件,这些文件可以被看成一个个可以被动态加载的单元,因此当只有部分类被修改时,只需要重新编译变化的类即可,而不需要重新编译所有文件,因此加快了编译速度。
在java语言中,类的加载是动态的,它并不会一次性地将所有类全部加载后再运行,而是保证程序运行的基础类完全加载到JVM中,至于其他类,则在需要时才加载。
在java语言中,类可以分为三类:系统类、扩展类和自定义类。关系如下:
类加载通过委托的方式实现的。当有类需要被加载时,类加载器会请求父类来完成这个载入工作,父类会使用其自己的搜索路径来搜索需要被载入的类,如果搜索不到,才会由子类按照其搜索路径来搜索待加载的类。
类加载的主要步骤(3步)
1、装载。根据查找路径找到相对应的class文件,然后导入。
2、链接。链接又可以分为3个小的步骤,如下:
(1)检查。检查待加载的class文件的正确性。
(2)准备。给类中的静态变量分配存储空间。
(3)解析。将符号引用转换成直接引用。
3、初识化。对静态变量和静态代码块执行初始化工作。
四、GC(垃圾回收)
GC的作用是回收程序中不再使用的内存。
垃圾回收器主要负责完成3项任务:分配内存、确保被引用对象的内存不被错误地回收以及回收不再被引用的对象的内存空间。
垃圾回收器的存在一方面提高了开发人员的生产效率,另一方面保证了程序的稳定性。
只要有一个以上的变量引用该对象,该对象就不会被垃圾回收。
常用的垃圾回收算法
1、引用计数算法
引用计数作为一种简单但是效率较低的方法,其主要原理如下:在堆中对每个对象都有一个引用计数器;当对象被引用时,引用计数器加1;当引用被置为空或离开作用域时,引用计数减1.
2、追踪回收算法
追踪回收算法利用JVM维护的对象引用图,从根节点开始遍历对象的应用图,同时标记遍历到的对象。当遍历结束后,未被标记的对象就是目前已不被使用的对象,可以被回收了。
3、压缩回收算法
压缩回收算法的主要思路为:把堆中活动的对象移动到堆中一端,这样就会在堆中另外一端留出很大的一块空闲区域,相当于对堆中的碎片进行了处理。虽然这种方法可以大大简化消除堆碎片的工作,但是每次工作都会带来性能的损失。
4、复制回收算法
把堆分成两个大小相同的区域,在任何时刻,只有其中的一个区域被使用,直到这个区域的被消耗完为止,此时垃圾回收器会中断程序的执行,通过变量的方式把所有活动的对象复制到另外一个区域中,在复制的过程中它们是紧挨着布置的,从而可以消除内存碎片。当赋值过程结束后程序会接着运行,直到这块区域被使用完,然后再采用上面的方法继续进行垃圾回收。
这个算法的优点是在进行垃圾回收的同时对对象的布置也进行了安排,从而消除了内存碎片。但是也付出了很高的代价:对于制定大小的堆来说,需要两倍大小的内存空间;同时由于在内存调整的过程中要中断当前执行的程序,从而降低了程序的执行效率。
5、按代回收算法
把堆分成两个或者多个子堆,每一个子堆被视为一代。算法在运行的过程中优先收集那些“年幼的”对象,如果一个对象经过多次收集仍然“存活”,那么就可以把这个对象转移到高一级的堆里,减少对其的扫描次数。
五、java中的内存泄漏
1、概念
内存泄漏是指一个不再被程序使用的对象或变量还在内存中占有存储空间。
一般的,内存泄漏主要有两种情况:一是在堆中申请的空间没有被释放;二是对象已不再被使用,但还仍然在内存中保留着。java中的内存泄漏主要指的是第二种情况。
2、引起内存泄漏的原因
(1)静态集合类,这些类的实例生命周期与程序一致,在程序结束前不能被释放,从而造成内存泄漏。
(2)各种连接,只有连接关闭后,垃圾回收器才会回收对应的对象。
(3)监听器。在java语言中,往往会使用到监听器。通常一个应用中会用到多个监听器,但在释放对象的同时往往没有相应地删除监听器,这也可能导致内存泄漏。
(4)变量不合理的作用域,一般而言,如果一个变量定义的的作用域大于其使用范围,很有可能会造成内存泄漏,另一方面如果没有及时地把对象设置为null,很有可能会导致内存泄漏的发生。
(5)单例模式可能会造成内存泄漏。
六、java中的堆和栈的区别
基本数据类型的变量以及对象的引用变量,其内存都分配在栈上,变量出了作用域就会自动释放。
引用类型的变量,其内存分配在堆上或者常量池中,需要通过new等方式进行创建。
具体而言,栈内存主要用来存放基本数据类型与引用变量。栈内存的管理时通过压栈和弹栈操作来完成的,以栈帧为基本单位来管理程序的调用关系,每当有函数调用时,都会通过压栈方式创建新的栈帧,每当函数调用结束后都会通过弹栈的方式释放栈帧。
堆内存用来存放运行时创建的对象。一般用new关键字创建出来的对象都存放在堆内存中。由于JVM是基于堆栈的虚拟机,每个java程序都运行在一个单独的JVM实例上,每一个实例唯一对应一个堆,一个java程序内的多个线程也就运行在同一个JVM实例上,因此这些线程之间会共享堆内存,因此,多线程在访问堆中的数据时需要对数据进行同步。
java中引用的用法
在堆中产生了一个数组或对象后,还可以在栈中定义一个变量取值等于数组或对象在堆内存中的首地址,这个变量就成了数组或对象的引用变量。
引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
堆和栈的功能与作用比较
堆主要是用来存放对象的,栈主要是用来执行程序的。
相对于堆,栈的存取速度更快,但栈的大小和生存期必须是确定的,因此缺乏一定的灵活性。而堆却可以在运行时动态的分配内存,生存期不用提前告诉编译器,但这也导致了其存取速度的缓慢。