百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

一键释放iOS 64位App潜力

ccwgpt 2025-04-01 16:24 14 浏览 0 评论

作者:eddiecmchen,PCG客户端开发工程师

| 导语 把我的iPhone XR扶起来,它还能再顶一会儿~

背景

远在iOS 11时期(2017年),苹果就发公告要求所有需要上架AppStore的应用都必须支持64位。32位应用不再支持上架与运行。

升级64位应用有什么好处呢?(以下内容纯摘抄,客官可以直接跳过)

  • 指针字长更长,可使用的虚拟内存更大,摆脱32位下受限的4G内存空间
    • 16 bit = 65,536 bytes (64 Kilobytes)
    • 32 bit = 4,294,967,296 bytes (4 Gigabytes)
    • 64 bit = 18,446,744,073,709,551,616 (16 Exabytes)
  • 寄存器更多,减少内存读写,加快执行速度

这里我们要注意的是:虚拟内存确实比纯32位多了,但是App到底能用多少,是否跟宣传一样接近16EB?下面将会展开聊聊,我们先来看一个Crash。

一个长期存在的幽灵

我们先来看下面的一个内存导致的崩溃,JSC在使用bmalloc尝试进行内存分配时,提示OOM导致了SIGTRAP。

Last Exception :
0  JavaScriptCore                 0x000000018b777570 _pas_panic_on_out_of_memory_error
1  JavaScriptCore                 0x000000018b72e918 _bmalloc_try_iso_allocate_impl_impl_slow
2  JavaScriptCore                 0x000000018b73d3d8 _bmalloc_heap_config_specialized_local_allocator_try_allocate_small_segregated_slow +  5952
3  JavaScriptCore                 0x000000018b7276f8 _bmalloc_allocate_impl_casual_case +  800
4  JavaScriptCore                 0x000000018c60d494 JSC::PropertyTable::create(JSC::VM&, unsigned int) +  244
5  JavaScriptCore                 0x000000018c66ba74 JSC::Structure::materializePropertyTable(JSC::VM&, bool) +  324
6  JavaScriptCore                 0x000000018c66dfac JSC::Structure::changePrototypeTransition(JSC::VM&, JSC::Structure*, JSC::JSValue, JSC::DeferredStructureTransitionWatchpointFire&) +  612
7  JavaScriptCore                 0x000000018c559930 JSC::JSObject::setPrototypeDirect(JSC::VM&, JSC::JSValue) +  192
8  JavaScriptCore                 0x000000018c559e40 JSC::JSObject::setPrototypeWithCycleCheck(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue, bool) +  316
9  JavaScriptCore                 0x000000018c4f580c JSC::globalFuncProtoSetter(JSC::JSGlobalObject*, JSC::CallFrame*) +  192
10 JavaScriptCore                 0x000000018ba1f7a8 _vmEntryToNative +  280
11 JavaScriptCore                 0x000000018c1b0cd0 JSC::Interpreter::executeCall(JSC::JSGlobalObject*, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) +  616
12 JavaScriptCore                 0x000000018c474ecc JSC::GetterSetter::callSetter(JSC::JSGlobalObject*, JSC::JSValue, JSC::JSValue, bool) +  212
13 JavaScriptCore                 0x000000018c5b6264 JSC::JSGenericTypedArrayView::put(JSC::JSCell*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&) +  612
14 JavaScriptCore                 0x000000018c2c2ecc _llint_slow_path_put_by_id +  3244
// 忽略多余重复堆栈
37 JavaScriptCore                 0x000000018ba1f5fc _vmEntryToJavaScript +  264
38 JavaScriptCore                 0x000000018c1b0c7c JSC::Interpreter::executeCall(JSC::JSGlobalObject*, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) +  532
39 JavaScriptCore                 0x000000018bac7ae4 _JSObjectCallAsFunction +  568
40 mttlite                        0x0000000102a54914 hippy::napi::JSCCtx::CallFunction(std::__1::shared_ptr const&, unsigned long, std::__1::shared_ptr const*) (js_native_api_value_jsc.cc:406)
41 mttlite                        0x0000000102a664e0 _ZNSt3__110__function6__funcIZN11TimerModule5StartERKN5hippy4napi12CallbackInfoEbE3$_4NS_9allocatorIS8_EEFvvEEclEv (memory:3237)
42 mttlite                        0x0000000102a63018 hippy::base::TaskRunner::Run() (memory:3237)
43 mttlite                        0x0000000102a64974 ThreadEntry (thread.cc:0)
44 libsystem_pthread.dylib        0x00000001dc129348 __pthread_start +  116
------

Exception Type: SIGTRAP 
Exception Codes: fault addr: 0x000000018b777570
Crashed Thread: 48 hippy.js

这个OOM问题,与iOS上常见的OOM不一样。按照常规的理解,当App内存不足的时候,正常会触发系统的Jetsam机制杀死App。在系统日志中会留下Jetsam相关日志,理论上不会在Bugly等异常上报中发现。但这一类崩溃却一直在产生上报,并且低内存的崩溃堆栈表现形式有很多种。

以上的JSC崩溃问题已经存在很长一段时间了(至少2年),而且崩溃堆栈都集中在JSC执行JS代码的过程中,长期缺乏JS相关的监控与Debug工具导致该问题一直无法解决。

虽然堆栈上有明确的原因说明是OOM,但我们观察到有不少用户实际上物理内存空间还是足够的:

两年前,冲浪的时候偶然看来了来自微视同学的Case总结:《OOM与内存》

当时跟hippy SDK的同事也讨论过是否存在类似的内存不足情况。但由于大家对JSC黑盒都不熟悉,而且崩溃的JS堆栈也不确切。当时的建议是:少在后台加载JSC。最终也并没有解决该问题。

两年后,当浏览器集成flutter,类似的JS崩溃直接翻倍(21H2 0.08% -> 22H1 0.16%)。没办法,还是要看类似JSC和Dart VM的内存分配机制是怎样的,再挖掘一下是否存在解()决()方案。

JSC、DartVM的虚拟内存分配

翻阅相关虚拟机的内存管理相关代码,可以找到底层的内存分配基本实现都是基于mmap处理的。

// WebKit bmalloc VMAllocate
inline void* tryVMAllocate(size_t vmSize, VMTag usage = VMTag::Malloc)
{
    vmValidate(vmSize);
    void* result = mmap(0, vmSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | BMALLOC_NORESERVE, static_cast(usage), 0);
    if (result == MAP_FAILED)
        return nullptr;
    return result;
}
// Dart VM的虚拟内存
VirtualMemory* VirtualMemory::Allocate(intptr_t size,
                                       bool is_executable,
                                       const char* name) {
  ASSERT(Utils::IsAligned(size, PageSize()));

  const int prot = PROT_READ | PROT_WRITE | (is_executable ? PROT_EXEC : 0);

  int map_flags = MAP_PRIVATE | MAP_ANONYMOUS;
#if (defined(DART_HOST_OS_MACOS) && !defined(DART_HOST_OS_IOS))
  if (is_executable && IsAtLeastOS10_14()) {
    map_flags |= MAP_JIT;
  }
#endif  // defined(DART_HOST_OS_MACOS)

  // Some 64-bit microarchitectures store only the low 32-bits of targets as
  // part of indirect branch prediction, predicting that the target's upper bits
  // will be same as the call instruction's address. This leads to misprediction
  // for indirect calls crossing a 4GB boundary. We ask mmap to place our
  // generated code near the VM binary to avoid this.
  void* hint = is_executable ? reinterpret_cast(&Allocate) : nullptr;
  void* address = mmap(hint, size, prot, map_flags, -1, 0);
  if (address == MAP_FAILED) {
    return nullptr;
  }
  return new VirtualMemory(address, size);
}

VirtualMemory::~VirtualMemory() {
  if (address_ != nullptr) {
    if (munmap(address_, size_) != 0) {
      int error = errno;
      const int kBufferSize = 1024;
      char error_buf[kBufferSize];
      FATAL("munmap error: %d (%s)", error,
            Utils::StrError(error, error_buf, kBufferSize));
    }
  }
}

map_flags包含MAP_ANON时,并且fd传入-1时,mmap将直接使用虚拟内存进行分配,不需要依赖文件描述符。

mmap在xnu上的实现

/*
 * mmap stub, with preemptory failures due to extra parameter checking
 * mandated for conformance.
 *
 * This is for UNIX03 only.
 */
void *
mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
{
    /*
     * Preemptory failures:
     * 
     * o    off is not a multiple of the page size
     * o    flags does not contain either MAP_PRIVATE or MAP_SHARED
     * o    len is zero
     */
    extern void cerror_nocancel(int);
    if ((off & PAGE_MASK) ||
        (((flags & MAP_PRIVATE) != MAP_PRIVATE) &&
         ((flags & MAP_SHARED) != MAP_SHARED)) ||
        (len == 0)) {
        cerror_nocancel(EINVAL);
        return(MAP_FAILED);
    }

    void *ptr = __mmap(addr, len, prot, flags, fildes, off);
    
    if (__syscall_logger) {
        int stackLoggingFlags = stack_logging_type_vm_allocate;
        if (flags & MAP_ANON) {
            stackLoggingFlags |= (fildes & VM_FLAGS_ALIAS_MASK);
        } else {
            stackLoggingFlags |= stack_logging_type_mapped_file_or_shared_mem;
        }
        __syscall_logger(stackLoggingFlags, (uintptr_t)mach_task_self(), (uintptr_t)len, 0, (uintptr_t)ptr, 0);
    }

    return ptr;
}

上面的调用会传递到内核kern_mman.c的实现函数mmap(proc_t p, struct mmap_args *uap, user_addr_t *retval)

/*
 * XXX Internally, we use VM_PROT_* somewhat interchangeably, but the correct
 * XXX usage is PROT_* from an interface perspective.  Thus the values of
 * XXX VM_PROT_* and PROT_* need to correspond.
 */
int
mmap(proc_t p, struct mmap_args *uap, user_addr_t *retval)
{
        /*
         * 上面忽略了一部分代码
         */
        result = vm_map_enter_mem_object(user_map,
            &user_addr, user_size,
            0, alloc_flags, vmk_flags,
            tag,
            IPC_PORT_NULL, 0, FALSE,
            prot, maxprot,
            (flags & MAP_SHARED) ?
            VM_INHERIT_SHARE :
            VM_INHERIT_DEFAULT);

        /* If a non-binding address was specified for this anonymous
         * mapping, retry the mapping with a zero base
         * in the event the mapping operation failed due to
         * lack of space between the address and the map's maximum.
         */
        if ((result == KERN_NO_SPACE) && ((flags & MAP_FIXED) == 0) && user_addr && (num_retries++ == 0)) {
            user_addr = vm_map_page_size(user_map);
            goto map_anon_retry;
        }
        /*
         * 下面忽略了一部分代码
         */
}

其中又会调用vm_map.c内部的vm_map_enter_mem_object,而该方法最终会在vm_map_enter中依据对象进行内存分配:

// 下面这个只截了个头,大概带一下,我也没调过代码~
/*
 *  Routine:    vm_map_enter
 *
 *  Description:
 *      Allocate a range in the specified virtual address map.
 *      The resulting range will refer to memory defined by
 *      the given memory object and offset into that object.
 *
 *      Arguments are as defined in the vm_map call.
 */
kern_return_t
vm_map_enter(
    vm_map_t                map,
    vm_map_offset_t         *address,       /* IN/OUT */
    vm_map_size_t           size,
    vm_map_offset_t         mask,
    int                     flags,
    vm_map_kernel_flags_t   vmk_flags,
    vm_tag_t                alias,
    vm_object_t             object,
    vm_object_offset_t      offset,
    boolean_t               needs_copy,
    vm_prot_t               cur_protection,
    vm_prot_t               max_protection,
    vm_inherit_t            inheritance)

其中vm_map_enter在分配过程中会对hole_entry→vme_end作判断,vme_end即最大的可分配空间。

xnu上虚拟内存的分配范围

本来我只是观察到苹果在iOS15上增加了com.apple.developer.kernel.increased-memory-limit的能力声明。本着死马当活马医的想法,尝试在新版本上添加该声明以缓解一部分问题。

结果偶然看到部分开发者提问:该能力可配合com.apple.developer.kernel.extended-virtual-addressing使用。看到后我一下子反应过来,顺手搜到了今年二月国外有大佬做了相关的探索:

Size Matters: An Exploration of Virtual Memory on iOS

文章阐述了iOS的内存管理机制和虚拟内存空间分配在不同的机型上存在上限,代码如下:

#define ARM64_MIN_MAX_ADDRESS (SHARED_REGION_BASE_ARM64 + SHARED_REGION_SIZE_ARM64 + 0x20000000) // end of shared region + 512MB for various purposes
const vm_map_offset_t min_max_offset = ARM64_MIN_MAX_ADDRESS; // end of shared region + 512MB for various purposes

if (arm64_pmap_max_offset_default) {
    max_offset_ret = arm64_pmap_max_offset_default;
} else if (max_mem > 0xC0000000) {
    max_offset_ret = min_max_offset + 0x138000000; // Max offset is 13.375GB for devices with > 3GB of memory
} else if (max_mem > 0x40000000) {
    max_offset_ret = min_max_offset + 0x38000000;  // Max offset is 9.375GB for devices with > 1GB and <= 3GB of memory
} else {
    max_offset_ret = min_max_offset;
}

并且总结了一个上限值与机型表格:

RAM

Address Space

Usable

Devices

> 3 GiB

15.375 GiB

7.375 GiB

- iPhone XS – iPhone 13
- iPad Air (4th generation)
- iPad Pro (12.9-inch), (10.5-inch), (11-inch)

> 1 GiB

11.375 GiB

3.375 GiB

- iPhone 6s – X, SE, XR
- iPad (5th generation) – iPad (8th generation)
- iPad Air 2, iPad Air (3rd generation)
- iPad mini 4, iPad mini (5th generation)
- iPad Pro (9.7-inch)

<= 1 GiB

10.5 GiB

2.5 GiB

- iPhone 5s, iPhone 6
- iPad Air
- iPad mini 2, iPad mini 3

而xnu的源码(pmap.c)中还透露了内核内存分配存在jumbo机制。当iOS App带有指定的能力声明时,xnu内核将会以jumbo模式运行,虚拟内存地址空间将会直接分配为最大值64GB:

if (option == ARM_PMAP_MAX_OFFSET_JUMBO) {
    if (arm64_pmap_max_offset_default) {
        // Allow the boot-arg to override jumbo size
        max_offset_ret = arm64_pmap_max_offset_default;
    } else {
        max_offset_ret = MACH_VM_MAX_ADDRESS;     // Max offset is 64GB for pmaps with special "jumbo" blessing
    }
}

并且该上限值会在进程启动时进行调整,具体代码可以在kern_exec.c中找到:

/*
 * Apply the requested maximum address.
 */
if (error == 0 && imgp->ip_px_sa != NULL) {
    struct _posix_spawnattr *psa = (struct _posix_spawnattr *) imgp->ip_px_sa;

    if (psa->psa_max_addr) {
        vm_map_set_max_addr(get_task_map(new_task), (vm_map_offset_t)psa->psa_max_addr);
    }
}

甚少文档记录的entitlement

com.apple.developer.kernel.extended-virtual-addressing

苹果的文档仅有一句话说明该能力:

Use this entitlement if your app has specific needs that require a larger addressable space. For example, games that memory map assets to stream to the GPU may benefit from a larger address space.

举个例子:有的游戏需要将资源通过mmap的形式传递到GPU中渲染时,更大的地址空间可提高其运行效率。

描述上看,配置该选项时,将开启上面xnu的jumbo mode,地址的扩充刚好能解决上面的崩溃问题。

做一次极限测试

为验证地址分配的极限值,简单做个实验(测试设备使用iPhone XR iOS 16 Beta 2):

通过malloc进行连续的内存分配(也可以用vm_allocate,阈值不一样),阈值卡在1009字节(为什么是1009字节,这里可以参考【ios 内核】源码解读(3) 详解ios是怎么malloc的(上) - 钟路成的博客 (luchengzhong.github.io))。

for (size_t i = 0; i < SIZE_T_MAX; i++) {
    void *a = malloc(1009);
    if (a == NULL) {
        NSLog(@"error count: %lu", i);
        break;
    }
}

结果如下:

size = 1009 > SMALL_THRESHOLD (64位系统下1008字节,32位系统下496)

内存扩展前malloc失败阈值约 7065482 * 1009 = 6.63 GB

内存扩展后malloc失败阈值约 56753881 * 1009 = 53.33 GB

当然,在xnu的单元测试代码中,也可找到jumbo mode相关的测试代码,与上面的测试结果完全一致,即最多可分配53GB的空间。

#define GB (1ULL * 1024 * 1024 * 1024)

/*
 * This test expects the entitlement to be the enabling factor for a process to
 * allocate at least this many GB of VA space. i.e. with the entitlement, n GB
 * must be allocatable; whereas without it, it must be less.
 * This value was determined experimentally to fit on applicable devices and to
 * be clearly distinguishable from the default VA limit.
 */
#define ALLOC_TEST_GB 53

T_DECL(TESTNAME,
    "Verify that a required entitlement is present in order to be granted an extra-large "
    "VA space on arm64",
    T_META_NAMESPACE("xnu.vm"),
    T_META_CHECK_LEAKS(false))
{
    int i;
    void    *res;

    if (!dt_64_bit_kernel()) {
        T_SKIP("This test is only applicable to arm64");
    }

    T_LOG("Attemping to allocate VA space in 1 GB chunks.");

    for (i = 0; i < (ALLOC_TEST_GB * 2); i++) {
        res = mmap(NULL, 1 * GB, PROT_NONE, MAP_PRIVATE | MAP_ANON, 0, 0);
        if (res == MAP_FAILED) {
            if (errno != ENOMEM) {
                T_WITH_ERRNO;
                T_LOG("mmap failed: stopped at %d of %d GB allocated", i, ALLOC_TEST_GB);
            }
            break;
        } else {
            T_LOG("%d: %p\n", i, res);
        }
    }

#if defined(ENTITLED)
    T_EXPECT_GE_INT(i, ALLOC_TEST_GB, "Allocate at least %d GB of VA space", ALLOC_TEST_GB);
#else
    T_EXPECT_LT_INT(i, ALLOC_TEST_GB, "Not permitted to allocate %d GB of VA space", ALLOC_TEST_GB);
#endif
}

可见,当开启com.apple.developer.kernel.extended-virtual-addressing时,内核的可分配空间确实有明显提升。

上线效果与结论

从QQ浏览器的上线效果来看,JS相关的内存分配Crash在14.0以上系统几乎全部消失。上线第一天App崩溃率环比下降接近50%,效果显著。

简单总结:

  1. 苹果很少在公开文档中说明64位App在虚拟内存使用上存在限制。而且很多App也并没有像浏览器内一样,为业务灵活性而选择将hippy、flutter等技术进行大规模的组合使用,所以可能很多App其实并不会遇到虚拟内存不足的情况。
  2. 上线效果也说明浏览器在混合开发的场景下,内存优化仍然存在很大的空间。因为Extended Virtual Addressing仅能缓解虚拟内存不足的情况,并不意味着App的物理内存也得到增加,对FOOM的治理仍然需要持续。
  3. 鉴于司内有不少的著名组件都会使用mmap机制进行内存管理,建议在使用相关组件时,控制好mmap的大小。
  4. 如果有需要在iPhone 12 Pro、M1 iPad、M1上运行应用,并希望解放更多的物理内存,建议增加com.apple.developer.kernel.increased-memory-limit的能力声明,实测在iPhone 13 Pro下可以增加1GB的可用物理内存。
  5. ReactNative和类似框架在项目中使用较多的,建议需要考虑多个Context的复用,减少创建重复内容,司内外都有实践证明该措施十分有效。
  6. 对于flutter一类的内存优化,可翻阅engine的相关代码。flutter vm在创建时允许外部传参控制vm行为,包括:old heap size、leak vm等。合适的参数可比较有效控制内存占用。

以上源码相关的内容仅个人阅读理解,如有错误请指出。

相关推荐

团队管理“布阵术”:3招让你的团队战斗力爆表!

为何古代军队能够以一当十?为何现代企业有的团队高效似“特种部队”,有的却松散若“游击队”?**答案正隐匿于“布阵术”之中!**今时今日,让我们从古代兵法里萃取3个核心要义,助您塑造一支战斗力爆棚的...

知情人士回应字节大模型团队架构调整

【知情人士回应字节大模型团队架构调整】财联社2月21日电,针对原谷歌DeepMind副总裁吴永辉加入字节跳动后引发的团队调整问题,知情人士回应称:吴永辉博士主要负责AI基础研究探索工作,偏基础研究;A...

豆包大模型团队开源RLHF框架,训练吞吐量最高提升20倍

强化学习(RL)对大模型复杂推理能力提升有关键作用,但其复杂的计算流程对训练和部署也带来了巨大挑战。近日,字节跳动豆包大模型团队与香港大学联合提出HybridFlow。这是一个灵活高效的RL/RL...

创业团队如何设计股权架构及分配(创业团队如何设计股权架构及分配方案)

创业团队的股权架构设计,决定了公司在随后发展中呈现出的股权布局。如果最初的股权架构就存在先天不足,公司就很难顺利、稳定地成长起来。因此,创业之初,对股权设计应慎之又慎,避免留下巨大隐患和风险。两个人如...

消息称吴永辉入职后引发字节大模型团队架构大调整

2月21日,有消息称前谷歌大佬吴永辉加入字节跳动,并担任大模型团队Seed基础研究负责人后,引发了字节跳动大模型团队架构大调整。多名原本向朱文佳汇报的算法和技术负责人开始转向吴永辉汇报。简单来说,就是...

31页组织效能提升模型,经营管理团队搭建框架与权责定位

分享职场干货,提升能力!为职场精英打造个人知识体系,升职加薪!31页组织效能提升模型如何拿到分享的源文件:请您关注本头条号,然后私信本头条号“文米”2个字,按照操作流程,专人负责发送源文件给您。...

异形柱结构(异形柱结构技术规程)

下列关于混凝土异形柱结构设计的说法,其中何项正确?(A)混凝土异形柱框架结构可用于所有非抗震和抗震设防地区的一般居住建筑。(B)抗震设防烈度为6度时,对标准设防类(丙类)采用异形柱结构的建筑可不进行地...

职场干货:金字塔原理(金字塔原理实战篇)

金字塔原理的适用范围:金字塔原理适用于所有需要构建清晰逻辑框架的文章。第一篇:表达的逻辑。如何利用金字塔原理构建基本的金字塔结构受众(包括读者、听众、观众或学员)最容易理解的顺序:先了解主要的、抽象的...

底部剪力法(底部剪力法的基本原理)

某四层钢筋混凝土框架结构,计算简图如图1所示。抗震设防类别为丙类,抗震设防烈度为8度(0.2g),Ⅱ类场地,设计地震分组为第一组,第一自振周期T1=0.55s。一至四层的楼层侧向刚度依次为:K1=1...

结构等效重力荷载代表值(等效重力荷载系数)

某五层钢筋混凝土框架结构办公楼,房屋高度25.45m。抗震设防烈度8度,设防类别丙类,设计基本地震加速度0.2g,设计地震分组第二组,场地类别为Ⅱ类,混凝土强度等级C30。该结构平面和竖向均规则。假定...

体系结构已成昭告后世善莫大焉(体系构架是什么意思)

实践先行也理论已初步完成框架结构留余后人后世子孙俗话说前人栽树后人乘凉在夏商周大明大清民国共和前人栽树下吾之辈已完成结构体系又俗话说青出于蓝而胜于蓝各个时期任务不同吾辈探索框架结构体系经历有限肯定发展...

框架柱抗震构造要求(框架柱抗震设计)

某现浇钢筋混凝土框架-剪力墙结构高层办公楼,抗震设防烈度为8度(0.2g),场地类别为Ⅱ类,抗震等级:框架二级,剪力墙一级,混凝土强度等级:框架柱及剪力墙C50,框架梁及楼板C35,纵向钢筋及箍筋均采...

梁的刚度、挠度控制(钢梁挠度过大会引起什么原因)

某办公楼为现浇钢筋混凝土框架结构,r0=1.0,混凝土强度等级C35,纵向钢筋采用HRB400,箍筋采用HPB300。其二层(中间楼层)的局部平面图和次梁L-1的计算简图如图1~3(Z)所示,其中,K...

死要面子!有钱做大玻璃窗,却没有钱做“柱和梁”,不怕房塌吗?

活久见,有钱做2层落地大玻璃窗,却没有钱做“柱子和圈梁”,这样的农村自建房,安全吗?最近刷到个魔幻施工现场,如下图,这栋5开间的农村自建房,居然做了2个全景落地窗仔细观察,这2个落地窗还是飘窗,为了追...

不是承重墙,物业也不让拆?话说装修就一定要拆墙才行么

最近发现好多朋友装修时总想拆墙“爆改”空间,别以为只要避开承重墙就能随便砸!我家楼上邻居去年装修,拆了阳台矮墙想扩客厅,结果物业直接上门叫停。后来才知道,这种配重墙拆了会让阳台承重失衡,整栋楼都可能变...

取消回复欢迎 发表评论: