随心所遇,随遇而安。
Java数据类型
包装类型
为了让基本类型也具有对象的特征,就出现了包装类型(比如在使用集合类型Collection时就一定要使用包装类型而非基本类型)
因为容器都是装object的,这时就需要这些基本类型的包装类了。
区别:
1. 声明方式不同:包装类型需要使用new关键字来在堆空间中分配存储空间;
2. 存储方式以及位置不同:基本类型是直接将变量存储在栈中,而包装类是将对象放在堆中,通过引用来使用;
3. 默认值不同,基本类型都有默认值,int是0 、boolean是false;包装类型的默认值是null;
4. 使用方式不同:基本类型直接赋值使用;包装类型可当做对象来使用;
Integer128==128?false
Integer类内部 通过静态内部类提供了一个缓存池,范围在-128~127之间,
如果超过这个范围 Integer 值都是new出来的对象。
short s1=1
s1=s1+1 × 由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再
赋值给short类型s1时,编译器将报告需要强制转换类型的错误。
s1+=1 √ 由于 +=是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。
a++和++a
&和&&的区别
&和&& 都可以用作逻辑“与”的运算符,当运算符两遍表达式的结果都没true时,整个运算结果才为true,否则为false;
&&还具有短路的功能,对于if( d!=null && d.equals("11") ),当d为null时,后面的表达式不会执行,整个运算返回false;
将&&改为& ,则会执行后面表达式,抛出NullPointerException异常;
&还可以用作位运算符(“与”运算)↓↓
位运算符 & 、| 和 ^
按位与运算符 & : 按二进制位进行“与”运算 。
规则:两位同时为“1”,结果才为“1”,否则为0;比如5 & 7=5
按为或运算符 | : 按二进制位进行“或”运算 。
规则:两位只要有一个为“1”,其值为“1”,否则为“0”
3 | 5 即 011 | 101 = 111 3 | 5=7
按为异或运算符 ^ : 按二进制位进行“异或”运算。
规则:两位不同时,为“1”,否则为 “0”
5 ^ 7 即 101 | 111 = 010 5 ^ 7=2
switch选择结构可使用的数据类型的数据
基本类型中,没有boolean、
浮点类型(double和float)和
长类型long,
.相应的包装类型也没有。
外加String和enum。
try{}里有return时,与finally执行顺序
==和equals
1)对于 ==
如果作用于基本数据类型得变量,比较的是其存储的“值“是否相等;
如果作用于包装类型的变量,比较的是所指对象的内存空间地址;
2)对于 equals
equals方法不能作用于基本数据类型的变量,equals继承Object类,比较的是是否是同一个对象;
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
String重写的equals方法:
String s = "hello"和String s = new String("hello"); 的区别
在JAVA虚拟机(JVM)中存在着一个字符串池(常量池),其中保存着很多String对象,并且可以被共享使用,因此它提高了效率。
String s = new String("hello"); 可能创建两个对象也可能创建一个对象。如果常量池中有hello
字符串常量的话,则仅
仅在堆中创建一个对象。如果常量池中没有hello
对象,则堆上和常量池都需要创建。
String s = "hello" 这样创建的对象,JVM会直接检查字符串常量池是否已有"hello"字符串对象,如没有,就
分配一个内存存放"hello",如有了,则直接将字符串常量池中的地址返回给栈。
(没有new,没有堆的操作)
String s="a"+"b"+"c"+"d"
所以,String s="a"+"b"+"c"+"d"创建一个对象
String、StringBuffer和StringBuilder区别
1)可变与不可变
String底层使用一个不可变的字符串数组 所以内容不可变;
StringBuffer和StringBuilder都继承了AbstractStringBuilder底层的可变字符串数组 所以内容可变;
首先栈中的"str1"变量指向堆中的“123”对象,栈中的"str2"变量指向堆中的“456”对象,当执行到str1=str1+str2时,系统重新
在堆中new一个更大的数组出来,然后将"123"和"456"都复制进去,然后栈中的"str1"指向这个新new出来的数组;
所谓的不可变是指:它没有在原数组“123”上进行修改,而是新建了个更大数组进行扩展,也就是说,这时候堆里还是有“123”
这个对象数组存在的,只不过这个时候"str1"变量不在指向"123"这个数组了,而是指向了新new出来的数组,这就是和StringBuffered
的区别,后者是在原数组上进行修改,改变了原数组的值,StringBuffered不是通过新new一个数组去复制,而是在原数组基础上进行扩展。
2)线程安全
String线程安全;
StringBuffer线程安全,效率较低,StringBuffer的大部分方法用synchronized修饰,具有同步锁,多个线程只能互斥地调用这些方法。
StringBuilder线程不安全,效率较高;
集合
①List、Set
Queue接口与List、Set同一级别,都继承了Collection接口
LinkedList既可以实现Queue接口,也可以实现List接口;
LinkedList实现了Queue接口,Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果
是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法)。
----List 有序,可重复(这里的有序指的是按照放入的顺序进行存储。如按照顺序把1,2,3存入List,
那么,从List中遍历出来的顺序也是1,2,3)
· ArrayList 点击详解(源自公众号:Java知音) -----查询快,增删慢,效率高,线程不安全。
· LinkedList 点击详解(源自公众号:Java知音) -----查询慢,增删快,效率高,线程不安全。
· Vector ----- 底层结构是数组,查询快,增删慢,线程安全(synchronized),效率低。
对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
对于新增和删除操作add和remove,LinkedList优于ArrayList,因为ArrayList要移动数据。
----Set 唯一,不一定无序
· HashSet(无序,唯一,可为null) 底层是采用HashMap实现的(HashMap是Java中用哈希表实现的一种Map),也就是说
HashSet 底层数据结构其实是哈希表。
保证唯一性:hashCode()和equals()
· LinkedHashSet (存储有序,唯一,可为null) 底层数据结构是链表和哈希表
链表保证元素有序;哈希表保证元素唯一
· TreeSet (有序,唯一,不可为) 底层数据结构是采用TreeMap(红黑树)实现的
自然排序、比较器排序(点击查看:TreeSet的两种排序比较)
List<String> arrayList = new ArrayList<String>(); arrayList.add("1,"); arrayList.add("2,"); arrayList.add("3,"); arrayList.add("4,"); arrayList.add("5"); System.out.println("ArrayList:=========================="); for(String arrList : arrayList){ System.out.print(arrList); } System.out.println(""); List<String> linkedList = new LinkedList<String>(); linkedList.add("1,"); linkedList.add("2,"); linkedList.add("3,"); linkedList.add("4,"); linkedList.add("5"); System.out.println("LinkedList:==========================="); for(String linkList : linkedList){ System.out.print(linkList); } System.out.println(""); Set<String> hashSet = new HashSet<String>(); hashSet.add("1,"); hashSet.add("2,"); hashSet.add("3,"); hashSet.add("4,"); hashSet.add("5"); System.out.println("HashSet:=============================="); for(String hashst: hashSet){ System.out.print(hashst); } System.out.println(""); Set<String> linkedHashSet = new LinkedHashSet<String>(); linkedHashSet.add("1,"); linkedHashSet.add("2,"); linkedHashSet.add("3,"); linkedHashSet.add("4,"); linkedHashSet.add("5"); System.out.println("LinkedHashSet:========================="); for(String linkedst : linkedHashSet){ System.out.print(linkedst); } System.out.println(""); Set<String> treeSet = new TreeSet<String>(); treeSet.add("1,"); treeSet.add("2,"); treeSet.add("3,"); treeSet.add("4,"); treeSet.add("5"); System.out.println("TreeSet:=============================="); for(String treest : treeSet){ System.out.print(treest); }
②Map
HashMap,无序,线程不安全(方法不同步),key和value都允许null值;
实现原理:HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体;
利用key通过hashCode()计算hash值(存储位置),得到当前对象的元素在数组中的下标
存储时(put),利用key通过equals() 比较key值,此时有两种情况。
(1)如果key相同,则覆盖原始值;
(2)如果key不同(出现冲突),则将当前的key-value放入链表中。
获取时(get),利用key通过hashCode()计算hash值,得到存储位置;再通过equals() 判断
key值是否相同,从而找到对应值。
并发下的线程安全性:如果多个线程,在某时刻同时操作HashMap并执行put操作,而有大于两个key的hash值相同,
如下图中A1、A2,这个时候需要解决碰撞冲突,对于链表的结构暂且不讨论是从链表头部插入还
是从尾部插入,这个时候两个线程如果恰好都取到了对应位置的头结点L1,再加上put方法是非同
步的,最终的结果可想而知,A1、A2两个数据中势必会有一个会丢失。
HashTable,无序,线程安全(方法同步synchronized),key和value都不允许null值,可以为“ ”;
TreeMap,有序,线程不安全(方法不同步),key值不允许null值,value可以为null;
有序无序性测试:
HashMap tHashMap=new HashMap(); tHashMap.put("1","111"); tHashMap.put("2","222"); tHashMap.put("3","333"); tHashMap.put("4","444"); tHashMap.put("5","555"); Iterator<Map.Entry<String, String>> iterator = tHashMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, String> entry = iterator.next(); logger.info("HashMap,Key值为:"+entry.getKey()); logger.info("HashMap,Value值为:"+entry.getValue()); } Hashtable tHashtable=new Hashtable(); tHashtable.put("A","aaa"); tHashtable.put("B","bbb"); tHashtable.put("C","ccc"); tHashtable.put("D","ddd"); tHashtable.put("E","eee"); for(Iterator<String> iterator2=tHashtable.keySet().iterator();iterator2.hasNext();) { String key=iterator2.next(); logger.info("Hashtable,Key值为:"+key); logger.info("Hashtable,Value值为:"+tHashtable.get(key)); } TreeMap tTreeMap=new TreeMap(); tTreeMap.put("①","①①①"); tTreeMap.put("②","②②②"); tTreeMap.put("③","③③③"); tTreeMap.put("④","④④④"); tTreeMap.put("⑤","⑤⑤⑤"); for(Iterator<String> iterator3=tTreeMap.keySet().iterator();iterator3.hasNext();) { String key=iterator3.next(); logger.info("TreeMap,Key值为:"+key); logger.info("TreeMap,Value值为:"+tTreeMap.get(key)); }
去掉一个List集合中重复的元素
ArrayList、LinkedList同理
方法一:
方法二:
递归
① 3个可乐盖可兑换1瓶可乐,n瓶可乐可换多少瓶子?
n<3时,无法兑换,递归的终止条件
n>=3时,可以兑换,如何实现递归逻辑。
可以理解为,每次进递归方法(调自己)时,传入参数为当前的瓶盖数,包括兑换出的瓶盖数 + 数量小于3的未兑换的瓶盖数
比如,设共有8瓶可乐,6瓶可兑换出的瓶盖数(2) + 未兑换的瓶盖数(2)= 4 瓶,这4瓶再去兑换;
4瓶可乐,3瓶可兑换出的瓶盖数(1) + 未兑换瓶盖数(1)= 2 整瓶,小于3,无法兑换。
但是,最终统计的是瓶子数,是6+3+2=11;
②计算1+2+3+4+····+n的和;
public static int getSum(int n){ if(n==1){ return n; }else{ return n+getSum(n-1); } }
③计算n的阶乘;
public static int getSum(int n){ if(n==1){ return 1; }else{ return n*getSum(n-1); } }
④一列数的规则如下: 1、1、2、3、5、8、13、21、34...... 求第n位数是多少?
public static int getNum(int n){ if ( n <= 2) { return 1; }else{ return getNum(n-1)+getNum(n-2); } }
⑤3个可乐瓶盖可换1瓶可乐、2个瓶身可换1瓶可乐,购买n瓶可乐,最终总共获得几瓶?
思路与①类似,但是需要考虑瓶盖、瓶身换可乐时互不干扰,但换完后都得到新的瓶盖瓶身。
未完待续.....
线程、线程池及线程的创建方式
①线程和进程
线程是程序执行流的最小单位,是进程中的实际运作单位。
进程是一个动态的过程,是一个活动的实体。
简单来说,一个应用程序的运行就可以被看做是一个进程,而线程,是运行中的实际的任务执行者。可以说,进程中包含了多个可以同时运行的线程。
②线程的生命周期
新建: 当程序使用new关键字创建了一个线程之后,该线程就处于新建状态 ,和其他java对象一样,仅仅
由虚拟机为其分配内存,并初始化其成员变量的值,此时的线程对象没有表现出任何线程的动态特征,程
序也不会执行线程的线程执行体。
就绪: 当线程对象调用了start()方法之后,该线程就处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,
处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM
里线程调度器的调度。(注意start()与run()的区别)
运行: 如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。在线程运行
过程中可调用yield()和sleep()可释放cpu的占用。
yield():让当前运行线程回到就绪状态,以允许具有相同优先级的其他线程获得运行机会。
sleep():暂停执行指定的时间,让出cpu给其他线程,但该线程的监控状态依然保持,此时该线程处于阻塞状态,
当指定的时间到了又会自动恢复运行状态。sleep()方法不会释放线程的对象锁(经常与wait()比较,当
调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()
或notifyAll()方法后本线程才进入对象锁定池准备)
阻塞:当发生以下情况时,线程会进入阻塞状态:
1> 调用sleep()主动放弃cpu资源 --------->解除阻塞进入就绪状态:sleep()经过指定时间
2> 调用了一个阻塞式IO方法 --------->解除阻塞进入就绪状态:IO方法已返回
3> 线程视图获取正在被其他线程使用的同步监视器 --------->解除阻塞进入就绪状态:获取监视器
4> wait() --------->解除阻塞进入就绪状态:notify()或notifyAll()唤醒该线程
5> 调用了suspend()方法将该线程挂起,容易导致死锁
(suspend不会释放对象锁,如果加锁放生在resume()之前,则会发生死锁)
--------->解除阻塞进入就绪状态:调甩了resume()恢复挂起线程
死亡:当发生以下情况时,线程进入死亡状态:
1> run()或call()方法执行完成,线程正常结束
2> 线程抛出一个未捕获的Exception或Error
3> 调用stop()方法(会释放对象锁,不会释放占用资源)结束该线程,容易导致死锁
③线程池
多次使用线程,需要多次创建销毁线程,这样势必会造成内存的大量消耗。线程池的好处就是方便管理线程,减少内存的消耗。
④线程的创建方式
static、final和abstract区别
①final可修饰类、方法和变量
修饰类:不能被继承
修饰方法:子类中不能被重写
修饰变量:分配到常量池中,程序不可改变其值
使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
使用final关键字修饰一个变量时,是指引用变量不能变;
引用变量所指向的对象中的内容还是可以改变的。例如:
②static可修饰内部类、方法块和变量
修饰内部类:可以直接通过类来调用而不需要new
public class Penguin { public static void out1() { System.out.println("我是外部类的静态方法"); } public void out2() { System.out.println("我是外部类的非静态方法"); } //*静态内部类可以用public,protected,private修饰 public static class InnerClass{ //*静态内部类中可以定义静态或者非静态的成员 static int a = 100; int b= 200; // 静态内部类只能访问外部类的静态成员(包括静态变量和静态方法) //静态内部类不能访问外部类的非静态成员(包括非静态变量和非静态方法) static void eat(){ System.out.println("我是内部类的静态方法"); out1(); //out2(); } void sleep(){ System.out.println("我是内部类的非静态方法"); out1(); //out2(); } } public void out3() { // 外部类访问内部类的静态成员:内部类.静态成员(也可实例化) System.out.println(InnerClass.a); InnerClass.eat(); // 外部类访问内部类的非静态成员:实例化内部类即可 InnerClass inner = new InnerClass(); System.out.println(inner.a); System.out.println(inner.b); inner.sleep(); } public static void main(String[] args) { new Penguin().out3(); } }
修饰方法块:static修饰初始代码块,这时这个初始代码块就叫做静态初始代码块,
该代码块只在类加载时被执行一次
修饰变量:分配在内存堆上,引用都会指向这一个地址而不会重新分配内存
静态变量和实例变量的区别?
实例变量:创建实例对象,才会分配空间,实例变量才可以使用;
静态变量:程序加载类的字节码,不用创建实例对象,静态变量就会被分配空间,直接使用类名来引用。
对于下面的程序,无论创建多少个实例对象,永远都只分配了一个staticVar变量,并且每创建一个实例对象,
这个staticVar就会加1;但是,每创建一个实例对象,就会分配一个instanceVar,即可能分配多个instanceVar,并且
每个instanceVar的值都只自加了1次;
③abstract可修饰类和方法
修饰类:abstract修饰类,使这个类成为一个抽象类,类将不能生成对象实例;
有抽象方法的类一定是抽象类,但是抽象类中不一定都是抽象方法,也可以全是具体方法;
abstract修饰符在修饰类时必须放在类名前;
abstract修饰方法就是要求其子类覆盖(实现)这个方法。调用时可以以多态方式调用子类覆
盖(实现)后的方法,即抽象方法必须在其子类中实现,除非子类本身也是抽象类。
不能放在一起的修饰符:final和abstract,private和abstract,static和abstract
修饰方法:abstract修饰方法,使这个方法变成抽象方法,即只有声明(定义)而没有实现,需要子类继承实现(覆盖)。
abstract类和interface语法上有什么区别?
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4. 抽象类中可以包含静态方法,接口中不能包含静态方法
5. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中
定义的变量只能是public static final类型,并且默认即为public static final类型。
6. 一个类可以实现多个接口,但只能继承一个抽象类。
JVM垃圾处理方法
1. Java的垃圾回收机制
GC通过确定对象是否被活动对象引用来确定是否收集该对象,JDK1.2之前使用的是引用计数法,在
对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就+1,当引用失效的时候,
计数器的值就-1,当引用计数器被减为零的时候,标志着这个对象已经没有引用了,可以回收了!(缺点:
A、B对象方法互调,A和B的其他引用都消失,此时两个对象已经是垃圾了,但A和B对象的引用计数器各
为1,JVM并不会回收)。
通过可达性分析算法来判定对象是否存活的: 通过一系列的称为 GC Roots 的对象作为起点, 然后向下
搜索; 搜索所走过的路径称为引用链/Reference Chain, 当一个对象到 GC Roots 没有任何引用链相连时,
即该对象不可达, 也就说明此对象是不可用的, 如下图:虽然E和F相互关联, 但它们到GC Roots是不可达的,
因此也会被判定为可回收的对象。
1.1 触发GC的条件:
1> GC在优先级最低的线程中运行,一般在应用程序空闲即没有应用线程在运行时被调用 ;
2> Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若
这时内存空间不足,JVM就会强制调用GC线程。若GC一次之后仍不能满足内存分配,JVM
会再进行两次GC,若仍无法满足要求,则JVM将报“out of memory”的错误,Java应用将停止。
1.2 System.gc()方法:提醒JVM进行垃圾回收。
1.3 finalize()方法:在JVM垃圾回收器收集一个对象之前,一般要求程序调用适当的方法释放资源,但在
没有明确释放资源的情况下,Java提供了缺省机制来终止该对象心释放资源,这个方
法就是finalize()。在finalize()方法返回之后,对象消失,垃圾收集开始执行。
1.4 减少GC开销的措施
1> 不要显式调用System.gc()。很多情况下它会触发主GC,从而增加主GC的频率,增加了间歇性停顿的次数,大大的影响系统性能。
2> 减少临时对象的使用。减少临时变量产生的垃圾,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。
3> 不用的对象置为Null。一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定
垃圾,从而提高了GC的效率。
4> 尽量使用StringBuffer,而不用String来累加字符串。(String、StringBuffer和StringBuilder区别)
5> 能用基本类型如Int,Long,就不用Integer,Long对象。占用内存资源的多少。
6> 尽量少用静态对象变量。静态变量属于全局变量,不会被GC回收,它们会一直占用内存。
7> 分散对象创建或删除的时间。集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,
只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾
对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。
2. 对象在JVM堆区的状态
1> 可触及状态:程序中还有变量引用,那么此对象为可触及状态。
2> 可复活状态:当程序中已经没有变量引用这个对象,那么此对象由可触及状态转为可复活状态。
GC线程将在一定的时间准备调用此对象的finalize方法,finalize方法有可能将对象
转为可触及状态,否则对象转化为不可触及状态。
3> 不可触及状态:只有当对象处于不可触及状态时,GC线程才能回收此对象的内存。
3.新生代、老年代、持久代
新生代
方法中new一个对象,就会先进入新生代。
老年代
①新生代中经历了N次垃圾回收仍然存活的对象就会被放到老年代中。
②大对象一般直接放入老年代。
③当Survivor空间不足。需要老年代担保一些空间,也会将对象放入老年代。
永久代
指的就是方法区。
4. 常用垃圾收集器
1> 标记-清除收集器 (Mark-Sweep) (老年代)
基于tracing算法,该算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象, 在标记完成后统一清理掉所有被标记的对象。
缺点:①标记和清除效率不高;
②标记清除后会产生大量不连续的内存碎片, 空间碎片太多可能会导致在运行过程中需要分配较大对象时无法找到足够的连续内存
而不得不提前触发另一次垃圾收集 ;
2> 复制收集器 (Copying) ( 新生代 )
基于Copying算法,该算法的核心是将可用内存按容量划分为大小相等的两块, 每次只用其中一块, 当这一块的内存用完, 就将还存活的
对象(非垃圾)复制到另外一块上面, 然后把已使用过的内存空间一次清理掉。
优点:不用考虑碎片问题,方法简单高效。
缺点:内存浪费严重
3> 标记-压缩收集器 (Mark-Compact) ( 老年代 )
标记整理算法的标记过程与标记清除算法相同(tracing算法), 但后续步骤不再对可回收对象直接清理, 而是让所有存活的对象都向一端
移动,然后清理掉端边界以外的内存。
4> 分代收集器(Generational) (老年代 和 新生代)
分代收集算法是目前大部分JVM的垃圾收集器采用的算法;它的核心思想是根据对象存活的生命周期将内存划分为老年代和新生代。
老年代的特点是每次垃圾收集时只有少量对象需要被回收;而新生代的特点是每次垃圾回收时都有大量的对象需要被回收。
新生代一般都采取Copying算法,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大
的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中
还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。当Survivor空间不够用(不足以保
存尚存活的对象)时,需要依赖老年代进行空间分配担保机制,这部分内存直接进入老年代
老年代一般使用的是Mark-Compact算法。
5.MinGC和FullGC
MinGC:当Eden区满时,触发Minor GC.
FullGC
调用System.gc时,系统建议执行Full GC,但是不必然执行
老年代空间不足
方法区空间不足
通过Minor GC后进入老年代的平均大小大于老年代的剩余空间
堆中分配很大的对象,而老年代没有足够的空间
Servlet的运行原理和生命周期
Servlet的运行原理
1. init()
在Servlet的生命周期中,仅执行一次init()方法,它是在服务器装入Servlet时执行的,可以
配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。
无论有多少客户机访问Servlet,都不会重复执行init();
2. service()
它是Servlet的核心,每当一个客户请求一个HttpServlet对象,该对象的Service()方法就
要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应”(ServletResponse)
对象作为参数。在HttpServlet中已存在Service()方法。默认的服务功能是调用与HTTP请求的方法相应
的do功能(doPost、doGet)。
3. destroy()
仅执行一次,在服务器端停止且卸载Servlet(将实例销毁)时执行该方法。一个Servlet在运行service()
方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成。
Servlet的生命周期
Servlet被服务器实例化后,容器运行其init方法,请求到达时运行其service方法,service方
法自动派遣运行与请求对应的doXXX方法(doGet,doPost)等,当服务器决定将实例销毁的时
候调用其destroy方法。
在代码中,Servlet生命周期由接口javax.servlet.Servlet定义。所有的JavaServlet 必须直接或间
接地实现javax.servlet.Servlet接口,这样才能在Servlet Engine上运行。javax.servlet.Servlet接口
定义了一些方法,在Servlet 的生命周期中,这些方法会在特定时间按照一定的顺序被调用。
Servlet 中 forward()与redirect()的区别
1. 从地址显示栏来说
forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把
这些内容再发给浏览器,浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址。
2. 从数据共享来说
forward:转发页面和转发到的页面可以共享request里面的数据;
redirect:不能共享数据。
redirect不仅可以重定向到当前应用程序的其他资源,还可以重定向到同一个站点上的其他应用程序中的资源,
甚至是使用绝对URL重定向到其他站点的资源。
forward方法只能在同一个Web应用程序内的资源之间转发请求。forward 是服务器内部的一种操作,
redirect 是服务器通知客户端,让客户端重新发起请求.
所以,可以说 redirect 是一种间接的请求, 但是不能说"一个请求是属于forward还是redirect " 。
3. 从运用地方来说
forward:一般用于用户登陆的时候,根据角色转发到相应的模块。
redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等。
4. 从效率来说
forward高,redirect低
request.getAttribute()和 request.getParameter()
1. getParameter()方法
该方法是用于客户端通过get或者post传递过来的参数,它的返回值类型永远是是字符串类型。
2. getAttribute()方法
该方法用于获取request对象中的attribute值,这个值是之前在服务器端才放入到request对象里的,
即通过setAttribute(key ,value)放入request。
![](https://img2018.cnblogs.com/blog/1495080/201906/1495080-20190610165053468-2127610758.jpg)
-
-
- cookie数据存放在客户的浏览器上,session数据放在服务器上
- cookie不是很安全
- session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用COOKIE
- 单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能3K。
-
jsp和servlet的区别、共同点、各自应用的范围?
JSP是Servlet技术的扩展,本质上就是Servlet的简易方式。JSP编译后是“类servlet”。
Servlet和JSP最主要的不同点在于:
① Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。
而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。
② JSP侧重于视图,Servlet主要用于控制逻辑。
tomcat容器是如何创建servlet类实例?
当容器启动时,会读取在webapps目录下所有的web应用中的web.xml文件,然后对xml文件进行解析,并读取
servlet注册信息。然后将每个应用中注册的servlet类都进行加载,并通过反射的方式实例化。
在servlet注册时加上<load-on-startup>1</load-on-startup>如果为正数,则在一开始就实例化,如果不写或为负
数,则第一次请求实例化。
JDBC访问数据库的基本步骤是什么?
1. 加载数据库驱动(注册数据库驱动到JVM)
Class.forName(“com.mysql.jdbc.Driver”);
2. 通过DriverManager对象获取连接对象Connection
String tConnection = DriverManager.getConnection(url, username, password);
3. 获取数据库操作对象
Statement stmt = tConnection .createStatement();
4. 通过会话进行数据的增删改查,封装对象
ResultSet rs = stmt.executeQuery(sql);
5. 关闭资源
rs.close();
stmt.close();
tConnection.close();
preparedStatement和Statement
Statement执行不带参数的简单SQL语句,并返回它所生成结果的对象,每次执行SQL语句时,数据库都要编译该sql语句;
PreparedStatement用来执行带参数的预编译的SQL语句。
效率:使用PreparedStatement执行SQL命令时,命令会被数据库编译和解析,并放到命令缓冲区,以后每当执行同一个
PreparedStatement对象时,预编译的命令就可以重复使用。
安全性:PreparedStatement可防止SQL注入。
比如:“select * from t_user where userName = ‘” + userName + “ ’ and password =’” + password + “’”
如果用户名和密码输入的是’1’ or ‘1’=’1’ ; 则生产的sql语句是:
“select * from t_user where userName = ‘1’ or ‘1’ =’1’ and password =’1’ or ‘1’=’1’
这个语句中的where 部分没有起到对数据筛选的作用。