阿里-测试开发面经(五)

扶摇就业

共 4925字,需浏览 10分钟

 · 2021-04-27

点击蓝字关注我们,获取更多面经








内存溢出和内存泄露




一、定义

内存溢出: 即为out of memory, 当你要求分配的内存超过了系统给你的内存时, 系统就会抛出out of memory的异常(每个Android能用的内存是有限的)

比如: 当前应用只剩下4M的空间可用, 但你却加载得到一个需要占用5M空间的图片Bitmap对象, 就会抛出溢出的异常

内存泄露: 即为memory leak, 一个对象被创建后, 你不再使用它了, 但因为某种原因它又没有成为垃圾对象, 这块内存不能再被分配置使用.

比如: 查询数据库得到的cursor对象在使用完后没有关闭, Activity中使用Handler发延迟消息, 但退出前不移除未处理的消息

内存泄露不多时没有太大影响, 但积累得多了就会导致应用运动缓慢, 到最后就会内存溢出.


二、内存泄漏的分类

常发性内存泄漏: 发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏

偶发性内存泄漏: 发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的

一次性内存泄漏: 发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏

说明: 危害性大小顺序为: 1)>2)>3)


三、造成内存泄露的几种场景

长生命周期的对象持有短生命周期对象的引用: Activity中使用Handler

资源数据连接相关对象不关闭: cusor, stream, connection

HashSet中的对象或HashMap中的Key对象, 内部与hash值相关的属性被修改

一些对象产生后不会自动释放或需要完全执行完了才释放. 比如: Bitmap, Thread, AsyncTask


四、避免内存泄露

尽早释放无用对象的引用

使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域

尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收

避免在循环中创建对象


五、造成内存溢出的的场景

申请了太多的对象. 比如: 使用ListView时, 不复用convertView, 当数据项多时就会出现内存溢出

创建的对象内存太大. 比如: 不经过压缩直接加载大图片文件

内存泄露积累一定的时间后就可能出现


六、避免内存溢出

通过复用对象的方式, 减少产生的对象

大对象需要先压缩后创建

避免或减少内存泄露的情况






java中线程如何实现




Java中实现线程有三种方式:

1、1.继承Thread类


public class Thread extends Object implements Runnable


定义Thread类的子类,并重写Thread类的run()方法,创建子类对象(即线程对象),调用线程对象的start()方法来启动该线程。


2.实现Runnable接口


public interface Runnable


定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法同样是该线程的执行体。创建该Runnable实现类的实例,并将此实例作为Thread的target(即构造函数中的参数)来创建Thread对象(该Thread对象才是真正的线程对象,只是该Threa


3.使用Callable和Future


创建Callable接口的实现类,并实现call()方法,该方法有返回值;创建Callable实现类的实例,使用FutureTask来包装Callable对象,并且也封装了call()方法的返回值;使用FutureTask作为Thread类的target创建并启动线程;调用FutureTask对象的get()方法返回子线程执行结束后的返回值。


如何实现多线程:


首先是继承Thread类并重写run()方法


package com.csu.multiThread;
public class MultiThreadExtendsThread extends Thread{
String name;
public MultiThreadExtendsThread(String name) { this.name = name; }

public void run() {
for(int i=0;i<5;i++) {
System.out.println(name+"运行:"+i);
}
}

public static void main(String[] args) {
MultiThreadExtendsThread thread1 = new MultiThreadExtendsThread("A"); MultiThreadExtendsThread thread2 = new MultiThreadExtendsThread("B");
thread1.start();    thread2.start();
}}



二是实现Runnable接口,然后重写run()方法,

package com.csu.multiThread;
public class MultiThreadImplRunnable implements Runnable{
String name;
public MultiThreadImplRunnable(String name) { this.name = name;
}
@Override  public void run() {    for(int i=0;i<5;i++) { System.out.println(name+"运行:"+i);

}
}


public static void main(String[] args) {
MultiThreadExtendsThread thread1 = new MultiThreadExtendsThread("A");
MultiThreadExtendsThread thread2 = new MultiThreadExtendsThread("B");

new Thread(thread1).start(); new Thread(thread2).start();

}}

线程安全的实现:

最基本的:synchronized关键字。这个方法是最常用的,它通过互斥的方式保证同步。我们知道java中有几个操作是可以保证原子性的,其中lock/unlock就是一对。虽然java没有提供这两个字节码的接口,但是我们可以通过monitorenter/monitorexit,而synchronized会在块的前后调用两个字节码指令。同时synchronize对于同一条线程来说是可重入的;其次它也是阻塞的。我们知道java线程是映射到操作系统上的,而且是混用的内核态线程和用户态线程(N:M),而将线程从阻塞/唤醒,需要将线程从用户态转换到内核态,这样会消耗太亮的资源,所以synchronize是一个重量级锁


另外一种和synchronize类似的方法:ReentrantLock。它们两个的区别:(1)synchronize是隐式的,只要块内的代码执行完,就会释放当前的锁;而后者需要显式的调用unlock()方法手动释放,所以经常搭配try/finally方法(忘记在finally中unlock是非常危险的) (2)后者可以选择等待中断——即在当前持有锁线程长期不释放锁的情况下,正在等待的线程可以选择放弃等待选择处理其他的事情。(3) 后者可以选择公平锁(虽然默认是非公平的,因为公平锁的吞吐量很受影响)即先来后到,按申请的顺序获得锁。(4)可以绑定多个条件


前面提到的两种方式都是通过互斥来达到同步的目的,这其实是悲观锁的一种。下面介绍的是乐观锁,基于冲突检测的并发策略,不需要将线程挂起,因此又被成为非阻塞同步。


典型:CAS(Compare And Swap),通过Unsafe类提供。有三个操作数,内存位置、旧的预期值、和新的值;当且仅当内存地址V符合预期值A时,执行将值更新为新的预期值B。  存在的问题:“ABA”情况,即原值为A,但在检测之前发生了改变,变成了B,同时也在检测时变回了A;即不能保证这个值没有被其他线程更改过。


接下来是无同步方案:

可重入代码(纯代码):是一种无同步方案,在执行的任何时候去中断,转而执行其他的代码;在重新返回代码后,不会出现任何的错误。可重入性->线程安全,充分不必要条件。即可重入性的代码都是线程安全的,但反之不一定。简单判断原则:一个方法的返回结果是可预测的,只要输入了相同的数据,就都能返回相同的结果。


线程本地存储:即利用ThreadLocal类;每个Thread类中都有一个变量ThreadLocalMap,默认是为null的。它将为每一个线程创立一个该变量的副本。这样线程之间就不存在数据征用的问题了。适用情况:(1)数据库的Connection连接 (2)WEB中的“一个请求对应一个服务器线程”,在知乎上看到一个回答,解释的蛮清晰的。(3)Spring中创建的默认模式是Singleton单例 (4)“生产者-消费者问题”


ThreadLocal就是变量在不同线程上的副本,不同线程不共享,所以对变量改动时就不需要考虑线程间同步的问题了


ThreadLocal在web应用开发中是一种很常见的技巧,当web端采用无状态写法时(比如stateless session bean和spring默认的singleton),就可以考虑把一些变量放在ThreadLocal中


举个简单例子,以理解意思为主:你有两个方法A和B都要用到变量userId,又不想传来传去,一个很自然的想法就是把userId设为成员变量,但是在无状态时,这样做就很可能有问题,因为多个request在同时使用同一个instance,userId在不同request下值是不一样的,就会出现逻辑错误

但由于同一个request下一般都是处于同一个线程,如果放在ThreadLocal的话,这个变量就被各个方法共享了,而又不影响其他request,这种情况下,你可以简单把它理解为是一种没有副作用的成员变量(作者:卡斯帕尔)


线程栈线程的每个方法被执行的时候,都会同时创建一个帧(Frame)用于存储本地变量表、操作栈、动态链接、方法出入口等信息。每一个方法的调用至完成,就意味着一个帧在VM栈中的入栈至出栈的过程。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果VM栈可以动态扩展(VM Spec中允许固定长度的VM栈),当扩展时无法申请到足够内存则抛出OutOfMemoryError异常。








更多面经





360-测试开发面经(一)


百度-测试开发面经(一)


字节跳动-测试开发面经(一)


    扫描二维码

   获取更多面经

  扶摇就业  


浏览 9
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报