jvm相关
内存区域分布
共享区域:
堆:存储java的实例
方法区:静态变量,类的信息,常量池
线程私有区域:
程序计数器:记录正在执行的字节码指令地址
方法栈:用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
本地方法堆栈:针对的是 Native 方法
Gc原理和回收策略
回收区域:只针对堆、方法区;线程私有区域数据会随线程结束销毁,不用回收
回收类型:1.堆中的对象:分代收集 GC 方法会吧堆划分为新生代、老年代。新生代:新建小对象会进入新生代;通过复制算法回收对象;老年代:新建大对象及老对象会进入老年代;通过标记-清除算法回收对象。
2.方法区中的类信息、常量池
判断一个对象是否可被回收:
1.引用计数法:有循环引用的缺点
2.可达性分析法:从 GC ROOT 开始搜索,不可达的对象都是可以被回收的。其中 GC ROOT 包括虚拟机栈/本地方法栈中引用的对象、方法区中常量/静态变量引用的对象。
四种引用类型:
强引用:代码中普遍存在的,只要强引用还存在,垃圾收集器就不会回收掉被引用的对象。
软引用:SoftReference,用来描述还有用但是非必须的对象,当内存不足的时候会回收这类对象。
弱引用:WeakReference,用来描述非必须对象,弱引用的对象只能生存到下一次GC发生时,当GC发生时,无论内存是否足够,都会回收该对象。
虚引用:PhantomReference,一个对象是否有虚引用的存在,完全不会对其生存时间产生影响,也无法通过虚引用取得一个对象的引用,它存在的唯一目的是在这个对象被回收时可以收到一个系统通知。
虚引用必须与 ReferenceQueue 一起使用,当 GC 准备回收一个对象,如果发现它有虚引用,就会在回收之前,把这个 虚引用 加入到与之关联的 ReferenceQueue 中。
leakcanary利用这个原理,我们可以检测到对象何时被回收
回收算法:
1).标记-清除(Mark-sweep)
标记-清除算法采用从根集合进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收。标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。
2).标记-整理(Mark-Compact)
标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。该垃圾回收算法适用于对象存活率高的场景(老年代)。
3).复制(Copying)
复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这种算法适用于对象存活率低的场景,比如新生代。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。
4).分代收集算法
不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率。当代商用虚拟机使用的都是分代收集算法:新生代对象存活率低,就采用复制算法;老年代存活率高,就用标记清除算法或者标记整理算法。Java堆内存一般可以分为新生代、老年代和永久代三个模块:
类的加载
类的生命周期:1.加载;2.验证;3.准备;4.解析;5.初始化;6.使用;7.卸载
系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。
类加载过程:1.加载:获取类的二进制字节流;生成方法区的运行时存储结构;在内存中生成 Class 对象 2.验证:确保该 Class 字节流符合虚拟机要求 3.准备:为类变量分配内存,初始化静态变量 4.解析:将常量池的符号引用替换为直接引用 5.初始化:执行静态块代码、类变量赋值
解析举个例子:在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方法表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。
装饰者模式vs代理模式
装饰者模式
第一代机器人只会跳舞,第二代机器人通过传入第一代机器人拥有了跳舞功能,然后增加唱歌功能呢。
public interface Robot {
void dance();
}
public class firstRobot implements Robot {
@Override
public void dance() {
System.out.println("dance: dance");
}
}
public class twoRobot implements Robot {
protected Robot rebot;
public ShapeDecorator(Robot rebot){
this.rebot = rebot;
}
public void dance(){
rebot.dance();
}
public void singing(){
}
}
代理模式
public interface Image {
void display();
}
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
public class ProxyImage implements Image{
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName){
this.fileName = fileName;
}
@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
// 图像将从磁盘加载
image.display();
System.out.println("");
// 图像不需要从磁盘加载
image.display();
}
}
两个模式的区别
让别人帮助你做你并不关心的事情,叫代理模式
为让自己的能力增强,使得增强后的自己能够使用更多的方法,拓展在自己基础之上的功能的,叫装饰器模式
hashmap
长度16或者其他2的幂,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。
hash冲突的解决
1.链式寻址法,这是一种常见的方法,简单理解就是把存在Hash冲突的key,以单向链表来进行存储
2.开放定址法也称线性探测法,就是从发生冲突的那个位置开始,按照一定次序从Hash表找到一个空闲位置然后把发生冲突的元素存入到这个位置,而在java中,ThreadLocal就用到了线性探测法来解决Hash冲突
3.再Hash法,就是通过某个Hash函数计算的key,存在冲突的时候,再用另外一个Hash函数对这个可以进行Hash,一直运算,直到不再产生冲突为止,这种方式会增加计算的一个时间,性能上呢会有一些影响
4.建立公共移除区,就是把Hash表分为基本表和益处表两个部分,凡是存在冲突的元素,一律放到益处表中
hashmap的扩容机制
1、默认的初始化容量大小为16,必须为 2 的幂次方
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
2、最大的容量,在两个带参数的构造函数隐式指定更高值时使用,必须为 2的幂次方
static final int MAXIMUM_CAPACITY = 1 << 30;
3、默认的负载因数,这个值最好不要改动,这个值可以大于1.
static final float DEFAULT_LOAD_FACTOR = 0.75f;
4、链表转红黑树的阈值:
static final int TREEIFY_THRESHOLD = 8;
5、红黑树退化成链表的阈值:
static final int UNTREEIFY_THRESHOLD = 6;
6、当哈希表的容量大于64时,才允许树化(链表转红黑树),如果小于64,则直接扩容,这个值不能小于4*TREEIFY_THRESHOLD。不然会进行选择扩容还是树化。
static final int MIN_TREEIFY_CAPACITY = 64;
2.1 什么时候发生扩容
在jdk1.8中,当元素数量超过阈值(容量*负载因子)时,一般就会发生扩容,每次扩容的容量都是之前容量的2 倍数。
HashMap的容量是有上限的,必须小于1<<30。如果容量超出了这个数,则不再增长,且阈值会被设置为Integer.MAX_VALUE。
(1)就是hashmap在存值的时候(默认大小为16,负载因子0.75,阈值12),可能达到最后存满16个值的时候,再存入第17个值才会发生扩容现象,因为前16个值,每个值在底层数组中分别占据一个位置,并没有发生hash碰撞。
(2)当然也有可能存储更多值(超多16个值,最多可以存26个值)都还没有扩容。原理:前11个值全部hash碰撞,存到数组的同一个位置(虽然hash冲突,但是这时元素个数小于阈值12,并没有同时满足扩容的两个条件。所以不会扩容),后面所有存入的15个值全部分散到数组剩下的15个位置(这时元素个数大于等于阈值,但是每次存入的元素并没有发生hash碰撞,也没有同时满足扩容的两个条件,所以叶不会扩容),前面11+15=26,所以在存入第27个值的时候才同时满足上面两个条件,这时候才会发生扩容现象。
在1.8版本中是先插入再扩容(除非第一次初始化是先初始化再插入值),所以在1.8中key的个数大于阈值便会扩容。
activity的启动流程
launcher进程通过binder传入上下文包名信息-->systemserver进程收到包名信息-->通过socket通知zygote进程——》fork一个app进程——》app进程通过binder进程发送attachApplication的信息---->systemserver进程AMS通过activity的代理类通过binder送scheduleLaunchActivity请求————》app进程的activityThreader通过handler发送启动Launcher_activity
![](https://img.haomeiwen.com/i1848495/6d5f91e7bdbc8cb5.png)
activity的渲染流程
onresume执行之后,会开始进行Activity的绘制,将Activity的dectorView attach 到window上,此后开始界面的渲染绘制。
因此,根据我们一般的开发习惯,在oncreate中执行setContentView,调用了xml的inflate方法,并不意味着布局文件已经渲染完毕,LayoutInflater 进行 inflate的过程,仅仅是将布局文件中定义的 view 元素通过调用createViewFromTag方法实例化的过程。setContentView实现了布局文件到java对象的转换,却并没有开始渲染和绘制。view的绘制发生在activity attachToWindow之后,执行WindowManagerImpl的addView方法
线程池的传参
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//...
}
corePoolSize:核心线程数,线程池正常情况下保持的线程数,大户人家“长工”的数量。
maximumPoolSize:最大线程数,当线程池繁忙时最多可以拥有的线程数,大户人家“长工”+“短工”的总数量。
keepAliveTime:空闲线程存活时间,没有活之后“短工”可以生存的最大时间。
TimeUnit:时间单位,配合参数 3 一起使用,用于描述参数 3 的时间单位。
BlockingQueue:线程池的任务队列,用于保存线程池待执行任务的容器。
ThreadFactory:线程工厂,用于创建线程池中线程的工厂方法,通过它可以设置线程的命名规则、优先级和线程类型。
RejectedExecutionHandler:拒绝策略,当任务量超过线程池可以保存的最大任务数时,执行的策略。
参数7:RejectedExecutionHandler
拒绝策略:当线程池的任务超出线程池队列可以存储的最大值之后,执行的策略。
默认的拒绝策略有以下 4 种:
AbortPolicy:拒绝并抛出异常。
CallerRunsPolicy:使用当前调用的线程来执行此任务。
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy:忽略并抛弃当前任务。
线程池的默认策略是 AbortPolicy 拒绝并抛出异常。
参数5:BlockingQueue
阻塞队列:线程池存放任务的队列,用来存储线程池的所有待执行任务。
它可以设置以下几个值:
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
比较常用的是 LinkedBlockingQueue,线程池的排队策略和 BlockingQueue 息息相关。
内存优化工具的使用
Android Profiler分为三大模块: cpu、内存 、网络。
耗时的查询:
内存的具体分析:
Android Profiler 工具参考官方文档 : 使用 Memory Profiler 查看 Java 堆和内存分配
1.profiler查找内存泄漏
内存泄漏代码:通过一个静态的单例,每次关闭activity持有了MainActivity2
public class PendingOrderManager {
private static PendingOrderManager instance;
private Context mContext;
public PendingOrderManager(Context context) {
this.mContext = context;
}
public static PendingOrderManager getInstance(Context context) {
if (instance == null) {
instance = new PendingOrderManager(context);
}
return instance;
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.w("TAG", "-----BActiviy onCreate---------");
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.tt);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,MainActivity2.class);
startActivity(intent);
}
});
}
}
public class MainActivity2 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
findViewById(R.id.tv).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
PendingOrderManager.getInstance(MainActivity2.this);
//finish();
}
}
![](https://img.haomeiwen.com/i1848495/19cc490bb8cdd1d0.png)
打开profile 的Memory,先点击垃圾桶图标gc回收下,然后操作写好的内存泄漏界面后,驼峰就是出现了内存泄漏。
![](https://img.haomeiwen.com/i1848495/808ce100f015d643.png)
勾选Capture heap dump然后Record,
![](https://img.haomeiwen.com/i1848495/ddcc8a4f33b7e500.png)
我们是通过包名去寻找,那个感叹号就是我们内存泄漏的对象MainActivity2,点击进去显示Instance,下面有四个,说明泄漏了四次。
MAT使用定位内存泄漏
快速定位耗时方法
方式1
检测开始代码处添加:
Debug.startMethodTracing();
检测结束代码处添加:
Debug.stopMethodTracing();
使用adb pull将生成的**.trace文件导出到电脑,然后使用Android Studio的Profiler加载。
![](https://img.haomeiwen.com/i1848495/40ad188d3b99a559.png)
打开 Android Device Monitor,在 DDMS 中打开 trace 文件或者直接使用 SDK 中的 TraceView:, TraceView 加载 trace 文件:
![](https://img.haomeiwen.com/i1848495/f0d57eab8727d38e.png)
上图介绍了 TraceView 的大致内容:
上半部分显示了 不同线程的执行时间
其中不同的颜色表示不同的方法
同一个颜色越长,说明执行时间越久,如图中的主线程 main
空白表示这个时间段内没有执行内容
下半部分展示了不同方法的执行时间信息,关键指标有三个:
Cpu Time/Call :该方法平均占用 CPU 的时间
Real Time/Call :平均执行时间,包括切换、阻塞的时间,>= Cpu Time
Calls + Recur Calls/Total :调用、递归次数
点击下面的任意一个方法,可以看到它的详细信息:
Parents:选中方法的调用处
Children:选中方法调用的方法
方式2
打开Profiler -> CPU -> 点击 Record -> 点击 Stop -> 查看Profiler下方Top Down/Bottom Up 区域找出耗时的热点方法。
Profile CPU
1、Trace types
Trace Java Methods
会记录每个方法的时间、CPU信息。对运行时性能影响较大。
Sample Java Methods
相比于Trace Java Methods会记录每个方法的时间、CPU信息,它会在应用的Java代码执行期间频繁捕获应用的调用堆栈,对运行时性能的影响比较小,能够记录更大的数据区域。
Sample C/C++ Functions
需部署到Android 8.0及以上设备,内部使用simpleperf跟踪应用的native代码,也可以命令行使用simpleperf。
Trace System Calls
检查应用与系统资源的交互情况。查看所有核心的CPU瓶。内部采用systrace,也可以使用systrace命令。
Android 音视屏基础
在Android中,我们有三种方式来实现视频的播放:
1、使用其自带的播放器。指定Action为ACTION_VIEW,Data为Uri,Type为其MIME类型。
2、使用VideoView来播放。在布局文件中使用VideoView结合MediaController来实现对其控制。
3、使用MediaPlayer类和SurfaceView来实现,这种方式很灵活。
SurfaceView和TextureView
SurfaceView优点:
可以在一个独立的线程中进行绘制,不会影响主线程
使用双缓冲机制,播放视频时画面更流畅
SurfaceView缺点:
虽然继承自view,但是view的很多属性是用不了的,Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中。SurfaceView 不能嵌套使用
SurfaceView 的核心在于提供了两个线程:UI线程和渲染线程,两个线程通过“双缓冲”机制来达到高效的界面刷新效果。
不用画布,直接在窗口上进行绘图叫做无缓冲绘图。用了一个画布,将所有内容都先画到画布上,在整体绘制到窗口上,就该叫做单缓冲绘图,那个画布就是一个缓冲区。用了两个画布,一个进行临时的绘图,一个进行最终的绘图,这样就叫做双缓冲。
而这个双缓冲可以理解为,SurfaceView 在更新视图时用到了两张 Canvas:
frontCanvas:实际显示的canvas。
backCanvas:存储的是上一次更改前的canvas。
当使用 lockCanvas() 获取画布时,得到的实际上是 backCanvas 而不是正在显示的 frontCanvas,之后你在获取到的 backCanvas 上绘制新视图,再 unlockCanvasAndPost(canvas)此视图,那么上传的这张 canvas 将替换原来的 frontCanvas 作为新的 frontCanvas,原来的 frontCanvas 将切换到后台作为 backCanvas。
指标 | SurfaceView | TextureView |
---|---|---|
内存 | 低 | 高 |
绘制 | 及时 | 1-3帧的延迟 |
耗电 | 低 | 高 |
动画和截图 | 不支持 | 支持 |
网友评论