LOADING

加载过慢请开启缓存 浏览器默认开启

开发日常小记

2023/7/16 后端 后端 Java

浏览器编码问题

在IE浏览器中处理文件名时需要使用URLEncoder.encode()方法,而在Chrome浏览器中使用该方法会出现乱码,需要使用new String(fileName.getBytes(“UTF-8”), “ISO-8859-1”)方法。具体实现如下:

public class EncodingUtils {
    /**
     * 转换为文件名
     *
     * @param request
     * @param fileName
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String convertToFileName(HttpServletRequest request, String fileName) throws UnsupportedEncodingException {
        // 针对IE或者以IE为内核的浏览器:
        String userAgent = request.getHeader("User-Agent");
        if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
            fileName = URLEncoder.encode(fileName, "UTF-8");
        } else {
            // 非IE浏览器的处理:  
            fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
        }
        return fileName;
    }
}

vscode和IDEA启动项目时的区别

在vscode中启动项目时,会在项目根目录下生成一个.vscode文件夹,里面有一个launch.json文件,该文件中配置了项目启动时的一些参数,如下:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "java",
            "name": "Debug (Launch)-Application",
            "request": "launch",
            "mainClass": "com.example.demo.DemoApplication",
            "projectName": "demo",
            "args": ""
        }
    ]
}

而在IDEA中启动JAVA项目修改一下run的configuration到你所需要的版本即可
不然就会出现vscode能正常启动,而IDEA启动时报错的情况。

grade推送github出现的问题

./gradlew bootbuildImage –imageName=registry.cn-hangzhou.aliyuncs.com/xxx/xxx:latest这个不行,会报错

改用docker推送
docker推送github

docker tag ghcr.io/github_name/
docker push ghcr.io/github_name/

redis常用命令

临时启动redis服务:redis-server.exe redis.windows.conf
启动redis客户端:redis-cli.exe
使用set和get命令:set key value,get key
创建永久服务:redis-server –service-install redis.windows.conf –loglevel verbose
启动服务:redis-server –service-start
停止服务:redis-server –service-stop
验证权限:auth password

HashMap为什么线程不安全

  1. 多线程下扩容时会出现死循环,导致CPU利用率接近100%,所以在多线程下使用ConcurrentHashMap,JDK1.7中使用头插法,扩容时有可能导致环形链表,JDK1.8中使用尾插法,扩容时不会出现环形链表
  2. 多线程下put时会出现数据丢失的情况,如果计算出来的索引位置是相同的,那会造成前一个key被后一个key覆盖,从而导致数据丢失,JDK1.7和JDK1.8中都存在
  3. put和get并发时,可能导致get到的数据为null,例如线程1执行put,因为元素个数超过threshold而导致rehash,线程2此时执行get,就会出现这个问题。JDK1.7和JDK1.8中都存在。

JDK1.7中concurrenthashmap由Segment数组结构和HashEntry数组构成。也就是concurrenthashmap把哈希桶切分成小数组Segment,每个小数组有n个HashEntry组成。其中Segment继承了ReentrantLock,所以Segment是一种可重入锁,扮演锁的角色,HashEntry用于存储键值对数据。

JDK1.8中concurrenthashmap则是使用数组+链表+红黑树结构,在锁的实现上,抛弃了原有的Segment分段锁,采用CAS+synchonized来保证并发安全性,锁的粒度更细,效率更高。

heap和stack的区别

  1. 申请方式不同:heap是程序员自己申请,stack是系统自动分配
  2. 申请后系统响应不同:heap是动态分配,系统响应速度慢,stack是静态分配,系统响应速度快
  3. 申请大小的限制:stack要连续的内存区域,所以大小受限于系统栈的大小,heap大小受限于计算机的内存大小
  4. 申请效率不同:stack系统自动分配,效率高,heap手动new,效率低,且容易产生内存碎片
  5. 存储内存不同:stack存储局部变量,heap存储对象

如何判断一个对象是否存活

  1. 引用计数法:给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1,当引用失效时,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的
  2. 可达性分析算法:通过一系列的称为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的

垃圾回收器

  1. Serial收集器:单线程收集器,只会使用一个线程进行垃圾回收,会暂停所有用户线程,直到垃圾回收结束,适用于单核CPU,收集垃圾时必须使用stop-the-world,使用复制算法。
  2. ParNew收集器:Serial收集器的多线程版本,除了使用多线程进行垃圾回收外,其余行为和Serial收集器一样,适用于多核CPU,收集垃圾时必须使用stop-the-world,使用复制算法。
  3. Parallel Scavenge收集器:多线程收集器,复制算法的收集器,和ParNew的最大区别是GC自动调节策略。虚拟机会根据系统的运行状态收集性能监控信息,动态调整垃圾收集线程数,达到目标的吞吐量,适用于后台运算而不需要太多交互的任务,收集垃圾时必须使用stop-the-world。
  4. Serial Old收集器:Serial收集器的老年代版本,使用标记-整理算法。
  5. Parallel Old收集器:Parallel Scavenge收集器的老年代版本,使用标记-整理算法。
  6. CMS:以获取最短回收停顿时间为目标的收集器,使用标记-清除算法,收集垃圾时不需要使用stop-the-world,但是会产生大量空间碎片,如果空间碎片过多,将会导致CMS进行Full GC,从而导致停顿时间变长。
  7. G1:标记整理算法实现,运行流程包括:初始标记,并发标记,最终标记,筛选回收。G1将堆内存分为多个大小相等的区域,每个区域都有一个标记位,标记位为1表示该区域中有存活对象,为0表示该区域中没有存活对象,G1会优先回收标记位为0的区域,适用于大内存应用,收集垃圾时不需要使用stop-the-world。

CMS的回收过程

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它使用标记-清除算法来进行垃圾回收。CMS收集器的回收过程包括以下几个阶段:

  1. 初始标记阶段:该阶段会暂停所有用户线程,标记所有直接与GC Roots相连的对象,并记录下所有老年代对象的引用关系,该阶段的时间比较短,一般只需要几百毫秒。
  1. 并发标记阶段:该阶段会和用户线程一起并发执行,标记所有从GC Roots可达的对象,并记录下所有老年代对象的引用关系,该阶段的时间比较长,可能需要几秒钟甚至几分钟。
  1. 重新标记阶段:该阶段会暂停所有用户线程,重新标记所有在并发标记阶段发生变化的对象,并记录下所有老年代对象的引用关系,该阶段的时间比较短,一般只需要几百毫秒。
  1. 并发清除阶段:该阶段会和用户线程一起并发执行,清除所有不再使用的对象,并释放它们占用的内存空间,该阶段的时间比较长,可能需要几秒钟甚至几分钟。

需要注意的是,CMS收集器在清除对象时不会进行内存整理,因此会产生大量空间碎片,如果空间碎片过多,将会导致CMS进行Full GC,从而导致停顿时间变长。为了解决这个问题,可以使用G1收集器等其他收集器来进行优化。

G1的回收过程

  1. 初始标记(会STW):仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
  2. 并发标记:从 GC Roots 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理在并发时有引用变动的对象。
  3. 最终标记(会STW):对用户线程做短暂的暂停,处理并发阶段结束后仍有引用变动的对象。
  4. 清理阶段(会STW):更新Region的统计数据,对各Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,必须暂停用户线程,由多条回收器线程并行完成的。

双亲委派机制

双亲委派机制是指当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求都应该传送到启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

使用双亲委派机制的原因是:为了防止内存中出现多个相同的字节码,因为如果没有双亲委派的话,用户之间定义一个java.lang.Object类,那么系统中就会出现多个相同的Object类,而且这些类之间还是不兼容的,因为类加载器在加载类时,会先从自己的缓存中查找是否已经加载过该类,如果没有加载过,才会从父类加载器中查找,如果父类加载器中也没有加载过,才会自己加载。

如何打破双亲委派机制:自定义类加载器,重写loadClass方法,不去调用父类的loadClass方法,而是自己去加载类。

本文作者:GWB
当前时间:2023-11-09 11:11:10
版权声明:本文由gwb原创,本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 国际许可协议。
转载请注明出处!