Android:
1、Android整体架构:
1.1 系统架构:
Linux操作系统为核心,从下往上,依赖关系。
a) 应用程序层:包括系统应用以及第三方应用。
b) 应用程序框架:提供应用开发所必须的一些API框架,是软件复用的重要手段
c) 库:android运行时(核心包(相当于JDK提供的包),虚拟机(优化过的JVM));C/C++的一些库
d) Linux核心:提供了电源管理、进程调度、内存管理、网络协议栈、驱动模型等核心系统服务
1.2 四大组件:
a) Activity:在Android应用中负责与用户交互的组件。
b) Service:常用于为其他组件提供后台服务或者监控其他组件的运行状态。经常用来执行一些耗时操作。
c) BroadcastReceiver:用于监听应用程序中的其他组件。
d) ContentProvider:Android应用程序之间实现实时数据交换。
2、Activity生命周期:
2.1 异常状态下生命周期:
a) 资源内存不足,低优先级activity被杀死。
b) 屏幕旋转。
&&
补充:当手机屏幕旋转时,Android系统会认为当前activity显示内容已经不再
适合新的屏幕显示模式,所以它会重新加载当前的activity。
屏幕旋转属性设置:android:configChanges="orientation | keyboardHidden | screenSize"
orientation: 当屏幕方向发生改变(屏幕旋转);
keyboardHidden:当调出键盘时;
screenSize: 当屏幕尺寸发生改变。
(1)不设置android:configChanges时,正常调用生命周期,横屏一次,
竖屏两次(模拟器,真机一次。原因不明。)。
onStart和onResume之间执行onSaveInstanceState,
onPause和onStop之间执行onRestoreInstanceState。
(2)设置android:configChanges="orientation"时,重新调用生命周期,横竖屏各一次。
onResume之后执行onConfigurationChanged。
(3)设置android:configChanges="orientation|keyboardHidden"时,
切换横竖屏只执行onConfigurationChanged方法。
&&
onSaveInstanceState(Bundle bundle):
在onStop()方法之前调用,用于临时保存数据。只有id的组件才会保存。
onRestoreInstanceState(Bundle bundle):
在onResume()方法之前调用,用于activity被销毁后恢复activity时调用。
2.2 Activity启动模式:
android : lunchMode
1、 standard --默认 activity栈中,每次启动都会创建新的activity
2、 singleTop --避免栈顶是当前activity
3、 singleTask --避免重复创建activity (常用)
4、 singleInstance --单独创建activity栈,可用于跨程序共享activity实例
3、Service生命周期:
Service分为两种:
a) 本地服务,属于同一个应用程序,通过startService来启动或者通过bindService来绑定并且
获取代理对象。如果只是想开个服务在后台运行的话,直接startService即可,
如果需要相互之间进行传值或者操作的话,就应该通过bindService。
b) 远程服务(不同应用程序之间),通过bindService来绑定并且获取代理对象。
4、BroadcastReceive:
4.1 BroadcastReceive的类别:
- 标准广播(无序广播):一种异步广播,效率高,不能被中断,但是设置优先级后可以。
- 有序广播:同步广播,可以中断。
- 全局广播:全局调用。
- 本地广播:只有本应用能调用。
4.2 BroadcastReceive的注册方式:
广播的注册方式有两种:
- 动态注册(代码注册),动态广播生命周期跟随着activity的结束而结束。
- 静态注册(manifest注册),静态注册的广播为常驻型广播。
4.3 BroadcastReceive的作用:
-
1、方便几大组件的信息和数据交互
-
2、方便程序间互通消息
-
3、效率上(参考UDP的广播协议在局域网的方便性)
-
4、设计模式上(反转控制的一种应用,类似监听者模式)
&&
补充:
无序广播,可设置优先级 中断。
5、ContentProvider:
- 用于在不同应用程序之间实现数据共享的功能,同时还能保证被访问数据的安全性
Android跨程序共享数据的标准方式。
5.1 Android常见的数据存储方式有哪几种:
- 1、sharePreference(键值对存储)
- 2、文件存储
- 3、数据库存储
- 4、ContentProvider
- 5、网络存储
6、IPC机制:(Inter-Process Communication)
- 进程间通信和跨进程通信,指两个进程直接进行数据交换的过程。
6.1 进程通信的方式:
- 1、Intent(Bundle)
- 2、AIDL(Binder)
- 3、Messenger
- 4、ContentProvider
- 5、Socket
- 6、BroadcastReceiver
6.2 Binder:
Binder为了解决跨进程通信。
简单来说,Binder分为Client和Server两个进程。
谁发消息,谁就是Client;谁收消息,谁就是Server。
ServiceManager负责把Binder Server注册到一个容器。
&& 进程通信 -- IPC(Intent Process Communication)机制。
Binder Client调用 Binder Server方法:
通过Binder驱动。
Client获取到Server对象。
Server在ServiceManager容器中注册。
Binder查询ServiceManager,返回Server代理对象。
Client通过代理对象调用Server方法,完成通信。
6.3 AIDL:
-- Android Interface Definition Language,Android接口定义语言。
-- Android系统中进程之间不能共享内存,Android系统采用RPC方式来实现。
&& RPC(Remote Procedure Call)远程过程调用。
-- AIDL机制 在不同的进程之间进行数据通信。
.aidl文件中,包含一个接口,以及Stub和Proxy两个实现了接口的类。
Stub是定义在接口中的,而Proxy则定义在Stub类中。
开始通信:
Client Stub的method()调用自己的asInterface(),判断参数IBinder。
-- IBinder对象,是否在同一线程。
否,将IBinder对象包装成一个Proxy对象,调用Proxy的method()。
Proxy在method()中,使用Parcelable序列化数据。
使用IBinder的transact方法,将数据传给Binder Server。
Server Stub通过onTransact()接收Client进程传过来的数据,执行对应函数,return结果。
6.4 AMS:
-- ActivityManagerService,Android系统服务,四大组件都归其管理。
-- 四大组件就是Binder Client,AMS就是Binder Server。
&& Framework层中,Activity Manager组成部分:
AMP(ActivityManagerProxy)服务代理。
-- 用于Server端提供的系统服务进行进程通信。代理Server对象。
AMN(ActivityManagerNative)服务中枢。
-- 继承Binder,实现IActivityManager,提供服务接口和Binder接口转换功能。
-- 并且存储服务代理对象,提供getDefault方法返回服务代理。
AMS(ActivityManagerService)系统服务。
-- 提供Server端系统服务。
Client 客户端。
-- 调用ActivityManager提供接口。通过代理对象,调用远程方法。
6.5 APP的启动:
首先,在手机屏幕上点击一个应用的icon图标。此时,手机屏幕就是一个Activity。
由手机系统厂商提供,称为Launcher APP。
下载的各类APP以Icon的形式显示在Launcher上。
Launcher与Icon是两个不同的APP,位于不同的进程,通过Binder通信。-- AMS作用:
启动流程:
Launcher通知AMS,启动Icon APP。
AMS记录Icon启动的首页。Launcher进入Pause状态。
AMS检查APP是否启动;是,唤起;否,启动新进程。Zygote --> DVM。
AMS在新进程中创建一个ActivityThread对象,启动main函数。
APP通知AMS,启动准备完毕。
AMS翻出记录的首页值,告诉APP,启动哪个页面。
APP启动首页,创建Context,与首页activity关联,调用onCreate函数。
6.6 Android系统的启动:
Bootloader引导。
Linux Kernel启动。
Android启动。
Init进程启动。
-- init.rc
Zygote进程启动。
-- Android系统中,每一个应用都有一个独立的DVM。通过Zygote进程初始化运行DVM。
-- 预加载,初始化核心类库。
Dalvik VM启动。
-- 加载.dex文件。
-- 动态加载,classLoader。
System Servers启动。
启动完成,显示桌面。
7、类的加载器,双亲机制,Android的类加载器。
7.1 类的加载器
Java应用程序由若干个.class文件组织而成。当程序在运行时,
即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在
不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,
如果另外一个文件不存在的,则会引发系统异常。
而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,
通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,
从而只有class文件被载入到了内存之后,才能被其它class所引用。
所以ClassLoader就是用来动态加载class文件到内存当中用的。
7.2 双亲机制
ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都有一个父类加载器的
引用(不是继承的关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)
本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。
当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给
它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的
类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给
Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,
如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。
如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。
否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String
来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,
就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,
所以用户自定义的ClassLoader永远也无法加载一个自己写的String,
除非你改变JDK中ClassLoader搜索类的默认算法。
JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。
只有两者同时满足的情况下,JVM才认为这两个class是相同的。
就算两个class是同一份class字节码,如果被两个不同的ClassLoader实例所加载,JVM也会认为它们是两个不同class。
比如网络上的一个Java类org.classloader.simple.NetClassLoaderSimple,
javac编译之后生成字节码文件NetClassLoaderSimple.class,ClassLoaderA和ClassLoaderB
这两个类加载器并读取了NetClassLoaderSimple.class文件,并分别定义出了java.lang.Class实例来
表示这个类,对于JVM来说,它们是两个不同的实例对象,但它们确实是同一份字节码文件,
如果试图将这个Class实例生成具体的对象进行转换时,就会抛运行时异常
java.lang.ClassCaseException,提示这是两个不同的类型。
7.3 Android类加载器
对于Android而言,最终的apk文件包含的是dex类型的文件,
dex文件是将class文件重新打包,打包的规则又不是简单地压缩,
而是完全对class文件内部的各种函数表,变量表进行优化,产生一个新的文件,即dex文件。
因此加载这种特殊的Class文件就需要特殊的类加载器DexClassLoader。
7.4 ClassLoader相关整理:
类加载器。
Java中用来将.class文件加载到JVM中的一种加载器。
Android中用来将.dex文件加载到JVM中的一种加载器。
JVM -- Java虚拟机。
Java实现跨平台的关键。
Java编译器会将.java文件编译为.class文件(cmd命令:javac "文件名")
JVM执行的就是.class文件。即字节码文件。
DVM -- Android虚拟机。(--Google)
DVM专门对移动操作系统的特性进行了优化。--> 例如特有的.dex文件。
DVM采用JIT -- just in time 即时编译技术。(遇到一个编译一个)
将字节码文件转换为精简原生指令码(机器码)。
Android中,每一个应用程序都是一个进程,都拥有一个独立的DVM。
每一个DVM在Linux中都是一个进程。
Dex
DVM特有的产物。
应用程序编译完成后会生成一个dex文件,该文件是所有的class类的整合优化。
Odex(Optimize Dex)
Dex的优化。
程序第一次被加载后生成。之后运行程序,直接加载Odex。
ART(Android Runtime)
Android 5.0取代DVM成为Android正式运行环境。
ART采用AOT -- Ahead of time 预编译技术
&&
APK执行过程:
代码被编译 Build 后生成 ask 文件,其实在里面生成了一个 classes.dex 文件。
这个 classes.dex 就是所有代码的集合,上一个可执行文件,android 运行的实质是
解压 apk 运行里面的这个 dex 文件。
apk 首次运行的时候会对这个 dex 文件进行优化,优化后生成一个 odex 文件,
存在于缓存中,下次启动直接打开 odex 文件,达到快速打开目的。
7.5 双亲委托机制相关整理:
双亲委托机制。即ClassLoader加载类的一种特性。
当需要一个类加载器加载class时,该加载器不会直接加载class文件,
而是将加载请求委托给其父类加载,依次层层委托。
若父类不能加载,则依次下发给子类加载。
这样做的好处在于:
防止系统中出现多个由不同加载器加载的同名object。
(如果由不同的类加载器加载同一个class,系统判定,这两个class不相等。)
8、Handler机制:
-
为什么要使用Handler?
因为屏幕的刷新频率是60Hz,大概16毫秒会刷新一次,所以为了保证UI的流畅性,耗时操作需要在子线程中处理,子线程不能直接对UI进行更新操作。因此需要Handler在子线程发消息给主线程来更新UI。这里再深入一点,Android中的UI控件不是线程安全的,因此在多线程并发访问UI的时候会导致UI控件处于不可预期的状态。Google不通过锁的机制来处理这个问题是因为:
引入锁会导致UI的操作变得复杂
引入锁会导致UI的运行效率降低
因此,Google的工程师最后是通过单线程的模型来操作UI,开发者只需要通过Handler在不同线程之间切换就可以了。
8.1 原理解析:
Handler机制,也就是异步消息机制,或者可以说是线程间的消息传递(通信)机制。
android两种特性:
一、Android中,子线程中不允许做UI操作,线程不安全。
二、主线程中尽量不要做耗时操作,避免产生ANR。
&&
原理解析:
首先,looper调用Looper.prepare(),创建一个looper对象,放入ThreadLocal中
(ThreadLocal保存looper对象)。
其中,looper会进行判空;
如果从ThreadLocal中拿到了looper对象,抛出异常。
("Only one Looper may be created per thread.")。
也就是说,如果looper不等于空,抛出异常。保证一个线程中只存在一个looper对象。
接着,looper调用Looper.loop(),从ThreadLocal中取出looper对象,
通过looper对象拿到MessageQueue。然后无限循环;
取出MessageQueue中的message,然后调用Handler的dispatchMessage,将消息发送给Handler。
(msg.target.dispatchMessage(msg))
最后message释放资源msg.recycle()。
如果message为空,消息队列阻塞,当前线程挂起 flushPendingCommands()。
(Flush any Binder commands pending in the current thread to the kernel driver. )
于是,Handler接收到了message,通过他本身的dispatchMessage方法。
然后在dispatchMessage方法中,调用了handlerMessage(),
也就是我们要处理的回调函数。
最后,Handler调用sendMessage(),将handler本身及其携带的message对象,
发送到消息队列MessageQueue中。
此时message不为空,looper.loop()中queue.next()不在阻塞,
msg.target.dispatchMessage(msg)执行。
完成消息通信。
(补充: looper调用Thread的本地方法(public static native Thread currentThread();),
进行线程间资源切换。)
--UML模型: Looper 中包含 Thread、 MessageQueue;
MessageQueue 中包含 Message;
Message 中包含 Handler、 Bundle;
Handler 中则什么都没有,它是下层封装好了,直接提供上层使用的类。
(当然,handler构造方法中重载了传递looper对象的构造方法,
可以手动传入主线程looper。(MainLooper))
public Handler(Looper looper) {
this(looper, null, false);
}
总结: Handler机制 --> looper让message在thread中传递。
9、ANR及其产生的原因和解决方案
- Application Not Responding,应用无响应。
类型有三种:
(1)、KeyDispatchTimeout(5 secends)
按键或触摸事件在特定时间内无响应
(2)、BroadcastTimeout(10 secends)
BroadcastReceiver在特定的时间内无法处理完成
(3)、ServiceTimeout(20 secends)
Service在特定的时间内无法处理完成
超时的原因有两种:
(1)、当前的事件没有机会得到处理。(主线程被占用)
(2)、当前的事件正在处理,但没有及时完成。(线程堵塞。所以一般耗时工作,开启单独的子线程完成)
10、OOM及其产生的原因和解决方案
- OutOfMemory(内存溢出) --> 内存泄露 != 内存溢出。
本质原因:由于手机设备的限制,一般一个应用使用的内存不能超过32M,超过时抛出OOM。
代码原因:
(1)、游标Cursor没关闭
(2)、调用registerReceiver后未调用unregisterReceiver
(3)、未关闭InputStream/OutputStream
(4)、Bitmap使用后未调用recycle
避免内存泄露:
(1)、使用缓存技术,比如LruCache、DiskLruCache、对象重复并且频繁调用可以考虑对象池
(2)、对于引用生命周期不一样的对象,可以用软引用或弱引用SoftReferner WeakReferner
(3)、对于资源对象 使用finally 强制关闭
(4)、内存压力过大就要统一的管理内存
Java:
1、JVM相关知识:
1.1 基础概念:
JVM也叫Java虚拟机,它是一个可以执行java字节码的虚拟机进程。
它的跨平台性指: 字节码文件(.class)可以在任何具有Java虚拟机的计算机或者电子设备上运行。
(由Java编译器进行编译)
运行时:Java源程序需要通过编译器编译成为.class文件。
1.2 JVM内存:
Jvm的内存大概分为三个,分别是堆内存、栈内存以及方法区。
在类加载的过程中,所有的非静态成员和静态成员会分别加载到JVM的方法区中,
静态变量的赋值以及静态构造代码块会先执行,也就是静态代码块是存储在方法区中的。
在创建对象的时候,非静态成员会被加载堆内存中,并完成成员变量的赋值初始化。
也就是说所有的非静态成员(包括成员变量、成员方法、构造方法、构造代码块、普通代码块)
是保存在堆内存中的,但是方法调用的时候,调用的方法会在栈内存中执行,
构造代码块也会在栈内存中执行。
方法区包含类信息、静态变量,常量池。
1.3 JVM基本构成:
1、 类加载器:在JVM启动时或者在类运行时将需要的class加载到JVM中。
2、 执行引擎:负责执行class文件中包含的字节码指令。
3、 内存区:是JVM运行的时候操作所分配的内存区。运行时内存区主要可以划分为5个区域:
a) 方法区:用于存储类结构信息的地方,包括常量池、静态变量、构造函数等。
b) 堆(heap):存储Java实例或者对象的地方。GC的主要区域。方法区和堆是被所有java线程共享的。
c) 栈(stack):java栈总是和线程关联在一起,每当创建一个线程是,JVM就会为这个线程创建
一个对应的java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就创建一个
栈帧,用于存储局部变量表、操作栈、方法返回值等。
每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。
所以java栈是线程私有的。
d) 程序计数器:用于保存当前线程执行的内存地址。
由于JVM程序是多线程执行的(线程轮流切换),所以为了保证线程切换回来后,
还能恢复到原先状态,就需要一个独立计数器,记录之前中断的地方,
程序计数器也是线程私有的。
e) 本地方法栈:和java栈作用差不多,为jvm使用到的native方法服务。
4、 本地方法接口:主要调用C或C++实现的本地方法及返回结果。
2、HashMap相关知识:
2.1 hash值计算方法:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
hash方法将传进来的key值的hashcode值(对象的内存地址) 异或 这个值无符号右移16位,
得到一个int值(hash)。
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
&&
补充:这里也就是为什么hashMap传入的key可以为null,而hashtable的key不能为null的原因。
因为,hashmap的key为null,hash值会返回0,而hanstable则是通过key.hashcode拿到hash。
HashMap底层是一个Entry数组,一旦发生Hash冲突的的时候,HashMap采用拉链法解决碰撞冲突
2.2 put方法:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
put(key, value) --> putVal(hash, key, value) -->
判断 table 是否为null or length=0;
是,扩容resize(),拿到table的长度n,运算 (n - 1) & hash, 得到数组索引i。
否,判断 i 是否为null:
是,直接插入 newNode(hash, key, value, null)。
否,判断 key 是否存在:
是,覆盖value。
否,判断 table[i]是否为treeNode:
是,红黑树直接插入键值对。
否,遍历链表准备插入,判断链表长度是否大于8:
是,转换成红黑树插入。
否,直接插入。
&&补充:
初始容量为 1 << CAPACITY << 4 --> 16
扩容因素为 FACTOR = 0.75f --> 16 * 0.75f = 12 --> table初始大小
扩容触发 --> if (++size > threshold)
resize();
最大容量为 MAXIMUM_CAPACITY = 1 << 30
(newCap = oldCap << 1) 扩容两倍
链表table长度为8,超过了这个长度,会将链表转换为红黑树,treeNode。
hash碰撞:指对象的内存地址hashcode相同。
当运算 (n - 1) & hash,得到的数组索引i相同时,
发生碰撞,hashmap采用拉链法(单向链表法),将键值对存储到下一个entry。
2.3 HashMap 和 HashTable 区别:
-
(1).两者最主要的区别在于Hashtable是线程安全,而HashMap则非线程安全
(&& hashMap的get方法没有synchronized) -
(2).HashMap可以使用null作为key,而Hashtable则不允许null作为key
(&& hashMap的key为null时,hash值返回0) -
(3).HashMap继承AbstractMap抽象类,Hashtable继承Dictionary抽象类,两者都实现了Map接口。
-
(4).HashMap扩容时是当前容量翻倍即:capacity2,Hashtable扩容时是容量翻倍+1
即:capacity2+1 -
(5).两者计算hash的方法不同
( Hashtable产生于JDK1.1,而hashMap产生于JDK1.2 )
3、枚举:
-
java 枚举的本质原理:是通过普通类来实现的,只是编译器为我们进行了加工处理。
每个枚举类型编译后的字节码实质都继承自 java.lang.Enum 的枚举类型同名普通类,
而每个枚举常量实质是一个枚举类型同名普通类的静态常量对象,
所有枚举常量都通过静态代码块进行 初始化实例赋值(由于是静态块,所以在类加载期间就初始化类)。
所以:枚举的本质是编译器处理成了类,
枚举值为类的静态常量属性,其属性在类加载时的静态代码块中被初始化实例赋值。枚举可以有修饰符不大于默认修饰符的构造方法(修饰符可为 private,不可为 public 等),
枚举只是一个语法糖,被编译器生成了最终的类而已。( java枚举Enum类的 equals 方法默认通过 == 来比较的;
类似的 Enum 的 compare To 方法比较的是 Enum 的 ordinal 顺序大小;
类似的还有 Enum 的 name 方法和 toString 方法一样都返回的是 Enum 的 name 值。
所以:java 的枚举值比较用 == 和 equals 方法没什么区别。)
4、String和StringBuffer和StringBuilder的区别:
-
String是字符串常量,StringBuffer是字符串变量。
( StringBuffer 相当于在堆内存中加了一个缓冲区,可以追加字 符串。) -
StringBuffer是线程安全的,StringBuilder是线程不安全的。
( 多线程并发,导致线程安全问题。StringBuffer源码中,对字符串的操作方法增加了synchronize关键字 )
5、浅拷贝和深拷贝:
浅拷贝就是在同一个内存空间中将对象重新复制一遍,内存 地址指向不变;
深拷贝就是将一个对象拷贝到不同的内存空间,内存地址指 向两个不同的对象。
(浅拷贝:在原有的空间拷贝;深拷贝:拷贝到另一空间。)
&&补充:
常用设计模式之一:原型模式 ,本质上就是对象拷贝。
--> 通过实现Cloneable接口,重写clone方法。
5、GC机制:
垃圾收集器一般必须完成两件事:检测出垃圾;回收垃圾。怎么检测出垃圾?一般有以下几种方法:
给一个对象添加引用计数器,每当有个地方引用它,计数器就加1;引用失效就减1。
好了,问题来了,如果我有两个对象A和B,互相引用,除此之外,没有其他任何对象引用它们,
实际上这两个对象已经无法访问,即是我们说的垃圾对象。
但是互相引用,计数不为0,导致无法回收,所以还有另一种方法:
以根集对象为起始点进行搜索,如果有对象不可达的话,即是垃圾对象。
这里的根集一般包括java栈中引用的对象、方法区常良池中引用的对象、本地方法中引用的对象等。
总之,JVM在做垃圾回收的时候,会检查堆中的所有对象是否会被这些根集对象引用,
不能够被引用的对象就会被垃圾收集器回收。一般回收算法也有如下几种:
1).标记-清除(Mark-sweep)
2).复制(Copying
3).标记-整理(Mark-Compact)
4).分代收集算法
&&
补充:
gc,Java中的垃圾回收器。当JVM发现内存资源紧张时,自动清理无用对象。可以调用System.gc()手动清理。
gc在JVM中使用的是root算法。对象循环引用是否可以被回收,取决于这个循环引用是否挂在root上。
static修饰的变量为root的一种。方法中的成员变量也是root的一种。(root - "根")
5、String s = “123”;这个语句有几个对象产生:
- 一个或者没有。
Java会先检查栈内存中,是否存在“123”字符串。如果存在,直接将s指向该对象;如果不存在,创建一个s对象,指向字符串常量“123”。
6、char型变量中能不能存贮一个中文汉字:
- 可以。
char类型用来存储Unicode编码的字符,包括了汉字。
如果是不包括在Unicode编码内的特殊汉字,则不能存储。
Unicode编码占用两个字节,char类型变量也占用两个字节。
7、int 与 Integer区别:
- 1、int是基本数据类型,Integer是int的包装类,是对象。
2、int默认值是0,Integer默认值是 null。
3、int储存在栈中, Integer储存在堆中。
8、Java中的内存溢出,内存泄漏:
- 内存溢出:
1、内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2、集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3、代码中存在死循环或循环产生过多重复的对象实体;
4、使用的第三方软件中的BUG;
5、启动参数内存值设定的过小;
内存泄漏:
一些没有被使用,但是却不能被gc回收的对象。(其他对象或实例持有)