Android面试题

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
    即:capacity
    2+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回收的对象。(其他对象或实例持有)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值