java 笔面试第二弹

目录##

1.内存屏障
2.关键字native
3.伪共享
4.线程Dump文件
5.内存溢出
6.jvm 配置和参数

1.内存屏障##

目前有4种屏障----

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

2.native##

"A native method is a Java method whose implementation is provided by non-java code."

java中,native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口(Java Native Interfac)调用其他语言来实现对底层的访问。存在的必要:需要与java外的环境做交互,与jvm,操作系统交互得到更多的特性,sun的解释器是C实现的,实现更好的交互。

  1. 标识符native可以与所有其它的java标识符连用,但是abstract除外
  2. 一个native method方法可以返回任何java类型,包括非基本类型,而且同样可以进行异常控制。
  3. 使用本地方法是有开销的

**JVM怎样使Native Method跑起来: **

当一个类第一次被使用到时,这个类的字节码会被加载到内存,并且只会回载一次。在这个被加载的字节码的入口维持着一个该类所有方法描述符的list,这些方法描述符包含这样一些信息:方法代码存于何处,它有哪些参数,方法的描述符(public之类)等等。

如果一个方法描述符内有native,这个描述符块将有一个指向该方法的实现的指针。这些实现在一些DLL文件内,但是它们会被操作系统加载到java程序的地址空间。当一个带有本地方法的类被加载时,其相关的DLL并未被加载,因此指向方法实现的指针并不会被设置。当本地方法被调用之前,这些DLL才会被加载,这是通过调用java.system.loadLibrary()实现的。

JNI的书写步骤如下:

a.编写带有native声明的方法的Java类

b.使用javac命令编译编写的Java类

c.使用java -jni ****来生成后缀名为.h的头文件

d.使用其他语言(C、C++)实现本地方法

e.将本地方法编写的文件生成动态链接库

注:
1.在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。
2.http://blog.csdn.net/wike163/article/details/6635321

3.伪共享##

伪共享(false sharing),就是多个线程同时修改共享在同一个缓存行里的独立变量,无意中影响了性能。

java 笔面试第二弹_第1张图片
伪共享发生的过程

当核心1上的线程想更新X,而核心2上的线程想更新Y,而X变量和Y变量在同一个缓存行中时;每个线程都要去竞争缓存行的所有权来更新变量。如果核心1获得所缓存行的所有权,那么缓存子系统将会使核心2中对应的缓存行失效,反之亦然。这会来来回回的经过L3缓存,大大影响了性能。这种情况,就像多个线程同事竞争锁的所有权一样。如果互相竞争的核心位于不同的插槽,就要额外横跨插槽连接,问题可能更加严重。

典型的CPU微架构有3级缓存, 每个核都有自己私有的L1, L2缓存.

解决伪共享的方法是通过补齐(Padding),使得每一条缓存行只存一个多线程变量

参考:
http://www.it165.net/pro/html/201403/11104.html
http://coderplay.iteye.com/blog/1486649

4.线程Dump文件##

Dump文件是进程的内存镜像。可以把程序的执行状态通过调试器保存到dump文件中。
Dump文件是用来给驱动程序编写人员调试驱动程序用的,这种文件必须用专用工具软件打开,比如使用WinDbg打开。

线程dump出来的信息包含线程基本信息;线程的运行状态、标识和调用的堆栈;调用的堆栈包含完整的类名,所执行的方法,如果可能的话还有源代码的行数。因此,可以使用线程Dump文件来进行诊断一些问题, 包括线程阻塞,CPU 使用率过高,JVM Crash,堆内存不足和类装载等。

输出的方法:

win下,在启动程序的控制台里敲: Ctrl - Break,线程的 dump会产生在标准输出中

Linux 下,你可以通过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java 应用的 dump 文件。

5.内存溢出##

1.什么是内存溢出

内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存。

jvm管理的内存大致包括三种不同类型的内存区域:Permanent Generation space(永久保存区域)、Heap space(堆区域)、Java Stacks(Java栈)。
永久保存区域主要存放Class(类)和Meta的信息,Class第一次被Load的时候被放入PermGen space区域,Class需要存储的内容主要包括方法和静态属性。
堆区域用来存放Class的实例(即对象),对象需要存储的内容主要是非静态属性。每次用new创建一个对象实例后,对象实例存储在堆区域中,这部分空间也被jvm的垃圾回收机制管理。
而Java栈跟大多数编程语言包括汇编语言的栈功能相似,主要基本类型变量以及方法的输入输出参数。Java程序的每个线程中都有一个独立的堆栈。容易发生内存溢出问题的内存空间包括:Permanent Generation space和Heap space。

2.引起内存溢出的主要原因

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的BUG;
5.启动参数内存值设定的过小

3.解决内存溢出的方法

第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

4.三种类型

** OutOfMemoryError: PermGen space**

发生这种问题的原意是程序中使用了大量的jar或class,使java虚拟机装载类的空间不够,与Permanent Generation space有关。解决这类问题有以下两种办法:

  1. 增加java虚拟机中的XX:PermSize和XX:MaxPermSize参数的大小,其中XX:PermSize是初始永久保存区域大小,XX:MaxPermSize是最大永久保存区域大小。如针对tomcat6.0,在catalina.sh 或catalina.bat文件中一系列环境变量名说明结束处(大约在70行左右) 增加一行:
    JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m"

  2. 清理应用程序中web-inf/lib下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,可以将共同的jar移到tomcat共同的lib下,减少类的重复加载。

OutOfMemoryError: Java heap space

发生这种问题的原因是java虚拟机创建的对象太多,在进行垃圾回收之间,虚拟机分配的到堆内存空间已经用满了,与Heap space有关。解决这类问题有两种思路:

  1. 检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法。

  2. 增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m

OutOfMemoryError:unable to create new native thread

这种错误在Java线程个数很多的情况下容易发生。

6.jvm 配置和参数##

-Xms 初始堆大小
-Xmx 最大堆大小
-Xmn 年轻代大小
-Xss 每个线程的堆栈大小
-XX:参数

具体的内容参照下面的引文吧。

堆分配

JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。
对象的堆内存由称为垃圾回收器的自动内存管理系统回收。

java 笔面试第二弹_第2张图片
堆分配

Eden:存放新生的对象
Survivor Space:有两个,存放每次垃圾回收后存活的对象
Old Generation:主要存放应用程序中生命周期长的存活对象

JVM(采用分代回收的策略),用较高的频率对年轻的对象(young generation)进行YGC,而对老对象(tenured generation)较少(tenured generation 满了后才进行)进行Full GC。这样就不需要每次GC都将内存中所有对象都检查一遍。

非堆内存分配

JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。

GC不会在主程序运行期对PermGen Space进行清理,所以如果你的应用中有很多CLASS(特别是动态生成类,当然permgen space存放的内容不仅限于类)的话,就很可能出现PermGen Space错误。

参考:
JVM性能优化
JVM参数设置、分析
GC策略&内存申请、对象衰老
HotSpot VM GC 的种类

你可能感兴趣的:(java 笔面试第二弹)