Java对象之家--Java堆
java堆一般被分为新生代和老年代。其中新生代存放新生对象,老年代存放老年对象(年龄判断见深入理解jvm二),新生代有可能分为eden区,s0区,s1区,s0和s1区也被称为from区和to区,他们是两块大小相等、可以互换角色的内存空间(实际上是为了进行垃圾回收,即复制算法)。
下面通过示例展示java堆、方法区、java栈之间的关系:
public class SimpleHeap {
private int id;
public SimpleHeap(int id) {
super();
this.id = id;
}
public void show(){
System.out.println("My ID is "+id);
}
public static void main(String[] args) {
SimpleHeap s1 = new SimpleHeap(1);
SimpleHeap s2 = new SimpleHeap(2);
s1.show();
s2.show();
}
}
上述示例中,main函数创建的两个simpleHeap示例放在java堆中,描述simpleHeap类的信息存放在方法区,main函数中s1、s2局部变量存放在java栈中,并指向堆中的两个实例。
对象工位--出入java栈
Java虚拟机提供xss来指定线程的最大栈空间,也就决定了调用的最大深度。
实验一:探究栈空间的大小对函数调用深度的影响
public class TestStackDeep1 {
private static int count = 0;
private static void recursion(){
count++;
recursion();
}
public static void main(String[] args) {
try{
recursion();
}catch(Throwable e){
System.out.println("deep of calling = "+count);
e.printStackTrace();
}
}
}
1. jvm参数为
-Xss128K
输出结果为:
deep of calling = 1083
java.lang.StackOverflowError
at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:6)
at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:7)
at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:7)
2.设置jvm参数为
-Xss256K
输出结果为:
deep of calling = 2726
java.lang.StackOverflowError
at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:6)
at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:7)
实验结果表明,java栈的空间越大,函数调用深度越大。
局部变量表
局部变量表用于保存函数的参数以及局部变量。局部变量表中的变量只在当前函数调用中有效,当前函数调用结束后,随着函数栈帧销毁。而函数的参数和局部变量的增多又会使局部变量表膨胀,占用的栈空间变大,从而减少可调用次数。
实验二:局部变量表的膨胀对函数调用深度的影响
public class TestStackDeep2 {
private static int count =0 ;
public static void recursion(long a,long b,long c){
long e=1,f=2,g=3,h=4,i=5,k=6,q=7,x=8,y=9,z=10;
count++;
recursion(a, b, c);
}
public static void recursion(){
count++;
recursion();
}
public static void main(String[] args) {
try{
recursion(0, 0, 0);
//recursion();
}catch(Throwable e){
System.out.println("deep of calling = "+count);
e.printStackTrace();
}
}
}
设置jvm参数为
-Xss128K
当选用有参函数时,输出结果为:
deep of calling = 304
java.lang.StackOverflowError
at chapter2_4.TestStackDeep2.recursion(TestStackDeep2.java:6)
at chapter2_4.TestStackDeep2.recursion(TestStackDeep2.java:8)
用jclasslib可以看出,第一个recursion函数的最大局部变量表大小为:
它的局部变量表为
第二个recursion函数的最大局部变量表大小为:
它的局部变量表为
当选用无参函数时结果与实验一同参数结果一致,所以可以看到,但局部变量和函数参数增多时,局部变量表膨胀导致函数调用的最大深度下降。
实验三:探究局部变量表中的槽位是否可重用
public class Localvar {
public void localvar1() {
int a = 0;
System.out.println(a);
int b = 0;
}
public void localvar2() {
{
int a = 0;
System.out.println(a);
}
int b=0;
}
}
查看localvavr1函数的局部变量表
查看函数localvar2的局部变量表
可以看到如果一个局部变量过了其作用域,那么在其作用域之后声明的局部变量就有可能过期局部变量的槽位,从而节省资源。
实验四:局部变量对垃圾回收的影响
public class LocalvarGC {
public void localcarGC1(){//申请空间后立即回收
byte[] a = new byte[6*1024*1024];
System.gc();
}
public void localcarGC2(){//先置为null,使其失去强引用再回收
byte[] a = new byte[6*1024*1024];
a=null;
System.gc();
}
public void localcarGC3(){//先使局部变量失效再回收
{
byte[] a = new byte[6*1024*1024];
}
System.gc();
}
public void localcarGC4(){//使变量c复用变量a的字,对数组回收
{
byte[] a = new byte[6*1024*1024];
}
int c=10;
System.gc();
}
public void localcarGC5(){//调用函数1,再函数返回后在进行回收
localcarGC1();
System.gc();
}
public static void main(String[] args) {
LocalvarGC gc = new LocalvarGC();
gc.localcarGC1();
gc.localcarGC2();
gc.localcarGC3();
gc.localcarGC4();
gc.localcarGC5();
}
}
gc.localcarGC2();
gc.localcarGC3();
gc.localcarGC4();
gc.localcarGC5();
}
}
设置jvm参数:
-XX:+PrintGC
输出结果为:
[GC 7475K->6744K(124416K), 0.0046744 secs]
[Full GC 6744K->6606K(124416K), 0.0092658 secs]//无回收
[GC 14081K->6638K(124416K), 0.0003266 secs]
[Full GC 6638K->461K(124416K), 0.0067632 secs]//有回收
[GC 7271K->6605K(124416K), 0.0011679 secs]
[Full GC 6605K->6605K(124416K), 0.0030256 secs]//无回收
[GC 13415K->6605K(124416K), 0.0003572 secs]
[Full GC 6605K->461K(124416K), 0.0060987 secs]//有回收
[GC 7271K->6605K(124416K), 0.0010584 secs]
[Full GC 6605K->6605K(124416K), 0.0031301 secs]//无回收
[GC 6605K->6605K(124416K), 0.0002726 secs]
[Full GC 6605K->461K(124416K), 0.0065306 secs]//有回收
可以看到,但局部变量的作用域失效、失去强引用、所在栈帧销毁时,才执行垃圾回收。
jvm优化--栈上分配
对于那些线程私有的对象,可以将他们打散分配在栈上,而不是分配在堆上,分配在栈上的好处是函数调用后自行销毁,不许进行垃圾回收,从而提高系统性能。栈上分配的基础是进行逃逸分析,判断对象作用域是否有可能逃逸出函数体
实验五:对非逃逸对象的栈上分配
public class OnStackTest {
public static class User{
public int id;
public String name;
}
//private static User u;//逃逸对象
public static void alloc(){
User u = new User();//非逃逸对象,该对象没有被alloc函数返回,未发生逃逸
u.id = 5;
u.name = "geym";
}
public static void main(String[] args) {
long b = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
alloc();
}
long e = System.currentTimeMillis();
System.out.println(e-b);
}
}
设置jvm参数(在server模式下启用逃逸分析并打印gc日志,同时开启标量替换):
-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations
程序输出结果
7
可以看到,程序没有进行任何形式的gc回收就执行完成,说明user对象的分配过程被优化。
类的户口注册--方法区
大量类信息存放在方法区时会产生溢出
实验六 方法区的溢出观察
需要的jar包:
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class PermTest {
static class OOMObject {
}
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
// TODO Auto-generated method stub
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
}
输出结果:
[GC [PSYoungGen: 12016K->1247K(38400K)] 12016K->1255K(124416K), 0.0020854 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 1247K->0K(38400K)] [ParOldGen: 8K->1147K(64000K)] 1255K->1147K(102400K) [PSPermGen: 4095K->4094K(4096K)], 0.0148559 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC [PSYoungGen: 0K->0K(38400K)] 1147K->1147K(102400K), 0.0003406 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 0K->0K(38400K)] [ParOldGen: 1147K->1147K(124928K)] 1147K->1147K(163328K) [PSPermGen: 4095K->4095K(4096K)], 0.0050305 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC [PSYoungGen: 0K->0K(38400K)] 1147K->1147K(163328K), 0.0002956 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 0K->0K(38400K)] [ParOldGen: 1147K->968K(196096K)] 1147K->968K(234496K) [PSPermGen: 4095K->4095K(4096K)], 0.0115525 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC [PSYoungGen: 0K->0K(39424K)] 968K->968K(235520K), 0.0003338 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 0K->0K(39424K)] [ParOldGen: 968K->968K(309760K)] 968K->968K(349184K) [PSPermGen: 4095K->4090K(4096K)], 0.0116310 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
Exception in thread "main" [GC [PSYoungGen: 1392K->128K(35328K)] 2361K->1096K(345088K), 0.0005686 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 128K->0K(35328K)] [ParOldGen: 968K->789K(429568K)] 1096K->789K(464896K) [PSPermGen: 4094K->4094K(4096K)], 0.0122008 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
[GC [PSYoungGen: 0K->0K(36864K)] 789K->789K(466432K), 0.0003058 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 0K->0K(36864K)] [ParOldGen: 789K->789K(595456K)] 789K->789K(632320K) [PSPermGen: 4094K->4094K(4096K)], 0.0058110 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC [PSYoungGen: 1090K->96K(38400K)] 1880K->885K(633856K), 0.0004882 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 96K->0K(38400K)] [ParOldGen: 789K->798K(766464K)] 885K->798K(804864K) [PSPermGen: 4095K->4095K(4096K)], 0.0132138 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC [PSYoungGen: 737K->32K(39424K)] 1536K->830K(805888K), 0.0005683 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 32K->0K(39424K)] [ParOldGen: 798K->798K(986624K)] 830K->798K(1026048K) [PSPermGen: 4095K->4095K(4096K)], 0.0077948 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
[GC [PSYoungGen: 0K->0K(39424K)] 798K->798K(1026048K), 0.0002922 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 0K->0K(39424K)] [ParOldGen: 798K->798K(1223168K)] 798K->798K(1262592K) [PSPermGen: 4095K->4095K(4096K)], 0.0059662 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 39424K, used 768K [0x00000000d5d00000, 0x00000000d8580000, 0x0000000100000000)
eden space 38400K, 2% used [0x00000000d5d00000,0x00000000d5dc0030,0x00000000d8280000)
from space 1024K, 0% used [0x00000000d8280000,0x00000000d8280000,0x00000000d8380000)
to space 1024K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8580000)
ParOldGen total 1223168K, used 798K [0x0000000081800000, 0x00000000cc280000, 0x00000000d5d00000)
object space 1223168K, 0% used [0x0000000081800000,0x00000000818c7a80,0x00000000cc280000)
PSPermGen total 4096K, used 4095K [0x0000000081400000, 0x0000000081800000, 0x0000000081800000)
object space 4096K, 99% used [0x0000000081400000,0x00000000817ffff0,0x0000000081800000)
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
可以看到当大量的动态类生成后,方法区产生了溢出。要注意类和对象实例的区别,类主要是指类文件编译后的代码等数据,而实例是指从类文件中创建的对象。