如何应对 Android 面试官 -> 内存如何进行优化?玩转 LeakCanary

news2024/11/14 17:19:36

前言


在这里插入图片描述

本章主要围绕内存相关的知识点讲解;

内存分配


在内存优化中,我们通常需要借助一些常用的 adb 命令,方便我们快速定位,下面是一些常用的 adb 命令总结

常用 adb 命令

adb shell getprop ro.product.model // 手机型号

adb shell dumpsys battery //电池信息

adb shell wm size  //屏幕尺寸

adb shell wm density  //屏幕像素密度

adb shell dumpsys window displays // 查看总得设备信息,包含了显示屏编号,像素密度,分辨率等等

adb shell settings get secure android_id // android id 一般用来标识不同的手机

adb shell service call iphonesubinfo 1 // IMEI 

adb shell getprop ro.build.version.release // 版本

adb shell cat /proc/cpuinfo // cpu 信息

adb shell cat /system/build.prop // 查看构建文件信息

adb shell cat /proc/meminfo // 查看内存信息

adb shell dumpsys meminfo //获取内存信息

adb shell procrank //查看进程内存排名

adb shell top

adb shell top |grep your app name

adb shell vmstat // 

adb shell vmstat 2 // 

adb shell top -d 20 > meminfo

内存指标概念

在这里插入图片描述

RSS(共享库、so 动态链接库)
USS 进程独占内存空间
在这里插入图片描述

procs(进程)

r: Running队列中进程数量

b: IO wait的进程数量

memory(内存)

free: 可用内存大小

mapped:mmap映射的内存大小

anon: 匿名内存大小

slab: slab的内存大小

system(系统)

in: 每秒的中断次数(包括时钟中断)

cs: 每秒上下文切换的次数

cpu(处理器)

us: user time (用户时间)

ni: nice time

sy: system time(系统时间)

id: idle time

wa: iowait time

ir: interrupt time

用户时间 + 系统时间 = 进程时间;

内存分配


在这里插入图片描述

Java 内存分配模型

在这里插入图片描述

内存分配模型,详细的讲解可以看我之前的文章(https://juejin.cn/post/7314365534107238438)

java 对象生命周期

在这里插入图片描述

  • 创建
    • 为对象分配内存空间,调用构造方法构造对象
  • 应用
    • 此时对象至少被一个强引用持有
  • 不可见
    • 对象还存在,但是没有被强引用了,如果被GC扫描到了,就会进行可达性分析
  • 不可达
    • 可达性分析,发现不可达(也就是没有任何强应用了)
  • 收集
    • GC准备对该对象内存空间进行重新分配
    • 如果重写了finalize方法,这个方法就会被调用
  • 终结
    • 被垃圾回收器 回收
  • 对象空间重新分配
    • 对象被回收之后,这块空间重新分配

java 对象的内存布局

在这里插入图片描述

  • 对象头
    • 存储对象自身的运行时数据
      • 哈希码
      • GC 分代年龄
      • 锁状态标识
      • 线程持有的锁
      • 偏向线程 ID
      • 偏向时间戳
    • 类型指针
    • 若为对象数据,还应该记录数组长度的数据
  • 实例数据
  • 对齐填充

本地方法栈

线程私有,每个线程不一样

程序计数器

线程切换使用,也是线程私有;

垃圾回收算法

在这里插入图片描述

标记清除

位置不连续,产生内存碎片,效率略低,两遍扫描。多次进行标记清除算法,内存就会千疮百孔;先标记存活对象,然后清除回收对象,产生内存碎片;

在这里插入图片描述

复制算法

实现简单,运行高效,没有内存碎片,但是利用率只有一半;将内存一分为二,将存活对象复制到剩余的一半,其余的回收

在这里插入图片描述

标记整理算法

没有内存碎片,效率偏低,两边扫描、指针需要调整;先标记,然后将存活对象整理到一起。剩下的回收掉

在这里插入图片描述

分代收集算法

综合应用上面的算法;

四种引用

在这里插入图片描述

Android 内存回收机制

在这里插入图片描述

Android Kill 机制

在这里插入图片描述

  • 在 Android 的 lowmemroykiller 机制中,会对于所有进程进行分类,对于每一类别的进程会有其 oom_adj 值的取值范围,oom_adj值越高则代表进程越不重要,在系统执行低杀操作时,会从 oom_adj 值越高的开始杀;
  • 对于期望较长时间留在后台的服务,应该将服务运行在单独的进程里,即是 UI 进程与 Servie 进程分离,这样期望长时间留在后台的 Serivce 会存在与一个被 lowmemorykiller 分类为 Service 进程的服务而获得较小的Adj 值,而占有大量内存的UI进程则会分类为 Cached 进程,能够在需要的时候更快地被回收;
oom_adj
  • 当所有应用退后台之后并不会立马被杀掉,而是通过 oom_adj 进行了一个分级[-16, 15] 从 -16 到 15 的这样的一个区间,应用对应的数字越小 越不容易被 OOM Killer 杀到
    • ams 又对齐进行了一个更高等级的区分 oom_score_adj,将应用优先级的区间划分为[-1000, 1000],从 -1000 到 1000,score 的值越低越不容易被杀掉;
  • 如果两个应用的 oom_adj 值一样,那么哪个 app 占用内存多,哪个就优先被杀掉。所以要尽可能的降低应用进入后台后的内存,才能保证尽可能的不被系统杀掉;

在这里插入图片描述

Android App内存组成以及限制

Android 给每一个 App 都分配一个 VM,让 App 运行在 Dalvik 上,这样即使 App 崩溃了也不会影响到系统,系统给 VM 分配了一定的内存大小,App 可以申请使用的内存大小不会超过此硬性逻辑限制,就算物理内存富余,如果应用超出 VM 最大内存,就会出现内存溢出 crash;

由程序控制操作的内存空间在 heap 上,分为 java heapsize 和 native heapsize;

native 层内存申请不受其限制,native 层受 native process 对内存大小的限制;

修改系统为每个App分配的内存大小;默认为每个app分配 16m 的内存大小;

在这里插入图片描述

LeakCanary 源码解析


从垃圾回收机制、源码分析、优缺点中分析;

总结的详细流程如下:

  • 通过 provider 进行 Leakcanary 的初始化逻辑
    • provider 的 onCreate 方法中,获取 Application
    • 加载 Leakcanary
      • 检查当前线程是否在主线程
      • 检查是否已经加载了 Leakcanary,如果加载了,则直接返回
      • 创建 Activity 的监视
        • 创建 Activity 的 onDestory 回调监视
          • 将可达的 Activity 删除
          • 根据 Activity 创建对应的弱引用,并绑定 ReferenceQueue
          • 将 reference 保存到 watchedObjects 数组中
          • 启动延时 5s 任务
          • 获取 GC 无法回收的 Activity
          • 通知内存泄露
        • 同 Application 绑定
      • 创建 Fragment 的监视
      • 调用上层模块 InternalLeakCanary.invoke

本质就是:监听 Activity 的 onDestory 方法回调,过 5s 之后进行一次 GC,通过 WeakReference 引用链看它有没有销毁;

LK 中使用的 垃圾回收算法

可达性分析算法、引用计算算法;

可以作为 GC Root 的对象有哪些?

  • 在线程栈中的局部变量(即正在被调用的方法里面的参数和局部变量)
  • 存活的线程对象
  • JNI的引用
  • Class对象(在Android中Class被加载后是不会被卸载的)
  • 引用类型的静态变量

在这里插入图片描述

模块层级(LK都有哪些模块,以及对应的模块都是做什么的?)

在这里插入图片描述

leakcanary-android

集成入口模块,提供 LeakCanary 安装,公开 API 等能力

leakcanary-android-core

核心模块

lealcanary-object-watcher、lealcanary-object-watcher-android、lealcanary-object-watcher-android-androidx、lealcanary-object-watcher-android-support-fragment

对象实例观察模块,在 Activity,Fragment 等对象的生命周期中,注册对指定对象实例的观察,有 Activity,Fragment,Fragment View,ViewModel 等;

leakcanary-android-process

和 leakcanary-android 一样,区别是会在单独的进程进行分析;

shark

hprof 文件解析与分析的入口模块;

shark-android

提供特定于 Android 平台的分析能力。例如设备的信息,Android 版本,已知的内存泄露问题等;

shark-graph

分析堆中对象的关系图模块;

shark-hprof

解析 hprof 文件模块;

share-log

日志模块;

LeakCanary 注册

<application>

    <provider
        android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
        android:authorities="${applicationId}.leakcanary-installer"
        android:exported="false"/>

</application>

这是通过注册 provider 进行 Leakcanary 的初始化逻辑,我们进入 AppWatcherInstaller 中看下

internal sealed class AppWatcherInstaller : ContentProvider() {
    
    internal class LeakCanaryProcess : AppWatcherInstaller() {
        override fun onCreate(): Boolean {
          super.onCreate()
          AppWatcher.config = AppWatcher.config.copy(enabled = false)
          return true
        }
    }
    
    override fun onCreate(): Boolean {
        // 获取 Application
        val application = context!!.applicationContext as Application
        // 加载 LeakCanary
        InternalAppWatcher.install(application)
        return true
    }
}

加载 Leakcanary,我们进入这个方法看下:

internal object InternalAppWatcher {
    // 加载
    fun install(application: Application) {
        // 检查当前线程是否在主线程
        checkMainThread()
        if (this::application.isInitialized) {
          // 如果 Leakcanary 已经加载过了,直接返回
          return
        }
        InternalAppWatcher.application = application
        val configProvider = { AppWatcher.config }
        // 监视 Activity
        ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
        // 监视 Fragment
        FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
        // 调用上层模块InternalLeakCanary.invoke
        onAppWatcherInstalled(application)
  }
}

我们进入 ActivityDestroyWatcher 看下 Activity 是如何被监视的;

internal class ActivityDestroyWatcher private constructor(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) {
    
    // 创建 Activity 的 destory 监听回调
    private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        // Activity 的 onDestory 回调,触发存在对象检查
        if (configProvider().watchActivities) {
          // 通过 objectWatcher 监视 Activity
          objectWatcher.watch(activity)
        }
      }
    }
    
    companion object {
        fun install(
          application: Application,
          objectWatcher: ObjectWatcher,
          configProvider: () -> Config
        ) {
          // 创建 Activity 的 onDestory 监听回调
          val activityDestroyWatcher =
            ActivityDestroyWatcher(objectWatcher, configProvider)
 // 绑定 Application           application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
        }
  }
}

我们进入 ObjectWatcher 中看下 Acivity 是如何被监视的;

@Synchronized fun watch(
    watchedObject: Any,
    name: String
  ) {
    if (!isEnabled()) {
      return
    }
    // 将可达的 activity 删除
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    // 根据 activity 创建对应的弱引用,并绑定ReferenceQueue
    val reference =
      KeyedWeakReference(watchedObject, key, name, watchUptimeMillis, queue)
    
    // 将 reference 保存到 watchedObjects 数组中
    watchedObjects[key] = reference
    // 启动延时 5s 任务
    checkRetainedExecutor.execute {
      // 获取 GC 无法回收的 Activity
      moveToRetained(key)
    }
  }

我们进入这个 removeWeaklyReachableObjects 方法看下,可达的 Activity 是怎么删除的;

private fun removeWeaklyReachableObjects() {
    
    var ref: KeyedWeakReference?
    do {
      // 重点,在 GC 或者 finalization 之前,在 WeakReferences 的被引用对象(这里是Activity)的可达性更改时,会把 WeakReferences 添加到创建时候指定的 ReferenceQueue 队列,这些可达性变更得对象,就是内存不泄露对象
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        // 在 watchedObjects 中删除不发送内存泄漏对象,剩下内存泄漏对象;
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }

我们接着看下这个延迟 5s 任务是如何创建的;

val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5)

private val checkRetainedExecutor = Executor {
    //在主线程延时五秒执行任务
    mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}

我们接着看下无法回收的 Activity 是如何获取的,进入 moveToRetained 方法看下;

@Synchronized private fun moveToRetained(key: String) {
    // 将可达activity删除
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
      // 保存当前时间作为泄漏时间
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      // 通知 InternalLeakCanary 发生内存泄漏
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
}

我们来看下内存泄露的场景是如何进行上报的,我们进入 HeapDumpTrigger 的 onObjectRetained 方法看下;

fun onObjectRetained() {
    // 再次检查内存是否泄露
    scheduleRetainedObjectCheck("found new object retained")
}

我们进入 scheduleRetainedObjectCheck 方法看下;

private fun scheduleRetainedObjectCheck(
    reason: String,
    delayMillis: Long
  ) {
    if (checkScheduled) {
      return
    }
    checkScheduled = true
    backgroundHandler.postDelayed({
      checkScheduled = false
      // 检查泄漏对象
      checkRetainedObjects(reason)
    }, delayMillis)
  }

我们进入 checkRetainedObjects 方法看下;

private fun checkRetainedObjects(reason: String) {
    ...
    
    if (retainedReferenceCount > 0) {
      // 执行一次 GC,确认所有存在对象都是泄露对象;
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }
    // 检查当前所有存在对象的个数
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
      showRetainedCountWithDebuggerAttached(retainedReferenceCount)
      // 如果配置了 debug 不使用 heap 且正在 debug,延时 20s 在执行checkRetainedObjects(reason)
      scheduleRetainedObjectCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS)
      return
    }
    
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    dismissRetainedCountNotification()
    // 执行dump Heap操作
    val heapDumpFile = heapDumper.dumpHeap()
    lastDisplayedRetainedObjectCount = 0
    objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
    HeapAnalyzerService.runAnalysis(application, heapDumpFile)
  }

我们来看下当前所有存在对象的个数是如何检查的;

 private fun checkRetainedCount(
    retainedKeysCount: Int,
    retainedVisibleThreshold: Int
  ): Boolean {
    val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
    lastDisplayedRetainedObjectCount = retainedKeysCount
    if (retainedKeysCount == 0) {
      // 存在对象为 0
      return true
    }

    if (retainedKeysCount < retainedVisibleThreshold) {
      // 存在对象低于阈值5个
      if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
        showRetainedCountBelowThresholdNotification(retainedKeysCount, retainedVisibleThreshold)
        // 当前应用可见,或者不可见时间间隔少于 5s,重新安排到 2s 后执行 checkRetainedObjects
        scheduleRetainedObjectCheck(
            "Showing retained objects notification", WAIT_FOR_OBJECT_THRESHOLD_MILLIS
        )
        return true
      }
    }
    return false
  }

到此,Leakcanary 的流程就整体跑完了;

线上内存如何监控


在引入任何自动分析工具之前,对于 Activity 泄漏,一般都是在自动化测试阶段监控内存占用,一旦超过预期,则发起一次 GC 后进行 Dump Hprof 操作。分析人员将 Hprof 文件导入 MAT 中查看各个 Activity 的引用链,找出被静态成员或 Application 对象等长生命周期对象持有的 Activity,再进行修复。对于冗余的 Bitmap,也是将 Hprof 导入 Android Monitor 后通过 Android Monitor 自带的工具找出冗余的 Bitmap 对象。

自动化监测目标流程

  • 自动且较为准确地监测 Activity 泄漏,发现泄漏之后再触发 Dump Hprof 而不是根据预先设定的内存占用阈值盲目触发;
  • 自动获取泄漏的 Activity 和冗余 Bitmap 对象的引用链;
  • 能灵活地扩展 Hprof 的分析逻辑,必要时允许提取 Hprof 文件人工分析;

监测阶段

Activity 对象被生命周期更长的对象通过强引用持有,使 Activity 生命周期结束后仍无法被 GC 机制回收,导致其占用的内存空间无法得到释放

具体如何做?

  • Activity 在执行销毁的时候 我们如何得知?
  • 如何判断一个 Activity 无法被 GC 机制回收?

采用 ActivityLifeCycleCallbacks + WeakHashMap 的方式,我们可以不主动暴露 RefrenceQueue 这个对象,WeakHashMap 的 key 可以自动被弱引用,可以自动被回收,那么这个 key 就可以是 Activity,key 被回收了,那么 value 也就跟着被移除了,监听 Activity 的回收,也就达到监听泄露的目的了;

借助 ActivityLifeCycleCallbacks 的 onActivityDestoryed 回调,在回调中将传递过来的 Activity 放入 WeakHashMap 中,然后在 onStop 的回调中通过 GC 监测 Activity 有没有泄露;

好了,内存这块就讲到这里吧

下一章预告


继续搞内存

欢迎三连


来到来了,点个关注,点个赞吧,你的支持是我最大的动力~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2101715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTTP“请求”和“响应”的报头及正文详解

目录 一、请求 "报头" (header) 二、请求 "正文" (body) 2.1 application/x-www-form-urlencoded 2.2 multipart/form-data 2.3 application/json 三、HTTP 响应状态码 四、响应 "报头" (header) 五、响应 "正文" (body) 5.1…

如何设置Word文档部分内容无法编辑?

工作中&#xff0c;我们可能会在word中制作一些请柬、表格之类的&#xff0c;有些文件内容不想要进行修改&#xff0c;为了防止他人随意修改内容。我们可以设置限制编辑&#xff0c;可以对一部分内容设置限制编辑&#xff0c;具体方法如下&#xff1a; 我们将需要将可以编辑的…

记录 PyQt6 / PySide 6 自定义边框窗口的 Bug 及可能可行的解决方案:窗口抖动和添加 DWM 环绕阴影的大致原理

前言&#xff1a; 本篇文章将要讨论我在前不久发表的关于 PyQt6 / PySide6 自定义边框窗口代码及内容中的问题&#xff1a; &#xff08;终&#xff09;PyQt6 / PySide 6 Pywin32 自定义标题栏窗口 完全还原 Windows 原生窗口边框特效_pyside6 win32 无边框窗口-CSDN博客ht…

【C++11(一)之入门基础)】

文章目录 C简介统一的列表初始化&#xff5b;&#xff5d;初始化 std::initializer_liststd::initializer_list是什么类型&#xff1a;std::initializer_list使用场景&#xff1a; 声明autodecltypenullptr STL中一些变化 C简介 在2003年C标准委员会曾经提交了一份技术勘误表(…

报告 | 以消费者为中心,消费品零售行业数字化建设持续深化

​2024年是“消费促进年”&#xff0c;国内消费市场稳步复苏。在消费需求多样化、国家政策的推动下&#xff0c;“数字化转型”仍是消费品零售行业的年度主题词&#xff0c;是品牌方获取核心竞争力的必要途径。消费品零售行业的数字化转型重心有所调整&#xff0c;从线上渠道布…

ARM架构

一、内存分布结构 栈&#xff1a;局部变量 函数参数 函数返回地址 堆&#xff1a;程序员自己管理的内存区域&#xff0c;使用是需要动态申请&#xff0c;使用捷顺后需要释放 bss&#xff1a; 初始化为0和未初始化的全局及静态变量 data&#xff1a;初始化不为0全局及静态变量…

Opencv中的直方图(4)局部直方图均衡技术函数createCLAHE()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 创建一个指向 cv::CLAHE 类的智能指针并初始化它。 函数原型 Ptr<CLAHE> cv::createCLAHE (double clipLimit 40.0,Size tileGridSize…

JavaScript的BOM模型

一、浏览器环境概述(BOM) JavaScript 是浏览器的内置脚本语言&#xff0c;一旦网页内嵌了 JavaScript 脚本&#xff0c;浏览器加载网页&#xff0c;就会去执行脚本&#xff0c;从而达到操作浏览器的目的&#xff0c;实现网页的各种动态效果 二、script 元素工作原理 浏览器加…

网络编程 0903作业

作业 1、将TCP的CS模型再敲一遍 tcpserver.c #include <myhead.h> #define SERPORT 1111 #define SERIP "192.168.58.128" #define BACKLOG 40 int main(int argc, const char *argv[]) {int oldfd socket(AF_INET,SOCK_STREAM,0);//1、产生一个原始套接字…

pikachu文件包含漏洞靶场

File inclusion(local) 创建1.php 步骤一&#xff1a;选择一个球员提交 ../../../../1.php File Inclusion(remote)&#xff08;远程文件包含&#xff09; 步骤一&#xff1a;更改参数 php.ini ⾥有两个重要的参数 allow_url_fopen 、allow_url_include &#xff1b; 步骤二…

玩机进阶教程-----如何通过boot查看当前机型版本号 型号以及启动分区 提升保资料写固件成功率

在玩机过程中我们会遇到一些无法开机进系统的机型。而有需要其中的数据。如果简单的写入固件。可能会由于与当前机型版本不符或者版本差别太大的缘故而导致资料无法保存。如果当前机型有版本仿回滚机制。那么有可能误刷系统也会不开机。那么如何通过简单的操作来查看当前机型的…

GAMES202——作业5 实时光线追踪降噪(联合双边滤波、多帧的投影与积累、À-Trous Wavelet 加速单帧降噪)

任务 1.实现单帧降噪 2.实现多帧投影 3.实现多帧累积 Bonus:使用-Trous Wavelet 加速单帧降噪 实现 单帧降噪 这里实现比较简单&#xff0c;直接根据给出的联合双边滤波核的公式就能实现 Buffer2D<Float3> Denoiser::Filter(const FrameInfo &frameInfo) {int heigh…

科研小白教程|如何远程连接实验室服务器跑代码?

博主简介&#xff1a;努力学习的22级计算机科学与技术本科生一枚&#x1f338;博主主页&#xff1a; Yaoyao2024往期回顾&#xff1a; 【计算机系统架构】从0开始构建一台现代计算机|时序逻辑、主存储器|第3章每日一言&#x1f33c;: 总之岁月漫长&#xff0c;然而值得等待。—…

国内可以免费使用的gpt网站【九月持续更新】

GPT Hub 是我最近使用的一款智能文本生成工具平台&#xff0c;它支持多种AI模型&#xff0c;包括最新的GPT-4模型&#xff0c;并且可以在国内网络环境中直接访问。以下是我在使用过程中发现的一些特点&#xff1a; 多功能支持&#xff1a;不仅支持代码生成&#xff0c;还涵盖了…

【主机入侵检测】Wazuh解码器之JSON解码器

前言 Wazuh 是一个开源的安全平台&#xff0c;它使用解码器&#xff08;decoders&#xff09;来从接收到的日志消息中提取信息。解码器将日志信息分割成字段&#xff0c;以便进行分析。Wazuh 解码器使用 XML 语法&#xff0c;允许用户指定日志数据应该如何被解析和规范化。解码…

Java基础(10)- 学生管理系统项目

一、JavaBean编写 public class Student {private int id;private String name;private int age;private String sex;public Student() {}public Student(int id, String name, int age, String sex) {this.id id;this.name name;this.age age;this.sex sex;}public int g…

绝对定位导致内容自动换行问题解决

今天在做一个定位元素的时候遇到一个嵌套定位之后&#xff0c;使用绝对定位的元素的内容自动换行的问题&#xff0c;希望不换行只在一行显示。 可以通过添加 white-space: nowrap; 样式控制不换行 <div class"box"><div class"box1"><div …

深入剖析:中国国际大学生创新大赛中不可忽视的12个扣分点

深入剖析&#xff1a;中国国际大学生创新大赛中不可忽视的12个扣分点 前言1. 项目名称&#xff1a;第一印象的力量2. 项目逻辑&#xff1a;清晰的思路是关键3. 问题分析&#xff1a;深入挖掘痛点4. 需求分析&#xff1a;解决方案的导向5. 科研课题与评审维度的匹配6. 团队介绍&…

DataWhale AI夏令营-《李宏毅深度学习教程》笔记-task3

DataWhale AI夏令营-《李宏毅深度学习教程》笔记-task2 第五章 循环神经网络5.1 独热编码5.2 RNN架构5.3 其他RNN5.3.1 Elman 网络 &Jordan 网络5.3.2 双向循环神经网络 第五章 循环神经网络 循环神经网络RNN&#xff0c;RNN在处理序列数据和时间依赖性强的问题上具有独特…

渗透测试靶机--- DC系列 DC-6

渗透测试靶机— DC系列 DC-6 开启靶机&#xff0c;登录页面&#xff0c;平平无奇 扫描ip&#xff0c;端口&#xff0c;服务等信息 访问80&#xff0c;发现这里是WordPress站点 直接wpscan扫描一下用户名wpscan --url http://wordy -e u 这里可以将扫出来的五个用户名保存&…