part 1JAVA程序员一面: 1.volatile有什么用? 把代码块声明为 synchronized,有两个重要后果,通常是指该代码具有 原子性(atomicity)和 可见性(visibility)。 原子性意味着个时刻,只有一个线程能够执行一段代码,这段代码通过一个monitor object保护。从而防止多个线程在更新共享状态时相互冲突。 可见性则更为微妙,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的。 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。 volatile的使用条件 Volatile 变量具有 synchronized 的可见性特性,但是不具备原子性。这就是说线程能够自动发现 volatile 变量的最新值。 Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。 因此,单独使用 volatile 还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式(Invariants)的类(例如 “start <=end”)。 模式 #1:状态标志 也许实现 volatile 变量的规范使用仅仅是使用一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。 [java] volatile boolean shutdownRequested; ... public void shutdown() { shutdownRequested = true; } public void doWork() { while (!shutdownRequested) { // do stuff } } 2.Minor GC和Full GC的触发时机 GC,即就是Java垃圾回收机制。目前主流的JVM(HotSpot)采用的是分代收集算法。 与C++不同的是,Java采用的是类似于树形结构的可达性分析法来判断对象是否还存在引用。 即:从gcroot开始,把所有可以搜索得到的对象标记为存活对象。 GC机制 要准确理解Java的垃圾回收机制,就要从:“什么时候”,“对什么东西”,“做了什么”三个方面来具体分析。 第一:“什么时候”即就是GC触发的条件。GC触发的条件有两种。(1)程序调用System.gc时可以触发;(2)系统自身来决定GC触发的时机。 系统判断GC触发的依据:根据Eden区和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程。 第二:“对什么东西”笼统的认为是Java对象并没有错。但是准确来讲,GC操作的对象分为:通过可达性分析法无法搜索到的对象和可以搜索到的对象。对于搜索不到的方法进行标记。 第三:“做了什么”最浅显的理解为释放对象。但是从GC的底层机制可以看出,对于可以搜索到的对象进行复制操作,对于搜索不到的对象,调用finalize()方法进行释放。 具体过程:当GC线程启动时,会通过可达性分析法把Eden区和From Space区的存活对象复制到To Space区,然后把Eden Space和From Space区的对象释放掉。当GC轮训扫描To Space区一定次数后,把依然存活的对象复制到老年代,然后释放To Space区的对象。 对于用可达性分析法搜索不到的对象,GC并不一定会回收该对象。要完全回收一个对象,至少需要经过两次标记的过程。 第一次标记:对于一个没有其他引用的对象,筛选该对象是否有必要执行finalize()方法,如果没有执行必要,则意味可直接回收。(筛选依据:是否复写或执行过finalize()方法;因为finalize方法只能被执行一次)。 第二次标记:如果被筛选判定位有必要执行,则会放入FQueue队列,并自动创建一个低优先级的finalize线程来执行释放操作。如果在一个对象释放前被其他对象引用,则该对象会被移除FQueue队列。 GC过程中用到的回收算法: 通过上面的GC过程不难看出,Java堆中的年轻代和老年代采用了不同的回收算法。年轻代采用了复制法;而老年代采用了标记-整理法 Minor GC ,Full GC 触发条件 Minor GC触发条件:当Eden区满时,触发Minor GC。 年轻代空间(包括 Eden 和 Survivor 区域) Full GC触发条件: (1)调用System.gc时,系统建议执行Full GC,但是不必然执行 (2)老年代空间不足 (3)方法去空间不足 (4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存 (5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小 3.反射用到了哪些接口,哪些类? Proxy类与InvocationHandler接口 例: Class<?> mClass = Class.forName("com.lastwarmth.library.MyLibrary"); Class<?> mCallback = Class.forName("com.lastwarmth.library.MyLibrary$Callback"); MyHandler mHandler = new MyHandler(); Object mObj = Proxy.newProxyInstance(MyTest.class.getClassLoader(), new Class[] { mCallback }, mHandler); Method mMethod = mClass.getDeclaredMethod("mainMethod", new Class[] { mCallback }); mMethod.invoke(mClass.newInstance(), new Object[] { mObj }); 4.反射机制中可以获取private成员的值吗? 可以:使用setAccessible PrivateClass2 p = new PrivateClass2(); Class<?> classType = p.getClass(); Field field = classType.getDeclaredField("name"); field.setAccessible(true); // 抑制Java对修饰符的检查 field.set(p, "lisi"); 5.Java中sleep方法和wait方法的区别 5.1这两个方法来自不同的类分别是Thread和Object 5.2最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法(锁代码块和方法锁)。 5.3wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围) 5.4sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常 5.5sleep方法属于Thread类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态, 不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了sleep方法之后, 并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。 但在sleep的过程中过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常, 如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常, 那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。 注意sleep()方法是一个静态方法,也就是说他只对当前对象有效,通过t.sleep()让t对象进入sleep,这样的做法是错误的, 它只会是使当前线程被sleep 而不是t线程 wait属于Object的成员方法,一旦一个对象调用了wait方法, 必须要采用notify()和notifyAll()方法唤醒该进程; 如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源, 而不限于这个被调用了wait()方法的对象。wait()方法也同样会在wait的过程中有可能被其他对象调用interrupt()方法而产生 6.Java中有哪些注解?在SpringMVC中,requestmapping是自定义注解,问:如何实现自定义注解? 6.1@Target用来定义注解将应用于什么地方,由ElementType枚举定义(如一个方法或者一个域) constructor:构造器的声明;field:域声明(包括enum实例);local_variable:局部变量声明 method:方法声明;package:包声明;parameter:参数声明 type:类、接口(包括注解类型)或enum声明;annotation_type:注解声明(应用于另一个注解上) type_parameter:类型参数声明(1.8新加入);type_use:类型使用声明(1.8新加入) 6.2@Retention用来定义注解在哪一个级别可用,由RetentionPolicy枚举定义,在源代码中(source),类文件中(class)或者运行时(runtime) source:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里) class:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机(JVM)中) runtime:VM将在运行期也保留注解信息,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息) PS:当注解未定义Retention值时,默认值是class 6.3@Documented 表示注解会被包含在javaapi文档中 6.4@Inherited 允许子类继承父类的注解 6.5@Deprecated 来标记类,方法,属性,不再使用 6.6@Override 用来修饰对父类进行重写的方法。如果一个并非重写父类的方法使用这个注解,编译器将提示错误 6.7@SuppressWarnings 用来抑制编译器生成警告信息。 6.8实现自定义注解: @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface RandomlyThrowsException { } part2JAVA程序员二面 1、线程池,threadpool有哪些重要的参数? corePoolSize:核心池的大小,这个参数与后面讲述的线程池的实现原理有非常大的关系。 在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务, 除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法, 从这2个方法的名字就可以看出,是预创建线程的意思, 即在没有任务到来之前就创建corePoolSize个线程或者一个线程。 默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后, 就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后, 就会把到达的任务放到缓存队列当中; maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程; keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用, 直到线程池中的线程数不大于corePoolSize:即当线程池中的线程数大于corePoolSize时, 如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize; 但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时, keepAliveTime参数也会起作用,直到线程池中的线程数为0; unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性,以及TimeUtil的源码展示: ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler); new ThreadPoolExecutor(0, 2147483647, 9223372036854775807L, TimeUnit.MILLISECONDS, new SynchronousQueue(), var1); 2.http协议头有哪些字段? HTTP请求头和响应头的含义 : 请求头: Accept: text/html,image/*(浏览器可以接收的类型) Accept-Charset: ISO-8859-1(浏览器可以接收的编码类型) Accept-Encoding: gzip,compress(浏览器可以接收压缩编码类型) Accept-Language: en-us,zh-cn(浏览器可以接收的语言和国家类型) Host: www.it315.org:80(浏览器请求的主机和端口) If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT(某个页面缓存时间) Referer: http://www.it315.org/index.jsp(请求来自于哪个页面) User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)(浏览器相关信息) Cookie:(浏览器暂存服务器发送的信息) Connection: close(1.0)/Keep-Alive(1.1)(HTTP请求的版本的特点) Date: Tue, 11 Jul 2000 18:23:51 GMT(请求网站的时间) 响应头: Location: http://www.it315.org/index.jsp(控制浏览器显示哪个页面) Server:apache tomcat(服务器的类型) Content-Encoding: gzip(服务器发送的压缩编码方式) Content-Length: 80(服务器发送显示的字节码长度) Content-Language: zh-cn(服务器发送内容的语言和国家名) Content-Type: image/jpeg; charset=UTF-8(服务器发送内容的类型和编码类型) Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT(服务器最后一次修改的时间) Refresh: 1;url=http://www.it315.org(控制浏览器1秒钟后转发URL所指向的页面) Content-Disposition: attachment; filename=aaa.jpg(服务器控制浏览器发下载方式打开文件) Transfer-Encoding: chunked(服务器分块传递数据到客户端) Set-Cookie:SS=Q0=5Lb_nQ; path=/search(服务器发送Cookie相关的信息) Expires: -1(服务器控制浏览器不要缓存网页,默认是缓存) Cache-Control: no-cache(服务器控制浏览器不要缓存网页) Pragma: no-cache(服务器控制浏览器不要缓存网页) Connection: close/Keep-Alive(HTTP请求的版本的特点) Date: Tue, 11 Jul 2000 18:23:51 GMT(响应网站的时间) 3、oracle如何实现分页(手写) select * from (select a.*, rownum rn from (select * from t_bmgl_bj) a where rownum <= 10) where rn >= 1; 4、SpringMVC在处理前端页面请求时,各模块是如何工作的? Spring工作流程描述 1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获; 2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象 (包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回; 3. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法) 4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作: HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中 5. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象; 6. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ; 7. ViewResolver 结合Model和View,来渲染视图 8. 将渲染结果返回给客户端。 5、数据库索引有什么作用?带来的问题是什么? 第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 第二,可以大大加快 数据的检索速度,这也是创建索引的最主要的原因。 第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 第四,在使用分组和排序 子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。 第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。 也许会有人要问:增加索引有如此多的优点,为什么不对表中的每一个列创建一个索引呢?这种想法固然有其合理性,然而也有其片面性。 虽然,索引有许多优点,但是,为表中的每一个列都增加索引,是非常不明智的。这是因为,增加索引也有许多不利的一个方面。 第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。 第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
转载于:https://my.oschina.net/llsydn/blog/1837187