• java 笔记


    后台开发的过程中积累的关于java的杂记

    架构

    SSH框架

    为什么要分层?

    因为分层使代码变得清晰,容易写也容易阅读,更重要的是让代码扩展性更好,层与层之间的改动不会互相影响

    各层的分工

    1. dao——与数据库交互
    2. service——处理业务逻辑,调用dao层方法
    3. action——用来控制转发,接到请求交给service处理

    dao是用于操作数据用的,service是为页面功能服务的,在service中对数据进行处理计算,然后返回数据结果到ACTION,而action则再对数据进一步处理,比如把list转成json,把两个service数据进行合并等,并发送到jsp页面显示。

    并发相关

    ReentrantLock

    参考Java多线程11:ReentrantLock的使用和Condition

    1. lock之后要自己unlock
    2. lock相比synchronized更加灵活,可以通过trylock判断锁是不是被占用了,在被占用的情况下可以忙其他事,而不是直接就阻塞了
    3. lock持有的是对象监视器,也就是类似于syncronized(this){} ,但是需要注意这两者持有的对象监视器是不同的
    4. lock配置Condition的signal和await可以实现syncronized的wait和notify来实现等待/通知模型,相比之下Condition更灵活:一个lock实例可以创建多个Condition实例,实现多路通知和有选择性的通知,而不是像notify一样是由jvm随机选择的

    BlockingQueue

    概念

    阻塞队列是一种支持当获取元素时会阻塞直到队列不为空,当插入元素时阻塞直到队列有空间。

    方法

    操作阻塞队列有四种形式的方法,add/remove/element抛出异常,offer/poll/peek返回具体值,put/take阻塞,offer(e, time, unit)、poll(time, unit)指定等待的最长时间

    不同实现

    1. ArrayBlockingQueue 只有一个锁,通过两个condition来实现阻塞、通知,添加和删除数据时只允许一个被执行
    2. LinkedBlockingQueue 有两个锁,putLock和takeLock,各自维护一个condition,添加和删除数据可以允许并行,当然删除和添加最多各自有一个线程在执行。
    3. LinkedBlockingQueue 不仅在消费数据的时候进行唤醒插入阻塞的线程,而且在插入如果容量还没满,也会唤醒插入阻塞的线程

    jvm原理

    垃圾回收

    参考深入理解java垃圾回收机制
    垃圾回收就是java中的一个亮点(有利有弊),通过一定的算法自动管理对象的生命周期,防止内存泄漏(内存对象的生命周期超过了程序需要它的时长)

    垃圾回收的算法有:

    1. 引用计数:早期的算法,通过给堆中的每个对象内置一个引用计数器来实现(缺点是:无法检测循环引用)
    2. 标志、清洗算法
    3. 分代收集:频繁收集新生代,比较少的收集老生代,基本不收集持久代(分代回收的GC分为 minor gc 和 full/major gc,以下为两种gc的日志格式)
    • GC:Alt text
    • FULL GC:Alt text
    • 新的对象都在eden区上创建,当eden区的大小达到阈值就会发生GC,eden区中存活的对象会复制到survivor区,并清除eden中无效的对象,如果survivor区中的对象达到年龄限制或者大小达到阈值,就会将存活的对象复制到old区,如果这时old区空间不足就会发生full gc,full gc之后old区的空间仍然无法承载young区要晋升的对象大小,就会发生OOM

    内存分配

    参考Java里的堆(heap)栈(stack)和方法区(method)深入探究JVM | 探秘Metaspace

    内存分为 heap、stack、method
    heap:

    1. 堆存放的都是对象,空间大但是访问慢(时空守恒)
    2. 为所有线程所共享
    3. java heap主要分成三种:
    • young:主要用来存放新生的对象
    • old: 主要用来存放生命周期长的内存对象
    • permanent:主要用来存放类和方法的元数据信息和常量池 ,类被加载后就放入这个区域。GC不会对持久层进行清理,
    • metaspace:在java8中持久代已经被移除了,因为持久代的大小是固定的,所以在类加载很多的情况下,容易出现OOM:PermGen space错误,类和方法的元数据被移入元数据区。(存在于本地内存中,所以大小只受物理内存的影响)元数据区是自动增长的,通过-XX:MaxMetaSpaceSize来限制Metaspace的大小,以前的Perm参数失效,超过最大值将会在metaspace发生full gc收集dead class或者classloader

    stack:

    1. 栈区放的都是基础数据类型和对象的引用
    2. 保存函数调用的现场

    method:

    1. 方法区也为所有线程所共享(另一种说法也就是permanent区)
    2. 方法区存放的都是在整个程序中永远唯一的元素,包括class、static变量
    3. 常量池也是方法区的一部分,存放程序中的字面量如”hello“ 以及常量

    java 集合框架

    哈希结构

    哈希表的数组长度为什么总是习惯用2^n?

    hash 的时候总是需要对对象的hashCode取哈希表长度的模,对于2^n 取模,可以简化为 hash & (2^n - 1),提高效率

    jdk1.8中的hashmap中的 hash算法是什么?有什么优点?

    hash算法是(h = key.hashCode()) ^ (h >>> 16) ,通过这样hash,高位的变化反映到低位里,这样我们取模的时候hash & (length - 1) 就不会只取低位相关,防止有些hashCode只和高位相关造成的冲突过多

    hashtable、hashmap、concurrenthashmap 哈希家族的异同点

    1. 都是继承于map接口,用于存储键值对。hashtable是同步的,如果不需要线程安全,推荐使用hashmap代替hashtable,如果需要高并发线程安全的实现,使用ConcurrentHashMap代替hashtable
    2. 集合方法返回的iterators是“fail-fast”的,也就是说当hash结果被修改,除了通过iterator的remove方法外的改动,都会造成iterator抛出ConcurrentModificationException异常。因此,在并发修改的情况下,iterator很快失败并清除,而不是冒险在未来不确定的时间做不确定的事。hashtable的方法返回的emurations却不是fail-fast的
    3. hashmap可以接受null值,hashtable则不行
    4. HashMap可以通过下面的语句进行同步:Map m = Collections.synchronizeMap(hashMap);

    ConcurrentHashMap(面试必考)

    concurrentHashMap是一个并发的hash表的实现,它支持完全的并发读取,支持大数量的并发更新操作。

    高性能的原因:

    1. 用分离锁实现多个线程间的更深层次的共享访问,不再是只有一个线程能同时持有容器的锁了。
    2. 利用hashEntry的不变性(final hash,key,next)来降低读操作对加锁的需求
    3. 用volatile变量协调读写线程间的内存可见性

    缺点

    1. 返回的迭代器是弱一致性的,fail-safe并且不会抛出ConcurrentModificationException异常

    源码分析(基于jdk1.7)

    Alt text
    concurrentHashMap是由segment数组和hashEntry数组组成的,segment是一种可重入锁ReentranLock,在CHM中扮演锁的角色,HashEntry用于存储键值对数据。一个CHM中包含一个segment数组,segment的结构和hashmap类似,一个segment中包含一个hashEntry数组,每个HashEntry都是一个链表的结构,每个segment守护着自己的hashEntry数组,要往这一段hashEntry数组中修改,必须先获得相应的锁

    arrayList、linklist、vector

    1. arrayList 内部用数组实现,随机访问和遍历快,插入删除慢
    2. linklist 内部用链表实现,适合数据的动态插入和删除,随机访问和遍历慢
    3. vector 跟 arraylist差不多,除了以下几点
      • vector是线程安全的,因此访问速度也较慢
      • arraylist在内存不够时扩展50% + 1个,vector默认扩展一倍

    java 代码执行顺序

    JAVA类首次装入时,会对静态成员变量或方法进行一次初始化,但方法不被调用是不会执行的,静态成员变量和静态初始化块级别相同,非静态成员变量和非静态初始化块级别相同。

    初始化顺序:先初始化父类的静态代码--->初始化子类的静态代码-->(创建实例时,如果不创建实例,则后面的不执行)初始化父类的非静态代码(变量定义等)--->初始化父类构造函数--->初始化子类非静态代码(变量定义等)--->初始化子类构造函数

    tips:
    若子类没有显示调用父类的构造函数,则默认调用父类的无参构造函数,如果父类没有则编译错误

    java 命令行参数

    1. -classpath
      java 通过指定-classpath 来指定虚拟机搜索的你要运行的类的目录、jar文件名、zip文件名,之间用;(linux 用:)隔开。否则java查不到你的class文件就会报java.lang.NoClassDefFoundError异常,在运行时可以通过System.getProperty(“java.class.path”)得到jvm查找类的路径
      也可以通过CLASSPATH环境变量来指定类搜索路径,建议用-cp

    2. -DpropertyName=value
      系统属性,可以通过System.getProperty(propertyName)获取value的值,用来设置全局变量值,如配置文件路径

    3. -Xms -Xmx 堆的最大最小值

    4. -Xss 线程堆的最大值

    5. --XX:+HeapDumpOnOutOfMemoryError
      当JVM不断地抛出OutOfMemory错误的时候,该命令会通知JVM拍摄一个“堆转储快照”, 并通过-XX:HeapDumpPath 指定该文件的保存路径,可以方便调试问题

    6. -XX:+UseParNewGC 使用多线程并发处理新生代GC

    7. -XX:+UseConcMarkSweepGC 使用CMS并发处理GC

    8. -Djava.awt.headless=true 无头模式,系统的配置模式,在该模式下,系统缺少了显示、键盘或鼠标。据说

    在Java服务器程序需要进行部分图像处理功能时,建议将程序运行模式设置为headless,这样有助于服务器端有效控制程序运行状态和内存使用(可防止在处理大图片时发生内存溢出)

    泛型

    • 上界:表示对泛型的限制,传进来的对象必须是class 或者 class 的子类
    • 通配符
      • java5之后添加了通配符 和 泛型(泛型我们是了解的),通配符的基本用法
    		GenericType<?>
    		GenericType<? extends upperBoundType> // 设置上界
    		GenericType<? super lowerBoundType> // 设置下界
    
    - 看了下 在 [Java 的泛型类型中使用通配符 - 博客频道 - CSDN.NET](https://app.yinxiang.com/shard/s15/nl/2659954/653fb6e2-ea9d-48f2-b4cf-5d5f749923fb/),觉得通配符主要是方便了**泛型对象作为方法参数可以引用泛型子类**,代码如下:
    
    	List<? extends Number> nums = new ArrayList<>();
        List<Integer> ints = Arrays.asList(1, 2);
        List<Double> doubles = Arrays.asList(1.1, 2.2);
    	nums.addAll(ints); 
    	nums.addAll(dbls); // addAll 方法使用了通配符作为参数
      }
    
     // print all Number 或 Number 子类的list, 如果没有通配符的话, 估计得一个一个子类都去实现以下。。。
      public void print(List<? extends Number> list) {
        list.forEach(x -> System.out.println(x));
      }
    
    - 通配符的限制:
    	- 不能用来直接创建变量对象
    	- 不能进行修改操作,例如下面的代码,编译器可能觉得,鬼知道你引用了哪个子类呢
    
    	List<? extends Number> nums = new ArrayList<Integer>();
    	nums.add(1); // 编译错误
    

    java 基础概念

    • 守护线程: 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作。当最后一个守护线程结束时,守护线程随同JVM一起结束工作。最典型的例子就是GC
    • volatile:
      • 用在多线程中,同步变量。一般情况下线程为了提高效率,会缓存主内存中的变量在自己的线程栈中,volatile声明的变量则不能缓存,保证了线程之间的变量一致性[虽然自己测不出这个效果。。。]
      • 不能保证线程安全。引用例子如下

    假如线程1,线程2 在进行read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值,在线程1堆count进行修改之后,会write到主内存中,主内存中的count变量就会变为6;线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6;导致两个线程及时用volatile关键字修改之后,还是会存在并发的情况。

    • 创建对象:

    创建对象有哪些方法?

    • 最普通的new(给对象分配空间,调用构造函数初始化,返回引用)

    • 调用对象的clone方法

      • clone给对象分配空间后,直接在内存上对已有对象影印,不需要构造函数
      • 需要实现CloneAble接口才能调用对象的clone方法,clone是一种浅复制,例如对象中包含一个String,那么新的对象中的String 跟原来的指向同一个字符串。
      • 要实现深复制,需要实现clone方法,不仅clone本身,还需要包含的引用对象
    • 运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法。【没玩过】

    • 访问修饰符: 只有private在同一个包内不能访问,其他包的只能public能访问

    日志操作

    log4j

    log4j 使用写日志变得很简单,支持多个输出和格式化

    log4j 主要由三个部分组成 logger 、appender、 layout

    • logger 负责日志的收集,主要由rootlogger 和 其他各个类的logger组成,通过logger.info等方法来记录消息,子logger中可以自定义各种配置,如果没有设置就会向上级的logger查找相应的配置,最上层为rootlogger
    • appender负责日志的输出,可以输出到文件、控制台、数据库、kafka等等
    • layout绑定到相应的的appender来格式化它的日志输出格式,丰满多姿
  • 相关阅读:
    Appium自动化环境搭建
    真机Android 8.0版本以上uiautomator定位元素-Unsupported protocol: 2/Unexpected error while obtaining UI hierarchy错误处理
    rsa非对称加密
    QT使用OpenSSL的接口实现RSA的加密解密
    lua安装后其他库使用产生问题解决方法
    log4cpp的使用描述
    std::function和std::bind
    C++11线程睡眠的方式
    高精度计时器
    如何解决TCP拆包粘包问题
  • 原文地址:https://www.cnblogs.com/kaixuanguilai/p/7773877.html
Copyright © 2020-2023  润新知