JAVA问题整理
1.什么是线程安全性?
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个类就是线程安全的。正确性:某个类的行为与其规范相一致。(我理解的规范就是我们在编写类时,能预知的状态结果)
竞态条件(Race Condition):某个计算的正确性取决于多个线程的交替执行的时序。(线程的时序不同,产生的结果可能会不同)
(360面试问题)。
“读取-修改-写入”,基于对象之前的状态来定义对象状态的转换。即使是volatile修饰的变量,在多线程的环境里面进行自增操作,同样会发生竞态条件,所以volatile不能保证绝对的线程安全
加锁机制
在线程安全的定义中,多个线程间的操作无论采用何种执行时序或交替方式,都要保证不变性条件不被破坏。当不变性条件中涉及多个变量时,各个变量之间并不是互相独立的,一个变量发生变化会对其他变量的值产生约束。因此,一个变量发生改变,在同一个原子操作里面,其他相关变量也要更新。
内置锁:
每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁
内置锁为互斥锁,即线程A获取到锁后,线程B阻塞直到线程A释放锁,线程B才能获取到同一个锁
内置锁使用synchronized关键字实现,synchronized关键字有两种用法:
1,修饰需要进行同步的方法(所有访问状态变量的方法都必须进行同步),此时充当锁的对象为调用同步方法的对象
2,同步代码块
和直接使用synchronized修饰需要同步的方法是一样的,但是锁的粒度可以更细,并且充当锁的对象不一定是this,也可以是其它对象,所以使用起来更加灵活
重入锁:
当一个持有锁的线程再次请求进入自己持有的锁时,该请求会成功。"重入"意味着获取锁的操作的粒度是“线程”,而不是“调用”。重入的一种实现方式,为每个锁关联一个计数器和线程持有者。
用锁来保护状态
由于锁能使其保护的代码路径以串行形式访问,因此可以通过锁来构造一些协议以实现对共享状态的独占访问。
对象的内置锁与其状态之间没有内在的联系,虽然大多数类都将内置锁用做一种有效的加锁机制,但对象的域并不一定要通过内置锁来保护。
对于每个包含多个变量的不变性条件,其中涉及的所有变量都要使用同一个锁来保护/同步。
equals:是否同一个对象实例。注意,是“实例”。比如String s = new String("test"); s.equals(s), 这就是同一个对象实例的比较;
等号(==):对比对象实例的内存地址(也即对象实例的ID),来判断是否是同一对象实例;又可以说是判断对象实例是否物理相等;
一、认识Thread的 start() 和 run()
1。start():
我们先来看看API中对于该方法的介绍:
使该线程开始执行;Java 虚拟机调用该线程的
run
方法。结果是两个线程并发地运行;当前线程(从调用返回给
start
方法)和另一个线程(执行其run
方法)。多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
2。run():
我们还是先看看API中对该方法的介绍:
如果该线程是使用独立的
Runnable
运行对象构造的,则调用该Runnable
对象的run
方法;否则,该方法不执行任何操作并返回。
Thread
的子类应该重写该方法。run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
3。总结:
调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。
三、线程状态说明:
线程状态从大的方面来说,可归结为:初始状态、可运行状态、不可运行状态和消亡状态,具体可细分为上图?所示7个状态,说明如下:
1)线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但不管怎样,当我们new了thread实例后,线程就进入了初始状态;
2)当该对象调用了start()方法,就进入可运行状态;
3)进入可运行状态后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;
4)进入运行状态后case就比较多,大致有如下情形:﹒run()方法或main()方法结束后,线程就进入终止状态;
当线程调用了自身的sleep()方法或其他线程的join()方法,就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源)。
当sleep()结束或join()结束后,该线程进入可运行状态,继续等待OS分配时间片;
当线程刚进入可运行状态(注意,还没运行),发现将要调用的资源被锁牢(synchroniza,lock),将会立即进入锁池状态,等待获取锁标记(这时的锁池里也许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入可运行状态,等待OS分配 CPU时间片;
当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。
当线程调用stop方法,即可使线程进入消亡状态,但是由于stop方法是不安全的,不鼓励使用,大家可以通过run方法里的条件变通实现线程的 stop。
HashTable的使用和原理
Java集合——HashMap、HashTable以及ConCurrentHashMap异同比较
HashTable和HashMap的区别详解
源码分析:
HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你!
2.Hash_table的底层是什么实现的?拉链法,数组+链表
拉链法解决冲突的做法是将所有通过哈希函数计算出来的哈希地址相同的结点存放在一个单链表之中。
(1)Hashtable 是一个散列表,它存储的内容是键值对(key-value)映射。
(2)Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。
(3)Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。
3.HashMap与Hashtable对比
1.HashTable是同步的,HashMap是非同步的
HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。
(Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。)
2)HashTable与HashMap实现的接口一致,但HashTable继承自Dictionary,而HashMap继承自AbstractMap;
3)HashTable不允许null值(key和value都不可以) ,HashMap允许null值(key和value都可以)。
4)HashTable有一个contains(Object value)功能和containsValue(Object value)功能一样。
contains方法其实做的事情就是containsValue, 里面将value值使用equals进行对比, 所以在HashTable中直接取消了contains方法而是使用containsValue代替.
这里我们可以直接对比HashMap和HashTable有关Contains的方法:
HashTable中的contains方法在HashMap中就被取消了,
5)HashTable使用Enumeration进行遍历,HashMap使用Iterator进行遍历。
HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
6)HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
7)哈希值的使用不同
HashTable:,HashTable直接使用对象的hashCode
HashMap:HashMap重新计算hash值,而且用与代替求模:
int hash = hash(k); int i = indexFor(hash, table.length); static int hash(Object x) { int h = x.hashCode(); h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); h ^= (h >>> 10); return h; } static int indexFor(int h, int length) { return h & (length-1); }
使用策略
由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。 HashMap不能保证随着时间的推移Map中的元素次序是不变的。
我们能否让HashMap同步?
HashMap可以通过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap);
快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
一:快速失败(fail—fast)
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除),则会抛出Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
二:安全失败(fail—safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
HashMap原理
HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用 hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。
HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象 的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的 键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。
当两个对象的hashcode相同会发生什么 碰撞探测(collision detection)以及碰撞的解决
因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。
如果两个键的hashcode相同,你如何获取值对象
找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。
为什么String, Interger这样的wrapper类适合作为键?
String, Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final 的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算 hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不 可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个field声明成final就能保证hashCode是不变的,那么请这么做吧。因为获取对象 的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的 hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。
我们可以使用自定义的对象作为键吗?
当然你可能使用任何对象作为键,只要它遵守了equals()和hashCode()方法的定义规则,并且当对象插入到Map中之 后将不会再改变了。如果这个自定义对象时不可变的,那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了。
如果要以可变对象作为key的话,那就必须要重写hashcode和equals方法来达到这个目的,除此之外,别无他法。
java 关于Map的key可不可以是自定义对象的学习
我们可以使用CocurrentHashMap来代替Hashtable吗?
我们知道Hashtable是synchronized的,但是 ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替 HashTable,但是HashTable提供更强的线程安全性。看看这篇博客查看Hashtable和ConcurrentHashMap的区别。
hashset和hashmap区别
为什么HashSet不能重复以及具体原理源码分析
为什么HashSet里面add的时候,不可以add相同的值,
为什么可以用HashSet的这个不重复的值的特性来去重复呢。
为什么要用HashSet去重复的时候,要重写Java model的hashcode和equal方法
先看如下HashSet的几个构造方法如下:
/** * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap<>(); } public HashSet(Collection<? extends E> c) { //这个就是在初始化hashmap的时候,给个合适大小的初始容量的算法;他不是直接给传入集合的size(),而是有点变化;至于具体原因,你得了解hashmap才行。 //原理同guava的Maps.newHashMapWithExpectedSize() map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); } public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } //这个是包内权限(知道不?不知道自己查下吧),不对外的一个构造方法 HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }
//类内部的map如下声明: private transient HashMap<E,Object> map; //Dummy value to associate with an Object in the backing Map//翻译:在支持映射中给一个对象关联的伪值 private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; }
再看上面的add方法,其实就是向这个map里面put();到这里,
由此可知HashSet的值是存储在一个HashMap的key里面的,而正好HashMap的key是不能重复的
如果你知道hashmap的原理,就好说,
如果不知道,那也好说,那你知道在往hashmap里面put的时候,如果put(key,value)的key相同的话,是不是就是把原来的旧值给覆盖啦
HashMap在add()时候,
若key没有存值,则存进去,返回oldValue;此值初始值是null;
若key已经有值了,那么add成功也返回oldValue,返回的是上次的值就不为null啦;也就解释了上面的add中 == null 为false啦。set的add失败啦。
再次总结一下:
HashSet的add()就是往一个HashMap里面put(),只是key一直不同,而value是一直相同的就是上面的那个伪值---PRESENT。
HashSet的值是存储在一个HashMap的key里面,而HashMap的key是不能重复的.
HashSet的其他方法如下:
public int size() { return map.size(); } public boolean isEmpty() { return map.isEmpty(); } public boolean contains(Object o) { return map.containsKey(o); } public boolean remove(Object o) { return map.remove(o)==PRESENT; } public void clear() { map.clear(); }
线程池
1、什么是线程池:
jdk1.5引入Executor线程池框架,通过它把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行。
线程池的作用:
线程池作用就是限制系统中执行线程的数量。
为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
线程池的工作过程如下:
- 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
- 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
- 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
- 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
- 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
- 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常RejectExecutionException。
- 当一个线程完成任务时,它会从队列中取下一个任务来执行。
- 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
Java线程池的工厂类:Executors类,
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
2.常见线程池
newFixedThreadPool()
说明:初始化一个指定线程数的线程池,其中corePoolSize == maxiPoolSize,使用LinkedBlockingQuene作为阻塞队列
特点:即使当线程池没有可执行任务时,也不会释放线程。
newCachedThreadPool()
说明:初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;
特点:在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源;当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;
因此,使用时要注意控制并发的任务数,防止因创建大量的线程导致而降低性能。
newSingleThreadExecutor()
说明:初始化只有一个线程的线程池,内部使用LinkedBlockingQueue作为阻塞队列。
特点:如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行
newScheduledThreadPool()
特定:初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据。
总结:除了newScheduledThreadPool的内部实现特殊一点之外,其它线程池内部都是基于ThreadPoolExecutor类(Executor的子类)实现的。
“线程池中的coreNum和maxNum有什么不同”
corePoolSize:
核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize:
线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:
表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
corePoolSize,maximumPoolSize,workQueue之间关系
1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
Java中线程池的实现原理-求职必备
深入分析java线程池的实现原理
java中方法传值小知识解析
1、java语言参数之间只有值传递,包括按值调用和按引用调用。 一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。
public class Example{ String str=new String("good"); char[]ch={'a','b','c'}; public static void main(String args[]){ Example ex=new Example(); ex.change(ex.str,ex.ch); System.out.print(ex.str+" and "); System.out.print(ex.ch); } public void change(String str,char ch[]){ //引用类型变量,传递的是地址,属于引用传递。 str="test ok"; ch[0]='g'; } }
堆和栈的概念和区别
堆与栈的区别:
1.栈内存存储的是局部变量而堆内存存储的是实体;
2.栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
3.栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。
GC详解及Minor GC和Full GC触发条件总结
面试题:“你能不能谈谈,java GC是在什么时候,对什么东西,做了什么事情?”
一.回答:什么时候?
3.能说出新生代、老年代结构,能提出minor gc/full gc
分析:到了这个层次,基本上能说对GC运作有概念上的了解,譬如看过《深入JVM虚拟机》之类的。这部分不足10%。
4.能说明minor gc/full gc的触发条件、OOM的触发条件,降低GC的调优的策略。
分析:列举一些我期望的回答:eden满了minor gc,升到老年代的对象大于老年代剩余空间full gc,或者小于时被HandlePromotionFailure参数强制full gc;gc与非gc时间耗时超过了GCTimeRatio的限制引发OOM,调优诸如通过NewRatio控制新生代老年代比例,通过MaxTenuringThreshold控制进入老年前生存次数等……能回答道这个阶段就会给我带来比较高的期望了,当然面试的时候正常人都不会记得每个参数的拼写,我自己写这段话的时候也是翻过手册的。回答道这部分的小于2%。
程序员不能具体控制时间,系统在不可预测的时间调用System.gc()函数的时候;当然可以通过调优,用NewRatio控制newObject和oldObject的比例,用MaxTenuringThreshold 控制进入oldObject的次数,使得oldObject 存储空间延迟达到full gc,从而使得计时器引发gc时间延迟OOM的时间延迟,以延长对象生存期。
二.回答:对什么东西?
超出了作用域或引用计数为空的对象;从gc root开始搜索找不到的对象,而且经过一次标记、清理,仍然没有复活的对象。
三.回答:做什么?
1.删除不使用的对象,腾出内存空间。
分析:同问题2第一点。40%。
2.补充一些诸如停止其他线程执行、运行finalize等的说明。
分析:起码把问题具体化了一些,如果像答案1那样我很难在回答中找到话题继续展开,大约占40%的人。
3.能说出诸如新生代做的是复制清理、from survivor、to survivor是干啥用的、老年代做的是标记清理、标记清理后碎片要不要整理、复制清理和标记清理有有什么优劣势等。
分析:也是看过《深入JVM虚拟机》的基本都能回答道这个程度,其实到这个程度我已经比较期待了。同样小于10%。
4.除了3外,还能讲清楚串行、并行(整理/不整理碎片)、CMS等搜集器可作用的年代、特点、优劣势,并且能说明控制/调整收集器选择的方式。
java中static的使用
如果一个类成员被声明为static,它就能够在类的任何对象创建之前被访问,而不必引用任何对象。static 成员的最常见的例子是main( ) 。因为在程序开始执行时必须调用main() ,所以它被声明为static。
声明为static的变量实质上就是全局变量。当声明一个对象时,并不产生static变量的拷贝,而是该类所有的实例变量共用同一个static变量,例如:声明一个static的变量count作为new一个类实例的计数。声明为static的方法有以下几条限制:
1、它们仅能调用其他的static 方法。
2、它们只能访问static数据。
3、它们不能以任何方式引用this 或super。
如果你需要通过计算来初始化你的static变量,你可以声明一个static块,Static 块仅在该类被加载时执行一次。下面的例子显示的类有一个static方法,一些static变量,以及一个static 初始化块:
public class UserStatic { static int a = 3; static int b; static void meth(int x) { System.out.println("x = " + x); System.out.println("a = " + a); System.out.println("b = " + b); } static { System.out.println("Static block initialized."); b = a * 4; } public static void main(String args[]) { meth(42); } }
一旦UseStatic 类被装载,所有的static语句被运行。首先,a被设置为3,接着static 块执行(打印一条消息),最后,b被初始化为a*4 或12。然后调用main(),main() 调用meth() ,把值42传递给x。3个println ( ) 语句引用两个static变量a和b,以及局部变量x 。
注意:在一个static 方法中引用任何实例变量都是非法的。
下面是该程序的输出:
Static block initialized.
x = 42
a = 3
b = 12
在定义它们的类的外面,static 方法和变量能独立于任何对象而被使用。这样,你只要在类的名字后面加点号(.)运算符即可。例如,如果你希望从类外面调用一个static方法,你可以使用下面通用的格式:
classname.method( )
这里,classname 是类的名字,在该类中定义static方法。可以看到,这种格式与通过对象引用变量调用非static方法的格式类似。一个static变量可以以同样的格式来访问——类名加点号运算符。这就是Java 如何实现全局功能和全局变量的一个控制版本。
总结:
1、static成员是不能被其所在class创建的实例访问的。
2、如果不加static修饰的成员是对象成员,也就是归每个对象所有的。
3、加static修饰的成员是类成员,就是可以由一个类直接调用,为所有对象共有的。