当前位置: 首页>移动开发>正文

java摘要

1.hashmap结构,hashMap的数组长度一定保持2的次幂,什么对象能作为hashmap的key

 HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。hashMap的数组长度一定保持2的次幂可以使新的数组索引和老数组索引一致,减少原来散列后数据的复制和移动,减少碰撞。不可变对象可以作为Key,可变对象可能造成数据丢失。

Bootstrap ClassLoader:启动类加载器,属于虚拟机的一部分,负责加载JAVA_HOME>\lib目录下并且被虚拟机识别的类库(细分的还有Extension ClassLoader,加载lib\ext目录下的类库)

Application ClassLoader:系统类加载器,负责加载用户类路径(ClassPath)上指定的类库,开发者可以直接使用,如果没有自定义的类加载器,这个时程序中默认的类加载器。

类加载顺序:

1、先执行父类的静态代码块和静态变量初始化,并且静态代码块和静态变量的执行顺序只跟代码中出现的顺序有关。

2、执行子类的静态代码块和静态变量初始化。

3、执行父类的实例变量初始化

4、执行父类的构造函数

5、执行子类的实例变量初始化

6、执行子类的构造函数

7、静态方法与非静态方法只有被调用的时候才会被加载

2.hashtable,concurrentHashMap,hashtable比较

HashTable

底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化

初始size为11,扩容:newsize = olesize*2+1

计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

HashMap

底层数组+链表实现,可以存储null键和null值,线程不安全

初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂

扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入

插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)

当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀

计算index方法:index = hash & (tab.length – 1)

ConcurrentHashMap

底层采用分段的数组+链表实现,线程安全

通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)

Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术

有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁

扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容

3.抽象类和接口区别

如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。

如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。

如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。

抽象类可以有默认的方法实现,接口完全是抽象的。它根本不存在方法的实现。抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口。抽象类速度稍快。

4.String,StringBuilder,StringBuffer

这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String。String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的,String:适用于少量的字符串操作的情况,StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况,StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。

5.对象的深浅复制

在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

6.wait,sleep分别是谁的方法,区别

sleep()方法,属于Thread类中的。而wait()方法,则是属于Object类。虽然两者都是用来暂停当前运行的线程,但是 sleep() 实际上只是短暂停顿,因为它不会释放锁,而 wait() 意味着条件等待,这就是为什么该方法要释放锁,因为只有这样,其他等待的线程才能在满足条件时获取到该锁。

7.CountDownLatch的await方法是否安全,怎么改造

主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。其他N 个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。

主任务调用一次CountDownLatch实例的await()方法时,当前线程就会一直占用一个活动线程,如果多次调用,那么就会一直占用多个活动线程,如果调用次数大于固定活动线程数,那么就可能造成阻塞队列中某些子任务一直不被执行。解决方法主任务不要和子任务放到同一线程池。

子任务有可以因为其它原因,不去执行CountDownLatch实例的countDown()方法,造成主任务所在线程无限等待。解决办法是最好不要用CountDownLatch实例的await(),归避长时间阻塞线程的风险,任何多线程应用程序都有死锁风险,改用CountDownLatch实例的await(long timeout, TimeUnit unit),设定超时时间,如果超时,将返回false,这样我们得知超时后,可以做异常处理。

9.aqs,cas

AQS的全称是AbstractQueuedSynchronizer,即抽象队列同步器,其底层是volatile与CAS。?AQS最核心的数据结构是一个volatile int state 和 一个FIFO线程等待对列。state代表共享资源的数量,如果是互斥访问,一般设置为1,而如果是共享访问,可以设置为N(N为可共享线程的个数);而线程等待队列是一个双向链表,无法立即获得锁而进入阻塞状态的线程会加入队列的尾部。当然对state以及队列的操作都是采用了volatile + CAS + 自旋的操作方式,采用的是乐观锁的概念。

1、首先你要知道的是AQS的锁的类型是分为两种的:第一种是独占式锁的获取和释放(),第二中的是共享式的锁的释放和获取

2、第一步首先是用acquire(int arg)方法先拿到这个线程的共享资源的状态,这个状态变量是用volatile来修饰的,只有当获取到的state大于等于0时才表示获取锁成功(重入一次锁状态就加1,释放一次锁状态就减一),若果失败就把当前线程包装成一个node节点入队列(FIFO)

3、第二步就是第一步获取状态小于0的时候说明失败,然后调用的 addWaiter(Node mode)方法把该线程包装成一个节点,为了提搞性能,首先执行一次快速入队操作,即直接尝试将新节点加入队尾

4、然后再上一步的基础上,如果尝试把该节点加入队列尾部失败,这里又会去调用 enq(final Node node)方法,(1)先去判断这个队列是不是已经初始化了,如果初始化了就用CAS保证只有一个头结点可以初始化成功,如果没有初始化县初始化再CAS来保证只有一个线程节点创建成功,(2)最后在enq方法中进行无限次的自旋,直到,如果成功会直接使用CAS操作只能返回一个节点(compareAndSetTail(pred, node))

5、操作完了上面的操作之后,就相当于是线程节点已经成功的加入的等待队列中,然后进行的就是挂起当前线程,等待被唤醒。然后调用boolean acquireQueued(final Node node, int arg) ,先把锁的标记为默认为false,然后去判断该节点的前置结点是不是头结点,如果是把当前结点线程用setHead(node);设置为头结点(这里有个小坑,就是只有head头结点才是cpu正在执行的线程节点,后面的节点都是等待线程节点,而且在这个头结点执行的线程过程中头结点是可以中断的),如果设置头结点成功,就把锁标记为设置为true并返回,

6、然后再接着上一个步骤继续判断,如果没有获取锁成功,则进入挂起逻辑,也就是如果没有成功的话就进入下一个方法,node是当前线程的节点,pred是它的前置节点

boolean shouldParkAfterFailedAcquire(Node pred, Node node),在这个方法判断成功之后,会继续接着5的步骤把锁的标记设置为true,然后如果判断锁的标记是与否,否的话继续 cancelAcquire(node);

7、处理获取锁失败之后的挂起逻辑boolean shouldParkAfterFailedAcquire(Node pred, Node node)的方法,(1)前置节点的waitStatus是Node.SIGNAL则返回true,然后会执行parkAndCheckInterrupt()方法进行挂起,(2)如果前置节点的waitStatus大于0的话,把当前结点赋给前置结点的下一个结点,如果不大于0 的话,使用CAS的compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 最后挂起的状态改为false,它是用来判断当前节点是否可以被挂起,也就是唤醒条件是否已经具备,即如果挂起了,那一定是可以由其他线程来唤醒的。该方法如果返回false,即挂起条件没有完备,那就会重新执行acquireQueued方法的循环体,进行重新判断,如果返回true,那就表示万事俱备

8、这里继续执行步骤5的 void cancelAcquire(Node node) 方法,拿到当前失败线程节点的等待状态是不是小于0,大于的话直接Node.CANCELLED;赋值给正在遍历的线程节点的waitStatus 中,然后继续判断当前节点是不是尾节点,是的话使用CAS操作compareAndSetNext(pred, predNext, null);把节点设置为空,若不是尾节点的话,当大于0的时候跳出循环,继续如果当前节点的后继节点没有被取消就把前置节点跟后置节点进行连接,相当于删除了当前节点compareAndSetNext(pred, predNext, next);

9、最后是释放锁,先去过去当前节点的waitStatus,然后如果waitStatus小于0尝试去释放锁使用compareAndSetWaitStatus(node, ws, 0)CAS操作,然后去判断如果当前线程节点的下一个节点,如果发现节点的waitStatus 小于0,就说明找到了待唤醒的节点,然后不为空的时候,就去唤醒该节点。

CAS的全称是Compare and Swap,即比较并交换。比较的是当前内存中存储的值与预期原值,交换的是新值与内存中的值。这个操作是硬件层面的指令,因此能够保证原子性。CAS操作包含三个操作数——内存位置、预期原值和新值。在执行CAS操作时,先进行Compare操作,即比较内存位置的值与预期原值是否相等,若相等,则执行Swap操作将新值放入该内存位置。若不相等,则不进行Swap操作。

10.ThreadLocal原理,注意事项,参数传递

ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。它采用采用空间来换取时间的方式,解决多线程中相同变量的访问冲突问题。每个Thread的对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。

ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。

2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。

3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。

对于ThreadLocal使用前或者使用后一定要先remove,线程不销毁意味着上条线程set的ThreadLocal.ThreadLocalMap中的数据依然存在,那么在下一条线程重用这个Thread的时候,很可能get到的是上条线程set的数据而不是自己想要的内容。

11.Java的锁,内置锁,显示锁,各种容器及锁优化:锁消除,锁粗化,锁偏向,轻量级锁

公平锁/非公平锁:公平锁是指多个线程按照申请锁的顺序来获取锁。非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

可重入锁:可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

独享锁/共享锁:独享锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有。

互斥锁/读写锁:上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。互斥锁在Java中的具体实现就是ReentrantLock

读写锁在Java中的具体实现就是ReadWriteLock。

乐观锁/悲观锁:乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景。

分段锁:分段锁其实是一种锁的设计,ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。

偏向锁/轻量级锁/重量级锁:偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

自旋锁:自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

锁粗化:为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽可能短,但是大某些情况下,一个程序对同一个锁不间断、高频地请求、同步与释放,会消耗掉一定的系统资源,因为锁的讲求、同步与释放本身会带来性能损耗,这样高频的锁请求就反而不利于系统性能的优化了,虽然单次同步操作的时间可能很短。锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。

锁消除:锁消除是发生在编译器级别的一种锁优化方式。有时候我们写的代码完全不需要加锁,却执行了加锁操作。

优化策略:1:减少锁持有时间 ;2减小锁粒度;3:锁分离 ,根据同步操作的性质,把锁划分为的读锁和写锁,读锁之间不互斥,提高了并发性。?4:锁粗化??5:锁消除

Java中具有通过Synchronized实现的内置锁,和ReentrantLock实现的显示锁,内置锁获得锁和释放锁是隐式的,进入synchronized修饰的代码就获得锁,走出相应的代码就释放锁。与Synchronized配套使用的通信方法通常有wait(),notify()。wait()方法会立即释放当前锁,并进入等待状态,等待到相应的notify并重新获得锁过后才能继续执行;notify()不会立刻立刻释放锁,必须要等notify()所在线程执行完synchronized块中的所有代码才会释放。ReentrantLock是显示锁,需要显示进行 lock 以及 unlock 操作。相比于Synchronized要复杂一些,而且一定要记得在finally中释放锁而不是其他地方,这样才能保证即使出了异常也能释放锁。

java容器:1)Collection:一个独立元素的序列,这些元素都服从一条或者多条规则。?List必须按照插入的顺序保存元素,而set不能有重复的元素。Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。?

2)Map:一组成对的“键值对”对象,允许你使用键来查找值。

1、List接口

List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。

实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。

1)LinkedList类

LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。

注意:LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:List

list = Collections.synchronizedList(new LinkedList(…));

2)

ArrayList

ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并 没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。

和LinkedList一样,ArrayList也是非同步的(unsynchronized)。一般情况下使用这两个就可以了,因为非同步,所以效率比较高。

如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。

3)Vector类

Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的 Iterator是同一接口,但是,因为Vector是同步的,当一个 Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例 如,添加或删除了一些元素),这时调用Iterator的方法时将抛出 ConcurrentModificationException,因此必须捕获该 异常。

4)Stack 类

Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方 法,还有 peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

2、Set接口

Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。 Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。

Set容器类主要有HashSet和TreeSet等。

1)HashSet类

Java.util.HashSet类实现了Java.util.Set接口。

-> 它不允许出现重复元素;

-> 不保证和政集合中元素的顺序

->允许包含值为null的元素,但最多只能有一个null元素。

2)TreeSet?

TreeSet描述的是Set的一种变体——可以实现排序等功能的集合,它在讲对象元素添加到集合中时会自动按照某种比较规则将其插入到有序的对象序列中,并保证该集合元素组成的读uixiangxulie时刻按照“升序”排列。

Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。

1、Hashtable类

Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。**添加数据使用put(key,

value),取出数据使用get(key),这两个基本操作的时间开销为常数。**Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。

由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。

如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。

2、HashMap类

HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null

key,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。

HashTable和HashMap区别

第一、继承不同。publicclassHashtableextendsDictionaryimplementsMappublicclassHashMapextendsAbstractMapimplementsMap第二、Hashtable中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。第三、Hashtable中,key和value都不允许出现null值。在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。第四、两个遍历方式的内部实现上不同。Hashtable、HashMap都使用了Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。第五、哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。第六、Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

3、WeakHashMap类

WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。

12.servlet是否线程安全,如何改造

不是线程安全,Servlet 默认是单例模式,在web 容器中只创建一个实例,所以多个线程同时访问servlet的时候,Servlet是线程不安全的。?此时如果Servlet1中定义了实例变量或静态变量,那么可能会发生线程安全问题(因为所有的线程都可能使用这些变量)。

①实现SingleThreadModel 接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销。②同步对共享数据的操作,使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段;使得其同时处理客户请求的吞吐量降低,而且很多客户处于阻塞状态。③避免使用实例变量,在实际的开发中也应避免或最小化 Servlet 中的同步代码;在Serlet中避免使用实例变量是保证Servlet线程安全的最佳选择。

13.session与cookie的区别,get和post区别,tcp3次握手,文件上传用post还是get

Session是保存在服务器上的数据结构,用于跟踪用户的状态。此数据可以保存在群集、数据库、文件中。

Cookie是客户端存储用户信息的机制。它用于记录有关用户的一些信息,是实现会话的一种方式。

1、Cookie和Session都是会话技术,Cookie是运行在客户端,Session是运行在服务器端。

2、Cookie有大小限制以及浏览器在存cookie的个数也有限制,Session是没有大小限制和服务器的内存大小有关。

?? 3、Cookie有安全隐患,通过拦截或本地文件找得到你的cookie后可以进行攻击。

?? 4、Session是保存在服务器端上会存在一段时间才会消失,如果session过多会增加服务器的压力。

get和post区别: 1. GET使用URL或Cookie传参。而POST将数据放在BODY中。 2. GET的URL会有长度上的限制,则POST的数据则可以非常大。 3. POST比GET安全,因为数据在地址栏上不可见。HTTP协议中的两种发送请求的方法。GET和POST的底层也是TCP/IP,但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。GET和POST还有一个重大区别,对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。也和浏览器有关。文件上传用post,大小不限制。

tcp3次握手: (1) 第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。

(2) 第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。

(3) 第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。

完成三次握手,客户端与服务器开始传送数据。

4次挥手:(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送(报文段4)。

(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。

(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A(报文段6)。

(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。

14.session的存储

sessionid是一个会话的key,浏览器第一次访问服务器会在服务器端生成一个session,有一个sessionid和它对应。session在访问tomcat服务器HttpServletRequest的getSession(true)的时候创建,tomcat的ManagerBase类提供创建sessionid的方法:随机数+时间+jvmid。 存储在服务器的内存中,tomcat的StandardManager类将session存储在内存中,也可以持久化到file,数据库,memcache,redis等。客户端只保存sessionid到cookie中,而不会保存session,session销毁只能通过invalidate或超时,关掉浏览器并不会关闭session。

15.如何防止表单重复提交

1.表单提交之后,用JS禁用submit按钮,可防止心急用户重复点击提交按钮。2.用户提交表单之后,执行重定向,转到其他页面或者重定向。3.使用Cookie记录表单提交的状态,根据其状态可以检查是否已经提交表单。4.在数据库中添加唯一约束,防止重复写入相同数据。5.使用session在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),提交表单是将token隐藏到表单信息中,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,不一致说明表单重复提交。

16.jvm内存模型

1.什么是jvm?

(1)jvm是一种用于计算设备的规范,它是一个虚构出来的机器,是通过在实际的计算机上仿真模拟各种功能实现的。

(2)jvm包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域。

(3)JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。

2.jdk、jre、jvm是什么关系?

(1)JRE(Java Runtime Environment),也就是java平台。所有的java程序都要在JRE环境下才能运行。

(2)JDK(Java Development Kit),是开发者用来编译、调试程序用的开发包。JDK也是JAVA程序需要在JRE上运行。

(3)JVM(Java Virtual Machine),是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。

Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。

3.JVM原理

(1)jvm是java的核心和基础,在java编译器和os平台之间的虚拟处理器,可在上面执行字节码程序。

(2)java编译器只要面向jvm,生成jvm能理解的字节码文件。java源文件经编译成字节码程序,通过jvm将每条指令翻译成不同的机器码

,通过特定平台运行。

4. JVM执行程序的过程

1) 加载.class文件?

2) 管理并分配内存?

3) 执行垃圾收集

java摘要,第1张

JRE(java运行时环境)由JVM构造的java程序的运行环,也是Java程序运行的环境,但是他同时一个操作系统的一个应用程序一个进程,

因此他也有他自己的运行的生命周期,也有自己的代码和数据空间。

JVM在整个jdk中处于最底层,负责于操作系统的交互,用来屏蔽操作系统环境,

提供一个完整的Java运行环境,因此也就虚拟计算机。

操作系统装入JVM是通过jdk中Java.exe来完成,

通过下面4步来完成JVM环境:

1) 创建JVM装载环境和配置?

2) 装载JVM.dll?

3) 初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例

4) 调用JNIEnv实例装载并处理class类。

5. JVM的生命周期

1) JVM实例对应了一个独立运行的java程序它是进程级别?

a) 启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点?

b) 运行。main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以表明自己创建的线程是守护线程?

c) 消亡。当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出?

2) JVM执行引擎实例则对应了属于用户运行程序的线程它是线程级别的

java摘要,第2张

1.?堆(Heap)

? ? ? ?是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,该内存区域存放了对象实例及数组(但不是所有的对象实例都在堆中)。其大小通过-Xms(最小值)和-Xmx(最大值)参数设置(最大最小值都要小于1G),前者为启动时申请的最小内存,默认为操作系统物理内存的1/64,后者为JVM可申请的最大内存,默认为物理内存的1/4,默认当空余堆内存小于40%时,JVM会增大堆内存到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小堆内存的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,当然为了避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。堆内存 = 新生代+老生代+持久代。在我们垃圾回收的时候,我们往往将堆内存分成新生代和老生代(大小比例1:2),新生代中由Eden和Survivor0,Survivor1组成,三者的比例是8:1:1,新生代的回收机制采用复制算法,在Minor GC的时候,我们都留一个存活区用来存放存活的对象,真正进行的区域是Eden+其中一个存活区,当我们的对象时长超过一定年龄时(默认15,可以通过参数设置),将会把对象放入老生代,当然大的对象会直接进入老生代。老生代采用的回收算法是标记整理算法。

2. 方法区(Method Area)

? ? ?方法区也称"永久代",它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。默认最小值为16MB,最大值为64MB(64位JVM由于指针膨胀,默认是85M),可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。它是一片连续的堆空间,永久代的垃圾收集是和老年代(old generation)捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。不过,一个明显的问题是,当JVM加载的类信息容量超过了参数-XX:MaxPermSize设定的值时,应用将会报OOM的错误。参数是通过-XX:PermSize和-XX:MaxPermSize来设定的

? ? ?运行时常量池(Runtime Constant Pool):是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

? ? ?从JDK7开始移除永久代(但并没有移除,还是存在),贮存在永久代的一部分数据已经转移到了Java Heap或者是Native Heap:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。从JDK8开始使用元空间(Metaspace),元空间的大小受本地内存限制,新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小。如果没有指定这个参数,元空间会在运行时根据需要动态调整。

3.虚拟机栈(JVM Stack)

? ? ?描述的是java方法执行的内存模型:每个方法被执行的时候都会创建一个"栈帧",用于存储局部变量表(包括参数)、操作栈、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。声明周期与线程相同,是线程私有的。栈帧由三部分组成:局部变量区、操作数栈、帧数据区。局部变量区被组织为以一个字长为单位、从0开始计数的数组,和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组。但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的,可以看作为临时数据的存储区域。除了局部变量区和操作数栈外,java栈帧还需要一些数据来支持常量池解析、正常方法返回以及异常派发机制。这些数据都保存在java栈帧的帧数据区中。

? ? 局部变量表:?存放了编译器可知的各种基本数据类型、对象引用(引用指针,并非对象本身),其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部变量表的大小空间。

4.本地方法栈(Native Stack)

? ? ? ?与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。(栈的空间大小远远小于堆)

5.程序计数器(PC Register)

? ?是最小的一块内存区域,它的作用是当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。

6.直接内存

? ? 直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小.

17.jvm问题工具

jinfo/jmap/jhat/jstack/jstat

这 几个命令行工具可以很方便地查看当前虚拟机的参数信息、堆、内存图、线程堆栈和垃圾回收信息,它们非常常用,不需要预先使用参数增加虚拟机开启的端口。其 中,jhat命令尤其强大,它可以把堆中的对象导出成为html文件,比较两次虚拟机快照的不同,同时还支持对象查询语句来查询堆中对象的状态。

NIO包(java.nio.*)引入了四个关键的抽象数据类型,它们共同解决传统的I/O类中的一些问题。

1. Buffer:它是包含数据且用于读写的线形表结构。其中还提供了一个特殊类用于内存映射文件的I/O操作。

2. Charset:它提供Unicode字符串影射到字节序列以及逆影射的操作。

3. Channels:包含socket,file和pipe三种管道,它实际上是双向交流的通道。

4. Selector:它将多元异步I/O操作集中到一个或多个线程中

线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。

java摘要,第3张

挂起和睡眠是主动的,挂起恢复需要主动完成,睡眠恢复则是自动完成的,因为睡眠有一个睡眠时间,睡眠时间到则恢复到就绪态。而阻塞是被动的,是在等待某种事件或者资源的表现,一旦获得所需资源或者事件信息就自动回到就绪态。睡眠和挂起是两种行为,阻塞则是一种状态。


static的用法:

在实际工作中,使用static关键字定义属性或方法的原因有:

1.希望在没有实例化对象时可以轻松地执行类的某些操作;

2.现在希望表示出数据共享的概念。


https://www.xamrdz.com/mobile/4cd1993945.html

相关文章: