文章目录 要不要在for循环内部new第一种情况 对于new的调用只存在于for循环以内第二种情况 对于new的调用不止存在于for循环以内外部也有调用 new 和 clone的区别 哪个更快new一个对象的过程clone 对象
要不要在for循环内部new 第一种情况 对于new的调用只存在于for循环以内
对象的保存位置是在堆内存中, p只是一个记录了堆地址的4个字节的引用 p存在的位置是栈内存
基与这个点我们可以画图表示下面的内存情况
对象new了1000次
对象new了1次
通过比对就可以很明显的发现第三种方法是最节省jvm内存的.第一种和第二种的区别在于栈空间内变量引用的数据,第一种只有一个变量,第二种有1000个变量.
对于这种情况上面的第三种写法就不可以 只能是前两种写法
public void demo() { List list= new ArrayList(); Persion p = null; for (int i = 0; i < 10; i++) { p = new Persion(); p.setAge(i); list.add(p); } list.stream().forEach(per -> { // 业务处理 System.out.println(per.toString()); }); }p只是一个引用,每次循环保存到集合里面的都是一个新的地址值
public void demo() { List list= new ArrayList(); for (int i = 0; i < 10; i++) { Persion p = new Persion(); p.setAge(i); list.add(p); } list.stream().forEach(p -> { // 业务处理 System.out.println(p.toString()); }); }基与java gc的考虑
第一 堆的角度 因为堆内存中必定要分配1000个对象大小的空间,两者的是没有区别的
第二 生命周期角度 第一个对象的作用域是整个方法 第二个对象的作用域是在for循环以内
他们不可达阶段是一致的,(不可达的对象才会被gc)
对象的生命周期 创建 -> 应用 -> 不可见 -> 不可达 -> 收集 ->终结 ->对象内存空间重新分配
基与栈的考虑
栈的管理是jvm自己管理的,没有gc的参与,一个线程对应一个栈,一个方法对应一个栈帧,方法执行完成之后自动出站,栈是先进后出,栈内存的大小可以通过jvm配置的参数来设定 -Xss256k,栈内存的溢出一般都是因为递归调用太深,或者递归调用使用不当.一个方法里面定义的变量再这个方法出站的时候,就一起跟着消失了.
所以我认为上面的两种写法几乎是没有差别的.
默认这个类第一次被加载 之前没有加过,new对象的时候首先会检查class是否被加载到方法区,如果加载过了就不会在加载,如果没有加载过回重新加载
1.加载:类加载器把类加载到jvm内并将类的信息保存到方法区
2.验证 准备 解析 这三个过程也叫做链接
3.初始化:初始化的时候为static 变量 static{代码块}赋值 先父类初始化赋值 再后子类初始化赋值
1->3是类加载的过程
4.给对象分配堆内存 有两种方式 指针碰撞 空闲列表
5.给变量赋默认值 从方法区内拷贝变量的引用 给堆内的对象赋默认值
6.最后执行构造方法 先执行父类的构造方法再执行子类的构造方法,如果继承关系比较负载父类还有父父父父类的话,要从顶级的父类开始执行,这种情况是比较耗时的.
Java 中的clone 需要对象实现 Cloneable
clone 默认是浅拷贝 对于基本数据类型拷贝的是值,对于引用数据类型拷贝的是引用,两个clone的对象内部还是存在联系的.
String 是引用数据类型 但是String很特别是不能被修改的,"zhangsan"是不能被修改的,所以这个例子中不会有什么问题.
如果name属性换成一个Car对象 car里面有color 如果p1修改了自己的car的颜色,p2的car颜色也会被修改所以存在一定的隐患
执行结果
falsePersion{name='zhangsan', age=0,car.color=yellow}Persion{name='zhangsan', age=0,car.color=red}clone与new的区别在于clone不用执行对象的构造方法,当对象的继承关系复杂 父类和自己的构造方法内操作比较多(比如初始化了好多的对象);这种情况clone的效率会明显高于new,如果是一个简单的对象简单的构造方法,初始化的是时间基本一样(亲自测过 clone 不见得一定快);